diff --git a/.changeset/flat-balloons-exercise.md b/.changeset/flat-balloons-exercise.md new file mode 100644 index 00000000..553dc29b --- /dev/null +++ b/.changeset/flat-balloons-exercise.md @@ -0,0 +1,5 @@ +--- +"penpot-exporter": minor +--- + +Shadows diff --git a/plugin-src/transformers/partials/index.ts b/plugin-src/transformers/partials/index.ts index 2efc6ae6..cd0e646c 100644 --- a/plugin-src/transformers/partials/index.ts +++ b/plugin-src/transformers/partials/index.ts @@ -1,6 +1,7 @@ export * from './transformBlend'; export * from './transformChildren'; export * from './transformDimensionAndPosition'; +export * from './transformEffects'; export * from './transformFills'; export * from './transformProportion'; export * from './transformSceneNode'; diff --git a/plugin-src/transformers/partials/transformEffects.ts b/plugin-src/transformers/partials/transformEffects.ts new file mode 100644 index 00000000..0eef6760 --- /dev/null +++ b/plugin-src/transformers/partials/transformEffects.ts @@ -0,0 +1,9 @@ +import { translateShadowEffects } from '@plugin/translators'; + +import { ShapeAttributes } from '@ui/lib/types/shape/shapeAttributes'; + +export const transformEffects = (node: BlendMixin): Partial => { + return { + shadow: translateShadowEffects(node.effects) + }; +}; diff --git a/plugin-src/transformers/transformEllipseNode.ts b/plugin-src/transformers/transformEllipseNode.ts index c60a32c8..e6e04d7d 100644 --- a/plugin-src/transformers/transformEllipseNode.ts +++ b/plugin-src/transformers/transformEllipseNode.ts @@ -1,6 +1,7 @@ import { transformBlend, transformDimensionAndPosition, + transformEffects, transformFills, transformProportion, transformSceneNode, @@ -18,6 +19,7 @@ export const transformEllipseNode = ( type: 'circle', name: node.name, ...transformFills(node), + ...transformEffects(node), ...transformStrokes(node), ...transformDimensionAndPosition(node, baseX, baseY), ...transformSceneNode(node), diff --git a/plugin-src/transformers/transformFrameNode.ts b/plugin-src/transformers/transformFrameNode.ts index 38771867..b3fc36f7 100644 --- a/plugin-src/transformers/transformFrameNode.ts +++ b/plugin-src/transformers/transformFrameNode.ts @@ -2,6 +2,7 @@ import { transformBlend, transformChildren, transformDimensionAndPosition, + transformEffects, transformFills, transformProportion, transformSceneNode, @@ -19,23 +20,29 @@ export const transformFrameNode = async ( baseX: number, baseY: number ): Promise => { + let frameSpecificAttributes: Partial = {}; + + if (!isSectionNode(node)) { + // Figma API does not expose strokes, blend modes, or constraint proportions for sections, + // they plan to add it in the future. Refactor this when available. + frameSpecificAttributes = { + // @see: https://forum.figma.com/t/why-are-strokes-not-available-on-section-nodes/41658 + ...transformStrokes(node), + // @see: https://forum.figma.com/t/add-a-blendmode-property-for-sectionnode/58560 + ...transformBlend(node), + ...transformProportion(node), + ...transformEffects(node) + }; + } + return { type: 'frame', name: node.name, showContent: isSectionNode(node) ? true : !node.clipsContent, ...transformFills(node), - // Figma API does not expose strokes for sections, - // they plan to add it in the future. Refactor this when available. - // @see: https://forum.figma.com/t/why-are-strokes-not-available-on-section-nodes/41658 - ...(isSectionNode(node) ? [] : transformStrokes(node)), + ...frameSpecificAttributes, ...(await transformChildren(node, baseX + node.x, baseY + node.y)), ...transformDimensionAndPosition(node, baseX, baseY), - // Figma API does not expose blend modes for sections, - // they plan to add it in the future. Refactor this when available. - // @see: https://forum.figma.com/t/add-a-blendmode-property-for-sectionnode/58560 - ...(isSectionNode(node) ? [] : transformBlend(node)), - ...transformSceneNode(node), - // Figma API does not expose constraints proportions for sections - ...(isSectionNode(node) ? [] : transformProportion(node)) + ...transformSceneNode(node) }; }; diff --git a/plugin-src/transformers/transformGroupNode.ts b/plugin-src/transformers/transformGroupNode.ts index bd499e40..c7698578 100644 --- a/plugin-src/transformers/transformGroupNode.ts +++ b/plugin-src/transformers/transformGroupNode.ts @@ -1,6 +1,7 @@ import { transformBlend, transformDimensionAndPosition, + transformEffects, transformSceneNode } from '@plugin/transformers/partials'; import { transformChildren } from '@plugin/transformers/partials'; @@ -17,6 +18,7 @@ export const transformGroupNode = async ( name: node.name, ...(await transformChildren(node, baseX, baseY)), ...transformDimensionAndPosition(node, baseX, baseY), + ...transformEffects(node), ...transformSceneNode(node), ...transformBlend(node) }; diff --git a/plugin-src/transformers/transformPathNode.ts b/plugin-src/transformers/transformPathNode.ts index 68ff3aa6..ad95f772 100644 --- a/plugin-src/transformers/transformPathNode.ts +++ b/plugin-src/transformers/transformPathNode.ts @@ -1,6 +1,7 @@ import { transformBlend, transformDimensionAndPosition, + transformEffects, transformFills, transformProportion, transformSceneNode, @@ -23,6 +24,7 @@ export const transformPathNode = ( name: node.name, ...(hasFillGeometry(node) ? transformFills(node) : []), ...transformStrokes(node), + ...transformEffects(node), ...transformVectorPaths(node, baseX, baseY), ...transformDimensionAndPosition(node, baseX, baseY), ...transformSceneNode(node), diff --git a/plugin-src/transformers/transformRectangleNode.ts b/plugin-src/transformers/transformRectangleNode.ts index e518f274..9c6b3ab2 100644 --- a/plugin-src/transformers/transformRectangleNode.ts +++ b/plugin-src/transformers/transformRectangleNode.ts @@ -1,6 +1,7 @@ import { transformBlend, transformDimensionAndPosition, + transformEffects, transformFills, transformProportion, transformSceneNode, @@ -18,6 +19,7 @@ export const transformRectangleNode = ( type: 'rect', name: node.name, ...transformFills(node), + ...transformEffects(node), ...transformStrokes(node), ...transformDimensionAndPosition(node, baseX, baseY), ...transformSceneNode(node), diff --git a/plugin-src/transformers/transformTextNode.ts b/plugin-src/transformers/transformTextNode.ts index 227db8ba..952218a1 100644 --- a/plugin-src/transformers/transformTextNode.ts +++ b/plugin-src/transformers/transformTextNode.ts @@ -1,6 +1,7 @@ import { transformBlend, transformDimensionAndPosition, + transformEffects, transformFills, transformProportion, transformSceneNode, @@ -42,6 +43,7 @@ export const transformTextNode = (node: TextNode, baseX: number, baseY: number): ] }, ...transformDimensionAndPosition(node, baseX, baseY), + ...transformEffects(node), ...transformSceneNode(node), ...transformBlend(node), ...transformProportion(node) diff --git a/plugin-src/translators/index.ts b/plugin-src/translators/index.ts index 953fd238..e6acf142 100644 --- a/plugin-src/translators/index.ts +++ b/plugin-src/translators/index.ts @@ -1,4 +1,5 @@ export * from './translateBlendMode'; +export * from './translateShadowEffects'; export * from './translateFills'; export * from './translateStrokes'; export * from './translateStyledTextSegments'; diff --git a/plugin-src/translators/translateShadowEffects.ts b/plugin-src/translators/translateShadowEffects.ts new file mode 100644 index 00000000..ec459214 --- /dev/null +++ b/plugin-src/translators/translateShadowEffects.ts @@ -0,0 +1,45 @@ +import { rgbToHex } from '@plugin/utils'; + +import { Shadow, ShadowStyle } from '@ui/lib/types/utils/shadow'; + +export const translateShadowEffect = (effect: Effect): Shadow | undefined => { + if (effect.type !== 'DROP_SHADOW' && effect.type !== 'INNER_SHADOW') { + return; + } + + return { + style: translateShadowType(effect), + offsetX: effect.offset.x, + offsetY: effect.offset.y, + blur: effect.radius, + spread: effect.spread ?? 0, + hidden: !effect.visible, + color: { + color: rgbToHex(effect.color), + opacity: effect.color.a + } + }; +}; + +export const translateShadowEffects = (effects: readonly Effect[]): Shadow[] => { + const shadows: Shadow[] = []; + + for (const effect of effects) { + const shadow = translateShadowEffect(effect); + if (shadow) { + // effects are applied in reverse order in Figma, that's why we unshift + shadows.unshift(shadow); + } + } + + return shadows; +}; + +const translateShadowType = (effect: DropShadowEffect | InnerShadowEffect): ShadowStyle => { + switch (effect.type) { + case 'DROP_SHADOW': + return 'drop-shadow'; + case 'INNER_SHADOW': + return 'inner-shadow'; + } +}; diff --git a/ui-src/lib/types/utils/color.d.ts b/ui-src/lib/types/utils/color.d.ts index 53072ab5..1a546b38 100644 --- a/ui-src/lib/types/utils/color.d.ts +++ b/ui-src/lib/types/utils/color.d.ts @@ -12,6 +12,6 @@ export type Color = { modifiedAt?: string; //@TODO: check this attribute in penpot refId?: Uuid; refFile?: Uuid; - gradient: Gradient; + gradient?: Gradient; image?: ImageColor; }; diff --git a/ui-src/lib/types/utils/shadow.d.ts b/ui-src/lib/types/utils/shadow.d.ts index f2d3b175..b7f53337 100644 --- a/ui-src/lib/types/utils/shadow.d.ts +++ b/ui-src/lib/types/utils/shadow.d.ts @@ -2,9 +2,11 @@ import { Color } from '@ui/lib/types/utils/color'; import { Uuid } from './uuid'; +export type ShadowStyle = 'drop-shadow' | 'inner-shadow'; + export type Shadow = { id?: Uuid; - style: symbol; // 'drop-shadow' | 'inner-shadow' + style: ShadowStyle; offsetX: number; offsetY: number; blur: number;