diff --git a/examples/lorem-ipsum/build/diez-lorem-ipsum-web/package.json b/examples/lorem-ipsum/build/diez-lorem-ipsum-web/package.json index 81f0e5aad..f52270b79 100644 --- a/examples/lorem-ipsum/build/diez-lorem-ipsum-web/package.json +++ b/examples/lorem-ipsum/build/diez-lorem-ipsum-web/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@diez/web-sdk-common": "^10.0.0-beta.2", + "@diez/web-sdk-common": "^10.0.0-beta.3", "lottie-web": "^5.5.2" } } diff --git a/examples/lorem-ipsum/examples/ios/LoremIpsum/ViewController.swift b/examples/lorem-ipsum/examples/ios/LoremIpsum/ViewController.swift index b37483c3c..bcbda2654 100644 --- a/examples/lorem-ipsum/examples/ios/LoremIpsum/ViewController.swift +++ b/examples/lorem-ipsum/examples/ios/LoremIpsum/ViewController.swift @@ -35,6 +35,7 @@ class ViewController: UIViewController { } view.iconView.image = designSystem.images.logo.uiImage + view.iconView.layer.apply(designSystem.shadows.logo) view.contentBackgroundView.backgroundColor = designSystem.palette.contentBackground.uiColor let margin = designSystem.layoutValues.contentMargin diff --git a/examples/lorem-ipsum/examples/web/src/App.module.scss b/examples/lorem-ipsum/examples/web/src/App.module.scss index a85feb22f..210d3d953 100644 --- a/examples/lorem-ipsum/examples/web/src/App.module.scss +++ b/examples/lorem-ipsum/examples/web/src/App.module.scss @@ -64,6 +64,7 @@ .icon { @include images-logo-background-image(); + @include shadows-logo-filter(); transform: translate(0, -50%); position: absolute; margin-left: $layout-values-spacing-medium-px; diff --git a/examples/lorem-ipsum/src/DesignSystem.ts b/examples/lorem-ipsum/src/DesignSystem.ts index 5e68e45fb..4740f197d 100644 --- a/examples/lorem-ipsum/src/DesignSystem.ts +++ b/examples/lorem-ipsum/src/DesignSystem.ts @@ -1,4 +1,4 @@ -import {Color, Image, Lottie, Toward, Typograph, Font, LinearGradient} from '@diez/prefabs'; +import {Color, DropShadow, Image, Lottie, Toward, Typograph, Font, LinearGradient, Point2D} from '@diez/prefabs'; import {Margin} from './components/Margin'; /** @@ -106,6 +106,14 @@ class Strings { helper = 'Modify the contents of “src/DesignSystem.ts” (relative to the root of the Diez project) to see changes to the design system in real time.'; } +class Shadows { + logo = new DropShadow({ + offset: Point2D.make(0, 1), + radius: 16, + color: colors.black.fade(0.59), + }); +} + /** * Note how this component is exported from `index.ts`. Diez compiles these * exported components for your apps' codebases. @@ -128,4 +136,5 @@ export class DesignSystem { layoutValues = new LayoutValues(); strings = new Strings(); loadingAnimation = Lottie.fromJson('assets/loadingAnimation.json', false); + shadows = new Shadows(); } diff --git a/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard.png b/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard.png index b3e579eea..d670c6ccb 100644 Binary files a/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard.png and b/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard.png differ diff --git a/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard@2x.png b/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard@2x.png index f939e63f9..6d74eb70e 100644 Binary files a/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard@2x.png and b/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard@2x.png differ diff --git a/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard@3x.png b/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard@3x.png index bbb314922..f32e7b949 100644 Binary files a/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard@3x.png and b/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard@3x.png differ diff --git a/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard@4x.png b/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard@4x.png index 8909225f5..d953a6d96 100644 Binary files a/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard@4x.png and b/examples/poodle-surf/assets/PoodleSurf.sketch.contents/slices/Artboard@4x.png differ diff --git a/examples/poodle-surf/build/diez-poodle-surf-web/package.json b/examples/poodle-surf/build/diez-poodle-surf-web/package.json index d28d88670..06650f177 100644 --- a/examples/poodle-surf/build/diez-poodle-surf-web/package.json +++ b/examples/poodle-surf/build/diez-poodle-surf-web/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@diez/web-sdk-common": "^10.0.0-beta.2", + "@diez/web-sdk-common": "^10.0.0-beta.3", "lottie-web": "^5.5.2" } } diff --git a/examples/poodle-surf/designs/PoodleSurf.sketch b/examples/poodle-surf/designs/PoodleSurf.sketch index 681a45eba..513828349 100644 Binary files a/examples/poodle-surf/designs/PoodleSurf.sketch and b/examples/poodle-surf/designs/PoodleSurf.sketch differ diff --git a/examples/poodle-surf/examples/ios-objc/PoodleSurfObjC/ViewController.m b/examples/poodle-surf/examples/ios-objc/PoodleSurfObjC/ViewController.m index 29304c76e..de6bb2c95 100644 --- a/examples/poodle-surf/examples/ios-objc/PoodleSurfObjC/ViewController.m +++ b/examples/poodle-surf/examples/ios-objc/PoodleSurfObjC/ViewController.m @@ -87,6 +87,8 @@ - (void)viewDidLoad { imageView.image = component.designs.navigationTitle.icon.uiImage; + [imageView.layer dez_applyDropShadow:component.designs.report.wind.shared.dropShadow]; + [gradientView.gradientLayer dez_applyLinearGradient:component.designs.report.waterTemperature.shared.gradient]; [animationView dez_loadLottie:component.designs.loading.animation completion:nil]; diff --git a/examples/poodle-surf/examples/ios/PoodleSurf/Report/ReportViewController+Diez.swift b/examples/poodle-surf/examples/ios/PoodleSurf/Report/ReportViewController+Diez.swift index 606db5c40..aff62a50f 100644 --- a/examples/poodle-surf/examples/ios/PoodleSurf/Report/ReportViewController+Diez.swift +++ b/examples/poodle-surf/examples/ios/PoodleSurf/Report/ReportViewController+Diez.swift @@ -76,8 +76,9 @@ extension ReportViewController { view.titleLabel.attributedText = design.titleTypograph.attributedString(decorating: design.title) view.titleContentSpacing = design.titleContentSpacing view.layoutMargins = UIEdgeInsets(design.layoutMargins) - view.cornerRadius = design.cornerRadius view.backgroundView.gradientLayer.apply(design.gradient) + view.backgroundView.cornerRadius = design.cornerRadius + view.layer.apply(design.dropShadow) } private func apply(_ design: DayPartDesign, to view: DayPartView) { diff --git a/examples/poodle-surf/examples/web/src/App.tsx b/examples/poodle-surf/examples/web/src/App.tsx index c9738c288..c139d214c 100644 --- a/examples/poodle-surf/examples/web/src/App.tsx +++ b/examples/poodle-surf/examples/web/src/App.tsx @@ -31,8 +31,10 @@ class App extends React.PureComponent<{}, {ds: DesignSystem, mocks: ModelMocks}> />
}} > - +
diff --git a/examples/poodle-surf/examples/web/src/components/Card.tsx b/examples/poodle-surf/examples/web/src/components/Card.tsx index e5e357ffe..b1f445ad0 100644 --- a/examples/poodle-surf/examples/web/src/components/Card.tsx +++ b/examples/poodle-surf/examples/web/src/components/Card.tsx @@ -14,6 +14,7 @@ export default class Card extends React.PureComponent { return (
{ paddingRight: ds.layoutMargins.right, borderRadius: ds.cornerRadius, marginBottom: ds.layoutMargins.bottom, - breakInside: 'avoid', + boxShadow: ds.dropShadow.boxShadow, display: 'flex', flexWrap: 'wrap', flexDirection: 'column', + width: '42%', }} >

{ds.title}

diff --git a/examples/poodle-surf/examples/web/src/components/ForecastCard.tsx b/examples/poodle-surf/examples/web/src/components/ForecastCard.tsx index ec29e8bd0..a62830960 100644 --- a/examples/poodle-surf/examples/web/src/components/ForecastCard.tsx +++ b/examples/poodle-surf/examples/web/src/components/ForecastCard.tsx @@ -19,15 +19,12 @@ export default class ForecastCard extends React.PureComponent
{Object.values(mocks).map((dayPart: WindDayPartMock) => ( () { @@ -45,7 +46,8 @@ class SharedCardDesign extends prefab() { left: LayoutValues.DefaultMargin, right: LayoutValues.DefaultMargin, }), - cornerRadius: 5, + cornerRadius: 8, + dropShadow: shadows.card, }; } diff --git a/examples/poodle-surf/src/designs/constants.ts b/examples/poodle-surf/src/designs/constants.ts index 957f5e539..f395de954 100644 --- a/examples/poodle-surf/src/designs/constants.ts +++ b/examples/poodle-surf/src/designs/constants.ts @@ -17,6 +17,12 @@ class Palette { */ export const palette = new Palette(); +class Shadows { + card = poodleSurfTokens.shadows.cardStyleDropShadow; +} + +export const shadows = new Shadows(); + /** * A registry of all of the design's fonts. */ diff --git a/packages/generation/src/api.ts b/packages/generation/src/api.ts index 1f20ad823..8089390af 100644 --- a/packages/generation/src/api.ts +++ b/packages/generation/src/api.ts @@ -53,6 +53,7 @@ export interface CodegenDesignSystem { projectRoot: string; colors: CodegenEntity[]; gradients: CodegenEntity[]; + shadows: CodegenEntity[]; typographs: CodegenEntity[]; fonts: GeneratedFonts; assets: GeneratedAssets; diff --git a/packages/generation/src/constants.ts b/packages/generation/src/constants.ts new file mode 100644 index 000000000..59183de89 --- /dev/null +++ b/packages/generation/src/constants.ts @@ -0,0 +1,6 @@ +/** + * The precision to use when rounding numbers to a reasonable float value. + * + * @ignore + */ +export const floatPrecision = 6; diff --git a/packages/generation/src/drop-shadow.ts b/packages/generation/src/drop-shadow.ts new file mode 100644 index 000000000..1bebc2ede --- /dev/null +++ b/packages/generation/src/drop-shadow.ts @@ -0,0 +1,16 @@ +import {floatPrecision} from './constants'; +import {GeneratedPoint2D, getPoint2DInitializer} from './point2d'; + +interface GeneratedDropShadow { + colorInitializer: string; + offset: GeneratedPoint2D; + radius: number; +} + +/** + * Returns a drop shadow initializer. + * @ignore + */ +export const getDropShadowInitializer = (shadow: GeneratedDropShadow) => { + return `new DropShadow({offset: ${getPoint2DInitializer(shadow.offset)}, radius: ${shadow.radius.toFixed(floatPrecision)}, color: ${shadow.colorInitializer}})`; +}; diff --git a/packages/generation/src/index.ts b/packages/generation/src/index.ts index fcff1dfbf..8d034f664 100644 --- a/packages/generation/src/index.ts +++ b/packages/generation/src/index.ts @@ -1,5 +1,7 @@ export * from './api'; export * from './color'; +export * from './drop-shadow'; export * from './linear-gradient'; +export * from './point2d'; export * from './typography'; export * from './utils'; diff --git a/packages/generation/src/linear-gradient.ts b/packages/generation/src/linear-gradient.ts index 1600d7a92..b49976bbe 100644 --- a/packages/generation/src/linear-gradient.ts +++ b/packages/generation/src/linear-gradient.ts @@ -1,25 +1,22 @@ -interface GeneratedPoint2D { - x: number; - y: number; -} +import {floatPrecision} from './constants'; +import {GeneratedPoint2D, getPoint2DInitializer} from './point2d'; interface GeneratedGradientStop { position: number; colorInitializer: string; } -const FloatPrecision = 6; - -const getPoint2DInitializer = (point: GeneratedPoint2D) => - `Point2D.make(${point.x.toFixed(FloatPrecision)}, ${point.y.toFixed(FloatPrecision)})`; - /** * Returns a linear gradient initializer. * @ignore */ -export const getLinearGradientInitializer = (stops: GeneratedGradientStop[], start: GeneratedPoint2D, end: GeneratedPoint2D) => { +export const getLinearGradientInitializer = ( + stops: GeneratedGradientStop[], + start: GeneratedPoint2D, + end: GeneratedPoint2D, +) => { const colorStopInitializers = stops.map((stop) => { - return `GradientStop.make(${stop.position.toFixed(FloatPrecision)}, ${stop.colorInitializer})`; + return `GradientStop.make(${stop.position.toFixed(floatPrecision)}, ${stop.colorInitializer})`; }); const colorStopInitializer = `[${colorStopInitializers.join(', ')}]`; return `new LinearGradient({stops: ${colorStopInitializer}, start: ${getPoint2DInitializer(start)}, end: ${getPoint2DInitializer(end)}})`; diff --git a/packages/generation/src/point2d.ts b/packages/generation/src/point2d.ts new file mode 100644 index 000000000..52738c484 --- /dev/null +++ b/packages/generation/src/point2d.ts @@ -0,0 +1,17 @@ +import {floatPrecision} from './constants'; + +/** + * A 2D point. + * @ignore + */ +export interface GeneratedPoint2D { + x: number; + y: number; +} + +/** + * Returns a 2D point initializer. + * @ignore + */ +export const getPoint2DInitializer = (point: GeneratedPoint2D) => + `Point2D.make(${point.x.toFixed(floatPrecision)}, ${point.y.toFixed(floatPrecision)})`; diff --git a/packages/generation/src/utils.ts b/packages/generation/src/utils.ts index 867420faa..0fd1c7c9d 100644 --- a/packages/generation/src/utils.ts +++ b/packages/generation/src/utils.ts @@ -67,6 +67,7 @@ export const createDesignSystemSpec = ( projectRoot, colors: [], gradients: [], + shadows: [], typographs: [], fonts: new Map(), assets: new Map(), @@ -157,10 +158,12 @@ export const codegenDesignSystem = async (spec: CodegenDesignSystem) => { const designSystemImports = new Set(); const colorsName = localResolver.getComponentName(`${designSystemName} Colors`); const gradientsName = localResolver.getComponentName(`${designSystemName} Gradients`); + const shadowsName = localResolver.getComponentName(`${designSystemName} Shadows`); const typographsName = localResolver.getComponentName(`${designSystemName} Typographs`); const hasColors = spec.colors.length > 0; const hasGradients = spec.gradients.length > 0; + const hasShadows = spec.shadows.length > 0; const hasTypographs = spec.typographs.length > 0; if (hasColors) { @@ -194,6 +197,22 @@ export const codegenDesignSystem = async (spec: CodegenDesignSystem) => { }); } + if (hasShadows) { + designSystemImports.add('Color'); + designSystemImports.add('Point2D'); + designSystemImports.add('DropShadow'); + sourceFile.addClass({ + name: shadowsName, + properties: spec.shadows.map(({name, initializer}) => { + const shadowName = localResolver.getPropertyName(name || 'Untitled Shadow', shadowsName); + return { + initializer, + name: shadowName, + }; + }), + }); + } + if (hasTypographs) { designSystemImports.add('Color'); designSystemImports.add('Typograph'); @@ -304,6 +323,13 @@ export const codegenDesignSystem = async (spec: CodegenDesignSystem) => { }); } + if (hasShadows) { + exportedClassDeclaration.addProperty({ + name: 'shadows', + initializer: `new ${shadowsName}()`, + }); + } + if (hasTypographs) { designSystemImports.add('Font'); exportedClassDeclaration.addProperty({ diff --git a/packages/generation/test/codegen.e2e.test.ts b/packages/generation/test/codegen.e2e.test.ts index c7da498d0..f333ac578 100644 --- a/packages/generation/test/codegen.e2e.test.ts +++ b/packages/generation/test/codegen.e2e.test.ts @@ -120,6 +120,17 @@ describe('codegen.e2e', () => { }, ); + spec.shadows.push( + { + name: '', + initializer: '6', + }, + { + name: 'Some Shadow', + initializer: '7', + }, + ); + registerAsset( { src: 'assets/blah/Foobar.png', diff --git a/packages/generation/test/drop-shadow.test.ts b/packages/generation/test/drop-shadow.test.ts new file mode 100644 index 000000000..4b38a5024 --- /dev/null +++ b/packages/generation/test/drop-shadow.test.ts @@ -0,0 +1,17 @@ +import {getDropShadowInitializer} from '../src/drop-shadow'; +import {getPoint2DInitializer} from '../src/point2d'; + +describe('drop-shadow', () => { + test('initializer', () => { + const offset = { + x: 0.1234567890, + y: 0.1234567890, + }; + const initializer = getDropShadowInitializer({ + offset, + radius: 0.1234567890, + colorInitializer: 'init', + }); + expect(initializer).toBe(`new DropShadow({offset: ${getPoint2DInitializer(offset)}, radius: 0.123457, color: init})`); + }); +}); diff --git a/packages/generation/test/goldens/codegennable/src/index.ts b/packages/generation/test/goldens/codegennable/src/index.ts index 8f0ecb188..f4c0ffd77 100644 --- a/packages/generation/test/goldens/codegennable/src/index.ts +++ b/packages/generation/test/goldens/codegennable/src/index.ts @@ -1,4 +1,4 @@ -import { Color, File, Font, GradientStop, Image, LinearGradient, Point2D, Typograph } from "@diez/prefabs"; +import { Color, DropShadow, File, Font, GradientStop, Image, LinearGradient, Point2D, Typograph } from "@diez/prefabs"; class MyDesignSystemColors { untitledColor = 2; @@ -10,6 +10,11 @@ class MyDesignSystemGradients { someGradient = 5; } +class MyDesignSystemShadows { + untitledShadow = 6; + someShadow = 7; +} + class MyDesignSystemTypographs { untitledTypograph = 0; someTypograph = 1; @@ -41,6 +46,7 @@ export const MyDesignSystemFonts = { export class MyDesignSystemTokens { colors = new MyDesignSystemColors(); gradients = new MyDesignSystemGradients(); + shadows = new MyDesignSystemShadows(); typographs = new MyDesignSystemTypographs(); } diff --git a/packages/generation/test/linear-gradient.test.ts b/packages/generation/test/linear-gradient.test.ts index 4af38c34e..388b4920d 100644 --- a/packages/generation/test/linear-gradient.test.ts +++ b/packages/generation/test/linear-gradient.test.ts @@ -1,7 +1,7 @@ import {getLinearGradientInitializer} from '../src/linear-gradient'; describe('linear-gradient', () => { - test('initializers', () => { + test('initializer', () => { const start = {x: 0.1234567890, y: 0.1234567890}; const end = {x: 1.1234567890, y: 1.1234567890}; const stops = [ diff --git a/packages/generation/test/point2d.test.ts b/packages/generation/test/point2d.test.ts new file mode 100644 index 000000000..31af0f999 --- /dev/null +++ b/packages/generation/test/point2d.test.ts @@ -0,0 +1,9 @@ +import {getPoint2DInitializer} from '../src/point2d'; + +describe('point2d', () => { + test('initializer', () => { + const point = {x: 0.1234567890, y: 0.1234567890}; + const initializer = getPoint2DInitializer(point); + expect(initializer).toBe('Point2D.make(0.123457, 0.123457)'); + }); +}); diff --git a/packages/prefabs/src/drop-shadow.ts b/packages/prefabs/src/drop-shadow.ts new file mode 100644 index 000000000..de5551f53 --- /dev/null +++ b/packages/prefabs/src/drop-shadow.ts @@ -0,0 +1,40 @@ +import {prefab} from '@diez/engine'; +import {Color} from './color'; +import {Point2D} from './point2d'; + +/** + * DropShadow data. + */ +export interface DropShadowData { + /** + * The offset of the drop shadow. + * + * This is defined in a coordinate space where x+ is right and y+ is down. + */ + offset: Point2D; + /** + * The blur radius of the drop shadow. + * + * This value assumes a Guassian blur and equals double the Guassian blur's standard deviation value. + * + * @see {@link https://drafts.csswg.org/css-backgrounds-3/#shadow-blur} + */ + radius: number; + /** + * The color of the drop shadow. + */ + color: Color; +} + +/** + * Provides a drop shadow. + * + * @noinheritdoc + */ +export class DropShadow extends prefab() { + defaults = { + offset: Point2D.make(0, 0), + radius: 0, + color: Color.rgb(0, 0, 0), + }; +} diff --git a/packages/prefabs/src/index.ts b/packages/prefabs/src/index.ts index bce600b83..8492662db 100644 --- a/packages/prefabs/src/index.ts +++ b/packages/prefabs/src/index.ts @@ -1,4 +1,5 @@ export * from './color'; +export * from './drop-shadow'; export * from './file'; export * from './image'; export * from './linear-gradient'; diff --git a/packages/prefabs/test/drop-shadow.test.ts b/packages/prefabs/test/drop-shadow.test.ts new file mode 100644 index 000000000..0800be73d --- /dev/null +++ b/packages/prefabs/test/drop-shadow.test.ts @@ -0,0 +1,28 @@ +import {Color} from '../src/color'; +import {DropShadow} from '../src/drop-shadow'; +import {Point2D} from '../src/point2d'; + +describe('DropShadow', () => { + test('basic functionality', () => { + const color = Color.hsla(0, 0.25, 0.5, 0.75); + const point = Point2D.make(1, 2); + const shadow = new DropShadow({ + color, + offset: point, + radius: 3, + }); + expect(shadow.serialize()).toEqual({ + color: { + h: 0, + s: 0.25, + l: 0.5, + a: 0.75, + }, + offset: { + x: 1, + y: 2, + }, + radius: 3, + }); + }); +}); diff --git a/packages/sources/src/exporters/figma.ts b/packages/sources/src/exporters/figma.ts index ddf04979c..a6cd06dad 100644 --- a/packages/sources/src/exporters/figma.ts +++ b/packages/sources/src/exporters/figma.ts @@ -4,6 +4,7 @@ import { codegenDesignSystem, CodegenDesignSystem, createDesignSystemSpec, + getDropShadowInitializer, getLinearGradientInitializer, getTypographInitializer, locateFont, @@ -55,7 +56,7 @@ const folders = new Map([ * Describes a Figma paint type retrieved from the Figma API. * @ignore */ -export const enum FigmaPaintType { +const enum FigmaPaintType { Solid = 'SOLID', GradientLinear = 'GRADIENT_LINEAR', } @@ -67,6 +68,11 @@ interface FigmaColor { a: number; } +interface FigmaColorStop { + position: number; + color: FigmaColor; +} + interface FigmaLinearGradient { type: FigmaPaintType.GradientLinear; gradientHandlePositions: FigmaVector[]; @@ -88,16 +94,32 @@ const isFigmaSolid = (paint: FigmaPaint): paint is FigmaSolid => { return paint.type === FigmaPaintType.Solid; }; +/** + * Describes a Figma effect type retrieved from the Figma API. + * @ignore + */ +const enum FigmaEffectType { + DropShadow = 'DROP_SHADOW', +} + interface FigmaVector { x: number; y: number; } -interface FigmaColorStop { - position: number; +interface FigmaDropShadow { + type: FigmaEffectType.DropShadow; color: FigmaColor; + offset: FigmaVector; + radius: number; } +type FigmaEffect = FigmaDropShadow | {type: unknown}; + +const isFigmaDropShadow = (effect: FigmaEffect): effect is FigmaDropShadow => { + return effect.type === FigmaEffectType.DropShadow; +}; + interface FigmaTextStyle { fontFamily: string; fontPostScriptName: string; @@ -114,9 +136,11 @@ interface FigmaNode { name: string; children?: FigmaNode[]; absoluteBoundingBox?: FigmaDimensions; + effects?: FigmaEffect[]; fills?: FigmaPaint[]; style?: FigmaTextStyle; styles?: { + effect?: string; fill?: string; text?: string; }; @@ -134,7 +158,7 @@ export interface FigmaFile { styles?: { [id: string]: { name: string; - styleType: 'FILL' | 'TEXT'; + styleType: 'FILL' | 'TEXT' | 'EFFECT'; }; }; components?: { @@ -233,30 +257,29 @@ const downloadAssets = async ( return Promise.all(streams); }; -const populateInitializerForFigmaPaint = (paint: FigmaPaint, name: string, spec: CodegenDesignSystem) => { - if (isFigmaSolid(paint)) { - spec.colors.push({ - name, - initializer: getSolidInitializerFromFigma(paint), - }); - return; - } +const getDropShadowInitializerFromFigma = (shadow: FigmaDropShadow) => + getDropShadowInitializer({ + offset: shadow.offset, + radius: shadow.radius, + colorInitializer: getColorInitializerFromFigma(shadow.color), + }); - if (isFigmaLinearGradient(paint)) { - spec.gradients.push({ +const populateInitializerForFigmaEffect = (effect: FigmaEffect, name: string, spec: CodegenDesignSystem) => { + if (isFigmaDropShadow(effect)) { + spec.shadows.push({ name, - initializer: getLinearGradientInitializerFromFigma(paint), + initializer: getDropShadowInitializerFromFigma(effect), }); return; } }; -const getSolidInitializerFromFigma = (solid: FigmaSolid) => - getColorInitializerFromFigma(solid.color); - const getColorInitializerFromFigma = ({r, g, b, a}: FigmaColor) => `Color.rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${a})`; +const getSolidInitializerFromFigma = (solid: FigmaSolid) => + getColorInitializerFromFigma(solid.color); + const getLinearGradientInitializerFromFigma = (gradient: FigmaLinearGradient) => { const stops = gradient.gradientStops.map((stop) => { return { @@ -267,6 +290,24 @@ const getLinearGradientInitializerFromFigma = (gradient: FigmaLinearGradient) => return getLinearGradientInitializer(stops, gradient.gradientHandlePositions[0], gradient.gradientHandlePositions[1]); }; +const populateInitializerForFigmaPaint = (paint: FigmaPaint, name: string, spec: CodegenDesignSystem) => { + if (isFigmaSolid(paint)) { + spec.colors.push({ + name, + initializer: getSolidInitializerFromFigma(paint), + }); + return; + } + + if (isFigmaLinearGradient(paint)) { + spec.gradients.push({ + name, + initializer: getLinearGradientInitializerFromFigma(paint), + }); + return; + } +}; + const getInitializerForTypographColorFromFigma = (node: FigmaNode) => { const fill = node.fills && node.fills[0]; if (!fill) { @@ -288,17 +329,23 @@ const getInitializerForTypographColorFromFigma = (node: FigmaNode) => { const processFigmaNode = async ( spec: CodegenDesignSystem, + effects: Map, fills: Map, typographs: Map, components: Set, componentDimensions: Map, node: FigmaNode, ) => { - if (!fills.size && !typographs.size && !components.size) { + if (!fills.size && !effects.size && !typographs.size && !components.size) { return; } if (node.styles) { + if (effects.size && node.styles.effect && effects.has(node.styles.effect) && node.effects && node.effects.length) { + populateInitializerForFigmaEffect(node.effects[0], effects.get(node.styles.effect)!, spec); + effects.delete(node.styles.effect); + } + if (fills.size && node.styles.fill && fills.has(node.styles.fill) && node.fills && node.fills.length) { populateInitializerForFigmaPaint(node.fills[0], fills.get(node.styles.fill)!, spec); fills.delete(node.styles.fill); @@ -336,7 +383,7 @@ const processFigmaNode = async ( if (node.children) { for (const childNode of node.children) { - await processFigmaNode(spec, fills, typographs, components, componentDimensions, childNode); + await processFigmaNode(spec, effects, fills, typographs, components, componentDimensions, childNode); } } }; @@ -352,12 +399,14 @@ const parseFigmaFile = async ( const styles = Object.entries(file.styles || {}); const fills = new Map(styles.filter( ([_, {styleType}]) => styleType === 'FILL').map(([id, {name}]) => [id, name])); + const effects = new Map(styles.filter( + ([_, {styleType}]) => styleType === 'EFFECT').map(([id, {name}]) => [id, name])); const typographs = new Map(styles.filter( ([_, {styleType}]) => styleType === 'TEXT').map(([id, {name}]) => [id, name])); const components = new Set(filenameMap.keys()); const componentDimensions = new Map(); for (const node of file.document.children) { - await processFigmaNode(spec, fills, typographs, components, componentDimensions, node); + await processFigmaNode(spec, effects, fills, typographs, components, componentDimensions, node); } for (const [id, filename] of filenameMap) { diff --git a/packages/sources/src/exporters/sketch.ts b/packages/sources/src/exporters/sketch.ts index 441ffff2d..88eee9023 100644 --- a/packages/sources/src/exporters/sketch.ts +++ b/packages/sources/src/exporters/sketch.ts @@ -1,11 +1,12 @@ import {execAsync, isMacOS, Log} from '@diez/cli-core'; import { AssetFolder, - CodegenDesignSystem, codegenDesignSystem, + CodegenDesignSystem, createDesignSystemSpec, GeneratedAssets, getColorInitializer, + getDropShadowInitializer, getLinearGradientInitializer, getTypographInitializer, locateFont, @@ -99,6 +100,20 @@ interface SketchSharedTypograph { }; } +interface SketchDropShadow { + offsetX: number; + offsetY: number; + blurRadius: number; + color: SketchColor; +} + +interface SketchSharedLayerStyle { + name: string; + value: { + shadows: SketchDropShadow[]; + }; +} + interface SketchLayer { ['']: string; exportOptions: { @@ -117,6 +132,9 @@ interface SketchDump { layerTextStyles: { objects: SketchSharedTypograph[]; }; + layerStyles: { + objects: SketchSharedLayerStyle[]; + }; pages: SketchLayer[]; } @@ -143,16 +161,6 @@ const populateAssets = (assetsDirectory: string, layers: SketchLayer[], extracte } }; -const populateInitializerForSketchGradient = (gradient: SketchGradient, name: string, spec: CodegenDesignSystem) => { - if (isSketchLinearGradient(gradient)) { - spec.gradients.push({ - name, - initializer: getLinearGradientInitializerForSketchGradient(gradient), - }); - return; - } -}; - const getLinearGradientInitializerForSketchGradient = (gradient: SketchLinearGradient) => { const stops = gradient.stops.map((stop) => { return { @@ -163,6 +171,16 @@ const getLinearGradientInitializerForSketchGradient = (gradient: SketchLinearGra return getLinearGradientInitializer(stops, gradient.from, gradient.to); }; +const populateInitializerForSketchGradient = (gradient: SketchGradient, name: string, spec: CodegenDesignSystem) => { + if (isSketchLinearGradient(gradient)) { + spec.gradients.push({ + name, + initializer: getLinearGradientInitializerForSketchGradient(gradient), + }); + return; + } +}; + class SketchExporterImplementation implements Exporter { /** * ExporterFactory interface method. @@ -236,6 +254,22 @@ class SketchExporterImplementation implements Exporter { populateInitializerForSketchGradient(gradient.gradient, gradient.name, codegenSpec); } + for (const style of dump.layerStyles.objects.filter((object) => object.value.shadows.length)) { + const shadow = style.value.shadows[0]!; + const initializer = getDropShadowInitializer({ + colorInitializer: getColorInitializer(shadow.color.value), + offset: { + x: shadow.offsetX, + y: shadow.offsetY, + }, + radius: shadow.blurRadius, + }); + codegenSpec.shadows.push({ + initializer, + name: `${style.name} Drop Shadow`, + }); + } + for (const {name, value: {textStyle}} of dump.layerTextStyles.objects) { const fontSize = textStyle.NSFont.attributes.NSFontSizeAttribute; const candidateFont = await locateFont( diff --git a/packages/sources/test/exporters/figma.test.ts b/packages/sources/test/exporters/figma.test.ts index 1598b029a..4c776a77a 100644 --- a/packages/sources/test/exporters/figma.test.ts +++ b/packages/sources/test/exporters/figma.test.ts @@ -19,7 +19,7 @@ jest.doMock('@diez/storage', () => ({ })); import {Readable} from 'stream'; -import {FigmaExporter, FigmaFile, FigmaPaintType} from '../../src/exporters/figma'; +import {FigmaExporter, FigmaFile} from '../../src/exporters/figma'; const figma = FigmaExporter.create('mock-token'); @@ -50,7 +50,7 @@ const mockFullResponse: FigmaFile = { id: '', name: '', fills: [{ - type: FigmaPaintType.Solid, + type: 'SOLID', color: { r: 0.03921568627451, g: 0.03921568627451, @@ -66,7 +66,7 @@ const mockFullResponse: FigmaFile = { id: '', name: '', fills: [{ - type: FigmaPaintType.Solid, + type: 'SOLID', color: { r: 0.392156862745098, g: 0.392156862745098, @@ -87,7 +87,7 @@ const mockFullResponse: FigmaFile = { id: '', name: '', fills: [{ - type: FigmaPaintType.GradientLinear, + type: 'GRADIENT_LINEAR', gradientHandlePositions: [ { x: 2.220446049250313e-16, @@ -138,7 +138,7 @@ const mockFullResponse: FigmaFile = { id: '', name: '', fills: [{ - type: FigmaPaintType.GradientLinear, + type: 'GRADIENT_LINEAR', gradientHandlePositions: [ { x: 2.220446049250313e-16, @@ -174,7 +174,22 @@ const mockFullResponse: FigmaFile = { }, ], }], + effects: [{ + type: 'DROP_SHADOW', + color: { + r: 0, + g: 0, + b: 0.062745101749897, + a: 0.4099999964237213, + }, + offset: { + x: 0, + y: 1, + }, + radius: 16, + }], styles: { + effect: 'dropShadow', fill: 'linearGradient', }, }, @@ -201,6 +216,10 @@ const mockFullResponse: FigmaFile = { name: 'Diez Black', styleType: 'FILL', }, + dropShadow: { + name: 'Diez Drop Shadow', + styleType: 'EFFECT', + }, linearGradient: { name: 'Diez Red To Purple', styleType: 'FILL', @@ -377,6 +396,12 @@ describe('Figma', () => { new Map([['BoldItalic', {name: 'Foobar-BoldItalic', path: '/path/to/Foobar-BoldItalic.ttf'}]]), ]]), projectRoot: '.', + shadows: [ + { + initializer: 'new DropShadow({offset: Point2D.make(0.000000, 1.000000), radius: 16.000000, color: Color.rgba(0, 0, 16, 0.4099999964237213)})', + name: 'Diez Drop Shadow', + }, + ], typographs: [ { name: 'Foobar Typograph', diff --git a/packages/sources/test/exporters/invision.test.ts b/packages/sources/test/exporters/invision.test.ts index 917e9ed22..b013dc078 100644 --- a/packages/sources/test/exporters/invision.test.ts +++ b/packages/sources/test/exporters/invision.test.ts @@ -76,6 +76,7 @@ describe('InvisionExporter', () => { assetsDirectory: 'out/Test.invision.contents', colors: [{initializer: 'Color.rgba(255, 0, 0, 1)', name: 'Red'}], gradients: [], + shadows: [], designSystemName: 'Test', filename: 'src/Test.invision.ts', fonts: new Map([['Foobar', new Map([['Italic', {name: 'Foobar-Italic', path: '/path/to/Foobar-Italic.ttf'}]])]]), diff --git a/packages/sources/test/exporters/sketch.test.ts b/packages/sources/test/exporters/sketch.test.ts index a2c366005..4fea52b0a 100644 --- a/packages/sources/test/exporters/sketch.test.ts +++ b/packages/sources/test/exporters/sketch.test.ts @@ -75,6 +75,26 @@ beforeEach(() => { ], imageCollection: [], }, + layerStyles: { + objects: [ + { + name: 'Card Style', + value: { + shadows: [ + { + blurRadius: 30, + color: { + value: 'rgba(255,63,112,0.70)', + }, + isEnabled: 1, + offsetX: 0, + offsetY: 2, + }, + ], + }, + }, + ], + }, layerTextStyles: { objects: [ { @@ -202,6 +222,12 @@ describe('Sketch', () => { name: 'Pink To Orange', initializer: 'new LinearGradient({stops: [GradientStop.make(0.000000, Color.rgba(255, 63, 112, 1)), GradientStop.make(1.000000, Color.rgba(255, 154, 58, 1))], start: Point2D.make(0.256905, -0.052988), end: Point2D.make(0.912005, 1.039424)})', }], + shadows: [ + { + name: 'Card Style Drop Shadow', + initializer: 'new DropShadow({offset: Point2D.make(0.000000, 2.000000), radius: 30.000000, color: Color.rgba(255, 63, 112, 0.7)})', + }, + ], designSystemName: 'Test', filename: 'src/Test.sketch.ts', fonts: new Map([['Foobar', new Map([['BoldItalic', {name: 'Foobar-BoldItalic', path: '/path/to/Foobar-BoldItalic.ttf'}]])]]), diff --git a/packages/targets/.diezrc b/packages/targets/.diezrc index e11872905..4fea2719e 100644 --- a/packages/targets/.diezrc +++ b/packages/targets/.diezrc @@ -15,6 +15,11 @@ "android": "./lib/bindings/Color/android", "web": "./lib/bindings/Color/web" }, + "@diez/prefabs:DropShadow": { + "ios": "./lib/bindings/DropShadow/ios", + "android": "./lib/bindings/DropShadow/android", + "web": "./lib/bindings/DropShadow/web" + }, "@diez/prefabs:File": { "ios": "./lib/bindings/File/ios", "android": "./lib/bindings/File/android", diff --git a/packages/targets/sources/ios/bindings/DropShadow+Binding.swift b/packages/targets/sources/ios/bindings/DropShadow+Binding.swift new file mode 100644 index 000000000..a8fd12d86 --- /dev/null +++ b/packages/targets/sources/ios/bindings/DropShadow+Binding.swift @@ -0,0 +1,15 @@ +import Foundation +import UIKit + +extension CALayer { + @objc(dez_applyDropShadow:) + public func apply(_ shadow: DropShadow) { + masksToBounds = false + shadowOpacity = 1 + shadowOffset = CGSize(width: shadow.offset.cgPoint.x, height: shadow.offset.cgPoint.y) + // `DropShadow`'s `radius` value is equal to twice the desired Guassian blur standard deviation. + // `shadowRadius` expects the standard deviation value so the value must be cut in half. + shadowRadius = shadow.radius / 2 + shadowColor = shadow.color.uiColor.cgColor + } +} diff --git a/packages/targets/sources/web/bindings/DropShadow.d.ts b/packages/targets/sources/web/bindings/DropShadow.d.ts new file mode 100644 index 000000000..beda2ea86 --- /dev/null +++ b/packages/targets/sources/web/bindings/DropShadow.d.ts @@ -0,0 +1,32 @@ +export declare class DropShadow { + /** + * The CSS box-shadow representation of the `DropShadow`. + * @example + * 0px 1px 16px rgba(0, 0, 16, .4) + */ + boxShadow: string; + /** + * The CSS text-shadow representation of the `DropShadow`. + * @example + * 0px 1px 16px rgba(0, 0, 16, .4) + */ + textShadow: string; + /** + * The CSS filter representation of the `DropShadow`. + * @example + * drop-shadow(0px 1px 16px rgba(0, 0, 16, .4)) + */ + filter: string; + /** + * CSS declarations for the `box-shadow` CSS property. + */ + boxShadowStyle: {boxShadow: string}; + /** + * CSS declarations for the `text-shadow` CSS property. + */ + textShadowStyle: {textShadow: string}; + /** + * CSS declarations for the `filter` CSS property. + */ + filterStyle: {filter: string}; +} diff --git a/packages/targets/sources/web/bindings/DropShadow.js b/packages/targets/sources/web/bindings/DropShadow.js new file mode 100644 index 000000000..c85bead41 --- /dev/null +++ b/packages/targets/sources/web/bindings/DropShadow.js @@ -0,0 +1,40 @@ +const {dropShadowToCss, dropShadowToFilterCss} = require('@diez/web-sdk-common'); + +Object.defineProperties(DropShadow.prototype, { + boxShadow: { + get () { + return dropShadowToCss(this); + }, + }, + textShadow: { + get () { + return dropShadowToCss(this); + }, + }, + filter: { + get () { + return dropShadowToFilterCss(this); + }, + }, + boxShadowStyle: { + get () { + return { + boxShadow: this.boxShadow, + }; + }, + }, + textShadowStyle: { + get () { + return { + textShadow: this.textShadow, + }; + }, + }, + filterStyle: { + get () { + return { + filter: this.filter, + }; + }, + }, +}); diff --git a/packages/targets/sources/web/bindings/LinearGradient.d.ts b/packages/targets/sources/web/bindings/LinearGradient.d.ts index 2a006ac89..3eeedba2f 100644 --- a/packages/targets/sources/web/bindings/LinearGradient.d.ts +++ b/packages/targets/sources/web/bindings/LinearGradient.d.ts @@ -1,6 +1,6 @@ export declare class LinearGradient { /** - * The CSS linear-gradient represntation of the `LinearGradient`. + * The CSS linear-gradient representation of the `LinearGradient`. * @example * linear-gradient(45deg, hsla(0, 0%, 100%, 1) 0%, hsla(0, 0%, 0%, 1) 100%) */ diff --git a/packages/targets/src/bindings/Color/web.ts b/packages/targets/src/bindings/Color/web.ts index 4c1ef4f30..e25140f54 100644 --- a/packages/targets/src/bindings/Color/web.ts +++ b/packages/targets/src/bindings/Color/web.ts @@ -8,7 +8,7 @@ const binding: WebBinding = { declarations: [join(sourcesPath, 'web', 'bindings', 'Color.d.ts')], assetsBinder: async (instance, program, output, spec, property) => { // TODO: this shouldn't be necessary with a good and general design for "resource boundaries". - if (property.parentType === 'Typograph') { + if (property.parentType === 'Typograph' || property.parentType === 'DropShadow') { return; } diff --git a/packages/targets/src/bindings/DropShadow/android.ts b/packages/targets/src/bindings/DropShadow/android.ts new file mode 100644 index 000000000..3cc9ff697 --- /dev/null +++ b/packages/targets/src/bindings/DropShadow/android.ts @@ -0,0 +1,10 @@ +import {DropShadow} from '@diez/prefabs'; +import {AndroidBinding} from '../../targets/android.api'; + +const binding: AndroidBinding = { + // TODO: Remove the need to provide an empty binding on prefabs without any binding overrides. + // Provide an empty array to prevent this prefab from being treated as a singleton. + sources: [], +}; + +export = binding; diff --git a/packages/targets/src/bindings/DropShadow/ios.ts b/packages/targets/src/bindings/DropShadow/ios.ts new file mode 100644 index 000000000..40bd2126c --- /dev/null +++ b/packages/targets/src/bindings/DropShadow/ios.ts @@ -0,0 +1,12 @@ +import {DropShadow} from '@diez/prefabs'; +import {join} from 'path'; +import {IosBinding} from '../../targets/ios.api'; +import {sourcesPath} from '../../utils'; + +const binding: IosBinding = { + sources: [ + join(sourcesPath, 'ios', 'bindings', 'DropShadow+Binding.swift'), + ], +}; + +export = binding; diff --git a/packages/targets/src/bindings/DropShadow/web.ts b/packages/targets/src/bindings/DropShadow/web.ts new file mode 100644 index 000000000..4210f6af7 --- /dev/null +++ b/packages/targets/src/bindings/DropShadow/web.ts @@ -0,0 +1,48 @@ +import {diezVersion} from '@diez/cli-core'; +import {DropShadow} from '@diez/prefabs'; +import {dropShadowToCss, dropShadowToFilterCss} from '@diez/web-sdk-common'; +import {join} from 'path'; +import {WebBinding} from '../../targets/web.api'; +import {joinToKebabCase, sourcesPath} from '../../utils'; + +const binding: WebBinding = { + sources: [join(sourcesPath, 'web', 'bindings', 'DropShadow.js')], + declarations: [join(sourcesPath, 'web', 'bindings', 'DropShadow.d.ts')], + assetsBinder: async (instance, program, output, spec, property) => { + const name = joinToKebabCase(property.parentType, property.name); + const value = dropShadowToCss(instance); + const filterValue = dropShadowToFilterCss(instance); + + output.styleSheet.styles.insertRule({ + selector: `${name}-box-shadow`, + declaration: { + 'box-shadow': value, + }, + }); + + output.styleSheet.styles.insertRule({ + selector: `${name}-text-shadow`, + declaration: { + 'text-shadow': value, + }, + }); + + output.styleSheet.styles.insertRule({ + selector: `${name}-filter`, + declaration: { + filter: filterValue, + }, + }); + + output.styleSheet.variables.set(name, value); + output.styleSheet.variables.set(`${name}-filter`, filterValue); + }, + dependencies: [{ + packageJson: { + name: '@diez/web-sdk-common', + versionConstraint: `^${diezVersion}`, + }, + }], +}; + +export = binding; diff --git a/packages/targets/src/bindings/Point2D/web.ts b/packages/targets/src/bindings/Point2D/web.ts index 3526037e5..0340b4741 100644 --- a/packages/targets/src/bindings/Point2D/web.ts +++ b/packages/targets/src/bindings/Point2D/web.ts @@ -8,7 +8,7 @@ const binding: WebBinding = { sources: [], assetsBinder: async (instance, program, output, spec, property) => { // TODO: this shouldn't be necessary with a good and general design for "resource boundaries". - if (property.parentType !== 'LinearGradient') { + if (property.parentType !== 'LinearGradient' && property.parentType !== 'DropShadow') { const name = joinToKebabCase(property.parentType, property.name); output.styleSheet.variables.set(`${name}-x-px`, `${instance.x}px`); output.styleSheet.variables.set(`${name}-x-rem`, `${instance.x}rem`); diff --git a/packages/targets/test/fixtures/Bindings/Bindings.ts b/packages/targets/test/fixtures/Bindings/Bindings.ts index 37a9d199e..0b3ba018f 100644 --- a/packages/targets/test/fixtures/Bindings/Bindings.ts +++ b/packages/targets/test/fixtures/Bindings/Bindings.ts @@ -1,5 +1,6 @@ import { Color, + DropShadow, File, FileType, Font, @@ -9,7 +10,7 @@ import { Point2D, Size2D, Toward, - Typograph + Typograph, } from '@diez/prefabs'; export class Bindings { @@ -33,4 +34,10 @@ export class Bindings { point = Point2D.make(0.5, 0.5); size = Size2D.make(400, 300); + + shadow = new DropShadow({ + offset: Point2D.make(1, 2), + radius: 3, + color: Color.rgba(0, 255, 0, 0.5), + }); } diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Bindings.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Bindings.kt similarity index 85% rename from packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Bindings.kt rename to packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Bindings.kt index 63babac2c..c77418cea 100644 --- a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Bindings.kt +++ b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Bindings.kt @@ -6,7 +6,8 @@ data class Bindings( val typograph: Typograph = Typograph(Font(File("assets/SomeFont.ttf", "font"), "SomeFont"), 50F, Color(0.16666666666666666F, 1F, 0.5F, 1F)), val linearGradient: LinearGradient = LinearGradient(arrayOf(GradientStop(0F, Color(0F, 1F, 0.5F, 1F)), GradientStop(1F, Color(0.6666666666666666F, 1F, 0.5F, 1F))), Point2D(0F, 0.5F), Point2D(1F, 0.5F)), val point: Point2D = Point2D(0.5F, 0.5F), - val size: Size2D = Size2D(400F, 300F) + val size: Size2D = Size2D(400F, 300F), + val shadow: DropShadow = DropShadow(Point2D(1F, 2F), 3F, Color(0.3333333333333333F, 1F, 0.5F, 0.5F)) ) : StateBag { companion object {} override val name = "Bindings" diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Color.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Color.kt similarity index 100% rename from packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Color.kt rename to packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Color.kt diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Diez.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Diez.kt similarity index 100% rename from packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Diez.kt rename to packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Diez.kt diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/DropShadow.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/DropShadow.kt new file mode 100644 index 000000000..86b079879 --- /dev/null +++ b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/DropShadow.kt @@ -0,0 +1,9 @@ +package org.diez.stub + +data class DropShadow( + val offset: Point2D, + val radius: Float, + val color: Color +) { + companion object {} +} diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Environment.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Environment.kt similarity index 100% rename from packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Environment.kt rename to packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Environment.kt diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/File.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/File.kt similarity index 100% rename from packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/File.kt rename to packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/File.kt diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Font.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Font.kt similarity index 100% rename from packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Font.kt rename to packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Font.kt diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/GradientStop.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/GradientStop.kt similarity index 100% rename from packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/GradientStop.kt rename to packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/GradientStop.kt diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Image.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Image.kt similarity index 100% rename from packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Image.kt rename to packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Image.kt diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/LinearGradient.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/LinearGradient.kt similarity index 100% rename from packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/LinearGradient.kt rename to packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/LinearGradient.kt diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Lottie.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Lottie.kt similarity index 100% rename from packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Lottie.kt rename to packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Lottie.kt diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Point2D.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Point2D.kt similarity index 100% rename from packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Point2D.kt rename to packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Point2D.kt diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Size2D.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Size2D.kt similarity index 100% rename from packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Size2D.kt rename to packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Size2D.kt diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Typograph.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Typograph.kt similarity index 100% rename from packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Typograph.kt rename to packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/stub/Typograph.kt diff --git a/packages/targets/test/goldens/Bindings/diez-stub-ios/DiezStub.xcodeproj/project.pbxproj b/packages/targets/test/goldens/Bindings/diez-stub-ios/DiezStub.xcodeproj/project.pbxproj index 046eb4781..1e1d50ec5 100644 --- a/packages/targets/test/goldens/Bindings/diez-stub-ios/DiezStub.xcodeproj/project.pbxproj +++ b/packages/targets/test/goldens/Bindings/diez-stub-ios/DiezStub.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 30D8DCC71661D0ECFB778AA59754131A /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8778BD519B243F8B2E2F9E6B58B6660F /* Environment.swift */; }; 31D405B5F5E3947ED5EC505C44F4868D /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6639302602D066191557EE67F2F3A8BB /* File.swift */; }; 35C4CB5F2F0A994E4D65F1145C6CBCCC /* Bundle+Static.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2033D197C87132619563FDCBF3076E /* Bundle+Static.swift */; }; + 3769622C01C56887C932DC6D84D79C94 /* DropShadow+Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 888C24B260614F9427FA14F4E1CB36CE /* DropShadow+Binding.swift */; }; 43F025A40CF48D48F83C7F712D32F9BE /* Lottie+Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7BEA91049CF649A1AE50E700DB149E5 /* Lottie+Binding.swift */; }; 48154B23C778B69B9B7892F5EE62A1D5 /* Size2D.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFBF4FBE0FC8626F48B4AD710A00CDE /* Size2D.swift */; }; 48EEAB321103D62DCD14567E8777BBD2 /* Typograph+Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1134BF4AC055DADAD76630D14C02E8A2 /* Typograph+Binding.swift */; }; @@ -34,6 +35,7 @@ BA3861C33773396382B30BD9AB6F0D3B /* Lottie.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9A0B8DEBE9CB8D94DACCED192B2D4E6 /* Lottie.framework */; }; BA503BB9F3C1F14E9E0521FB0144E94D /* Image+Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DC81DA6485DC5CEADF4FB56A15C36F3 /* Image+Binding.swift */; }; C2EE21012304852D84A336CE164EB6DF /* GradientStop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70CBE006ECEAF9C243C561883C13A8CE /* GradientStop.swift */; }; + C435F8C907683050CB21BCFBDC2D054A /* DropShadow.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0D2FA722CA1E02A23081718A4FD4D8 /* DropShadow.swift */; }; CB77CEDF8462794317E629439E5483D6 /* Color+Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A67FEB60DD6033589048DEE59E4C2A8 /* Color+Binding.swift */; }; D60BA4F9FF449FB834140D4F4E27E1F8 /* Size2D+Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A5BEEA9D863F4BE188359CB877C2E3 /* Size2D+Binding.swift */; }; EE60578FFB630A1C71BFD87CBD693839 /* File+Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 532372957C7BF2D47B40B9DD6C61D279 /* File+Binding.swift */; }; @@ -68,6 +70,7 @@ 813812AEFCC1BB5B22C557244A1BE5F5 /* LinearGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinearGradient.swift; sourceTree = ""; }; 853B0CBAF15611244D045E7999114C8A /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; }; 8778BD519B243F8B2E2F9E6B58B6660F /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; }; + 888C24B260614F9427FA14F4E1CB36CE /* DropShadow+Binding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DropShadow+Binding.swift"; sourceTree = ""; }; 8DC81DA6485DC5CEADF4FB56A15C36F3 /* Image+Binding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Image+Binding.swift"; sourceTree = ""; }; 9BE97E7A473C2D663F9221BBCFB0D6E5 /* Bundle+Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Environment.swift"; sourceTree = ""; }; 9FFBF4FBE0FC8626F48B4AD710A00CDE /* Size2D.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Size2D.swift; sourceTree = ""; }; @@ -81,6 +84,7 @@ D80839BA9666C276FD55637472736C1A /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = Sources/Static/assets; sourceTree = SOURCE_ROOT; }; D81C57324B94295575E3676C0F32C8D0 /* Bundle+File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+File.swift"; sourceTree = ""; }; DBA378500CBA8DF7D7D2779A86A54AAD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + ED0D2FA722CA1E02A23081718A4FD4D8 /* DropShadow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropShadow.swift; sourceTree = ""; }; EDE20D5466E834322461A7693EB8C949 /* Point2D.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Point2D.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -137,6 +141,7 @@ children = ( D81C57324B94295575E3676C0F32C8D0 /* Bundle+File.swift */, 2A67FEB60DD6033589048DEE59E4C2A8 /* Color+Binding.swift */, + 888C24B260614F9427FA14F4E1CB36CE /* DropShadow+Binding.swift */, 532372957C7BF2D47B40B9DD6C61D279 /* File+Binding.swift */, 8DC81DA6485DC5CEADF4FB56A15C36F3 /* Image+Binding.swift */, A6C61241B372972554AA1D66DF18F052 /* LinearGradient+Binding.swift */, @@ -175,6 +180,7 @@ children = ( 123F5E73C868209F9A884D6D03B826D6 /* Bindings.swift */, C3079B1384BD06EE8EBB48505985BAC9 /* Color.swift */, + ED0D2FA722CA1E02A23081718A4FD4D8 /* DropShadow.swift */, 6639302602D066191557EE67F2F3A8BB /* File.swift */, CF6D6468DECE18204492CA00282CCC8F /* Font.swift */, 70CBE006ECEAF9C243C561883C13A8CE /* GradientStop.swift */, @@ -310,6 +316,8 @@ CB77CEDF8462794317E629439E5483D6 /* Color+Binding.swift in Sources */, 64623CF1B84B7964CC33E272B5165330 /* Color.swift in Sources */, 1B0A959296A666217D7A0B4C315371A9 /* Diez.swift in Sources */, + 3769622C01C56887C932DC6D84D79C94 /* DropShadow+Binding.swift in Sources */, + C435F8C907683050CB21BCFBDC2D054A /* DropShadow.swift in Sources */, 30D8DCC71661D0ECFB778AA59754131A /* Environment.swift in Sources */, EE60578FFB630A1C71BFD87CBD693839 /* File+Binding.swift in Sources */, 31D405B5F5E3947ED5EC505C44F4868D /* File.swift in Sources */, diff --git a/packages/targets/test/goldens/Bindings/diez-stub-ios/Sources/DiezStub/Bindings/DropShadow+Binding.swift b/packages/targets/test/goldens/Bindings/diez-stub-ios/Sources/DiezStub/Bindings/DropShadow+Binding.swift new file mode 100644 index 000000000..a8fd12d86 --- /dev/null +++ b/packages/targets/test/goldens/Bindings/diez-stub-ios/Sources/DiezStub/Bindings/DropShadow+Binding.swift @@ -0,0 +1,15 @@ +import Foundation +import UIKit + +extension CALayer { + @objc(dez_applyDropShadow:) + public func apply(_ shadow: DropShadow) { + masksToBounds = false + shadowOpacity = 1 + shadowOffset = CGSize(width: shadow.offset.cgPoint.x, height: shadow.offset.cgPoint.y) + // `DropShadow`'s `radius` value is equal to twice the desired Guassian blur standard deviation. + // `shadowRadius` expects the standard deviation value so the value must be cut in half. + shadowRadius = shadow.radius / 2 + shadowColor = shadow.color.uiColor.cgColor + } +} diff --git a/packages/targets/test/goldens/Bindings/diez-stub-ios/Sources/DiezStub/Components/Bindings.swift b/packages/targets/test/goldens/Bindings/diez-stub-ios/Sources/DiezStub/Components/Bindings.swift index 6b3dba984..9daa9c709 100644 --- a/packages/targets/test/goldens/Bindings/diez-stub-ios/Sources/DiezStub/Components/Bindings.swift +++ b/packages/targets/test/goldens/Bindings/diez-stub-ios/Sources/DiezStub/Components/Bindings.swift @@ -9,6 +9,7 @@ public final class Bindings: NSObject, StateBag { @objc public internal(set) var linearGradient: LinearGradient @objc public internal(set) var point: Point2D @objc public internal(set) var size: Size2D + @objc public internal(set) var shadow: DropShadow convenience public override init() { self.init( @@ -17,7 +18,8 @@ public final class Bindings: NSObject, StateBag { typograph: Typograph(font: Font(file: File(src: "assets/SomeFont.ttf", type: "font"), name: "SomeFont"), fontSize: 50, color: Color(h: 0.16666666666666666, s: 1, l: 0.5, a: 1)), linearGradient: LinearGradient(stops: [GradientStop(position: 0, color: Color(h: 0, s: 1, l: 0.5, a: 1)), GradientStop(position: 1, color: Color(h: 0.6666666666666666, s: 1, l: 0.5, a: 1))], start: Point2D(x: 0, y: 0.5), end: Point2D(x: 1, y: 0.5)), point: Point2D(x: 0.5, y: 0.5), - size: Size2D(width: 400, height: 300) + size: Size2D(width: 400, height: 300), + shadow: DropShadow(offset: Point2D(x: 1, y: 2), radius: 3, color: Color(h: 0.3333333333333333, s: 1, l: 0.5, a: 0.5)) ) } @@ -27,7 +29,8 @@ public final class Bindings: NSObject, StateBag { typograph: Typograph, linearGradient: LinearGradient, point: Point2D, - size: Size2D + size: Size2D, + shadow: DropShadow ) { self.image = image self.lottie = lottie @@ -35,6 +38,7 @@ public final class Bindings: NSObject, StateBag { self.linearGradient = linearGradient self.point = point self.size = size + self.shadow = shadow } public static let name = "Bindings" diff --git a/packages/targets/test/goldens/Bindings/diez-stub-ios/Sources/DiezStub/Components/DropShadow.swift b/packages/targets/test/goldens/Bindings/diez-stub-ios/Sources/DiezStub/Components/DropShadow.swift new file mode 100644 index 000000000..dc30f97d4 --- /dev/null +++ b/packages/targets/test/goldens/Bindings/diez-stub-ios/Sources/DiezStub/Components/DropShadow.swift @@ -0,0 +1,25 @@ +import Foundation +import CoreGraphics + +@objc(DEZDropShadow) +public final class DropShadow: NSObject, Decodable { + @objc public internal(set) var offset: Point2D + @objc public internal(set) var radius: CGFloat + @objc public internal(set) var color: Color + + init( + offset: Point2D, + radius: CGFloat, + color: Color + ) { + self.offset = offset + self.radius = radius + self.color = color + } +} + +extension DropShadow: ReflectedCustomStringConvertible { + public override var description: String { + return reflectedDescription + } +} diff --git a/packages/targets/test/goldens/Bindings/diez-stub-web/index.d.ts b/packages/targets/test/goldens/Bindings/diez-stub-web/index.d.ts index b28d6a5fa..81ae11f73 100644 --- a/packages/targets/test/goldens/Bindings/diez-stub-web/index.d.ts +++ b/packages/targets/test/goldens/Bindings/diez-stub-web/index.d.ts @@ -148,7 +148,7 @@ export declare class Point2D { export declare class LinearGradient { /** - * The CSS linear-gradient represntation of the `LinearGradient`. + * The CSS linear-gradient representation of the `LinearGradient`. * @example * linear-gradient(45deg, hsla(0, 0%, 100%, 1) 0%, hsla(0, 0%, 0%, 1) 100%) */ @@ -163,6 +163,39 @@ export declare class LinearGradient { backgroundStyle: {background: string}; } +export declare class DropShadow { + /** + * The CSS box-shadow representation of the `DropShadow`. + * @example + * 0px 1px 16px rgba(0, 0, 16, .4) + */ + boxShadow: string; + /** + * The CSS text-shadow representation of the `DropShadow`. + * @example + * 0px 1px 16px rgba(0, 0, 16, .4) + */ + textShadow: string; + /** + * The CSS filter representation of the `DropShadow`. + * @example + * drop-shadow(0px 1px 16px rgba(0, 0, 16, .4)) + */ + filter: string; + /** + * CSS declarations for the `box-shadow` CSS property. + */ + boxShadowStyle: {boxShadow: string}; + /** + * CSS declarations for the `text-shadow` CSS property. + */ + textShadowStyle: {textShadow: string}; + /** + * CSS declarations for the `filter` CSS property. + */ + filterStyle: {filter: string}; +} + export declare class Bindings extends StateBag { image: Image; lottie: Lottie; @@ -170,5 +203,6 @@ export declare class Bindings extends StateBag { linearGradient: LinearGradient; point: Point2D; size: Size2D; + shadow: DropShadow; } diff --git a/packages/targets/test/goldens/Bindings/diez-stub-web/index.js b/packages/targets/test/goldens/Bindings/diez-stub-web/index.js index 2794db4a0..0bbc97624 100644 --- a/packages/targets/test/goldens/Bindings/diez-stub-web/index.js +++ b/packages/targets/test/goldens/Bindings/diez-stub-web/index.js @@ -407,6 +407,62 @@ Object.defineProperties(LinearGradient.prototype, { }, }); +class DropShadow { + constructor({ + offset, + radius, + color + }) { + this.offset = new Point2D(offset); + this.radius = radius; + this.color = new Color(color); + } +} + + +module.exports.DropShadow = DropShadow; + +const {dropShadowToCss, dropShadowToFilterCss} = require('@diez/web-sdk-common'); + +Object.defineProperties(DropShadow.prototype, { + boxShadow: { + get () { + return dropShadowToCss(this); + }, + }, + textShadow: { + get () { + return dropShadowToCss(this); + }, + }, + filter: { + get () { + return dropShadowToFilterCss(this); + }, + }, + boxShadowStyle: { + get () { + return { + boxShadow: this.boxShadow, + }; + }, + }, + textShadowStyle: { + get () { + return { + textShadow: this.textShadow, + }; + }, + }, + filterStyle: { + get () { + return { + filter: this.filter, + }; + }, + }, +}); + class Bindings { constructor({ image = {file: {src: "assets/image%20with%20spaces.jpg", type: "image"}, file2x: {src: "assets/image%20with%20spaces@2x.jpg", type: "image"}, file3x: {src: "assets/image%20with%20spaces@3x.jpg", type: "image"}, size: {width: 246, height: 246}}, @@ -414,7 +470,8 @@ class Bindings { typograph = {font: {file: {src: "assets/SomeFont.ttf", type: "font"}, name: "SomeFont", fallbacks: ["Verdana", "serif"], weight: 700, style: "normal"}, fontSize: 50, color: {h: 0.16666666666666666, s: 1, l: 0.5, a: 1}}, linearGradient = {stops: [{position: 0, color: {h: 0, s: 1, l: 0.5, a: 1}}, {position: 1, color: {h: 0.6666666666666666, s: 1, l: 0.5, a: 1}}], start: {x: 0, y: 0.5}, end: {x: 1, y: 0.5}}, point = {x: 0.5, y: 0.5}, - size = {width: 400, height: 300} + size = {width: 400, height: 300}, + shadow = {offset: {x: 1, y: 2}, radius: 3, color: {h: 0.3333333333333333, s: 1, l: 0.5, a: 0.5}} } = {}) { this.image = new Image(image); this.lottie = new Lottie(lottie); @@ -422,6 +479,7 @@ class Bindings { this.linearGradient = new LinearGradient(linearGradient); this.point = new Point2D(point); this.size = new Size2D(size); + this.shadow = new DropShadow(shadow); } } diff --git a/packages/targets/test/goldens/Bindings/diez-stub-web/static/styles.css b/packages/targets/test/goldens/Bindings/diez-stub-web/static/styles.css index 186ca4409..14249f210 100644 --- a/packages/targets/test/goldens/Bindings/diez-stub-web/static/styles.css +++ b/packages/targets/test/goldens/Bindings/diez-stub-web/static/styles.css @@ -22,6 +22,8 @@ --bindings-size-width-rem: 400rem; --bindings-size-height-px: 300px; --bindings-size-height-rem: 300rem; + --bindings-shadow: 1px 2px 3px hsla(120, 100%, 50%, 0.5); + --bindings-shadow-filter: drop-shadow(1px 2px 1.5px hsla(120, 100%, 50%, 0.5)); } .bindings-image-background-image { @@ -67,3 +69,15 @@ background-image: linear-gradient(90deg, hsla(0, 100%, 50%, 1) 0%, hsla(240, 100%, 50%, 1) 100%); } +.bindings-shadow-box-shadow { + box-shadow: 1px 2px 3px hsla(120, 100%, 50%, 0.5); +} + +.bindings-shadow-text-shadow { + text-shadow: 1px 2px 3px hsla(120, 100%, 50%, 0.5); +} + +.bindings-shadow-filter { + filter: drop-shadow(1px 2px 1.5px hsla(120, 100%, 50%, 0.5)); +} + diff --git a/packages/targets/test/goldens/Bindings/diez-stub-web/static/styles.scss b/packages/targets/test/goldens/Bindings/diez-stub-web/static/styles.scss index 4f94244ee..9a8062653 100644 --- a/packages/targets/test/goldens/Bindings/diez-stub-web/static/styles.scss +++ b/packages/targets/test/goldens/Bindings/diez-stub-web/static/styles.scss @@ -21,6 +21,8 @@ $bindings-size-width-px: 400px; $bindings-size-width-rem: 400rem; $bindings-size-height-px: 300px; $bindings-size-height-rem: 300rem; +$bindings-shadow: 1px 2px 3px hsla(120, 100%, 50%, 0.5); +$bindings-shadow-filter: drop-shadow(1px 2px 1.5px hsla(120, 100%, 50%, 0.5)); @mixin bindings-image-background-image { background-image: url("/diez/assets/image with spaces.jpg"); @@ -61,3 +63,15 @@ $bindings-size-height-rem: 300rem; background-image: linear-gradient(90deg, hsla(0, 100%, 50%, 1) 0%, hsla(240, 100%, 50%, 1) 100%); } +@mixin bindings-shadow-box-shadow { + box-shadow: 1px 2px 3px hsla(120, 100%, 50%, 0.5); +} + +@mixin bindings-shadow-text-shadow { + text-shadow: 1px 2px 3px hsla(120, 100%, 50%, 0.5); +} + +@mixin bindings-shadow-filter { + filter: drop-shadow(1px 2px 1.5px hsla(120, 100%, 50%, 0.5)); +} + diff --git a/packages/web-sdk-common/src/css-drop-shadow.ts b/packages/web-sdk-common/src/css-drop-shadow.ts new file mode 100644 index 000000000..45c5d5dcb --- /dev/null +++ b/packages/web-sdk-common/src/css-drop-shadow.ts @@ -0,0 +1,27 @@ +import {DropShadowData} from '@diez/prefabs'; + +/** + * Returns a string containing a valid CSS value for and/or from a [[Shadow]] prefab + * instance. + */ +export const dropShadowToCss = (shadow: DropShadowData) => { + // TODO: Use `colorToCss` once it's moved into this package. + const {h, s, l, a} = shadow.color; + const color = `hsla(${h * 360}, ${s * 100}%, ${l * 100}%, ${a})`; + return `${shadow.offset.x}px ${shadow.offset.y}px ${shadow.radius}px ${color}`; +}; + +/** + * Returns a string containing a valid CSS value from a [[Shadow]] prefab instance. + */ +export const dropShadowToFilterCss = (shadow: DropShadowData) => { + // TODO: Use `colorToCss` once it's moved into this package. + const {h, s, l, a} = shadow.color; + const color = `hsla(${h * 360}, ${s * 100}%, ${l * 100}%, ${a})`; + + // Since the `DropShadowData`'s radius value represents double the standard deviation of the desired Guassian blur, + // and `drop-shadow` expects the standard deviation, the value must be cut in half. + // See https://css-tricks.com/breaking-css-box-shadow-vs-drop-shadow/#comment-1612592 + const radius = shadow.radius / 2; + return `drop-shadow(${shadow.offset.x}px ${shadow.offset.y}px ${radius}px ${color})`; +}; diff --git a/packages/web-sdk-common/src/index.ts b/packages/web-sdk-common/src/index.ts index b3846a0c8..e14d83bc3 100644 --- a/packages/web-sdk-common/src/index.ts +++ b/packages/web-sdk-common/src/index.ts @@ -1 +1,2 @@ export * from './css-linear-gradient'; +export * from './css-drop-shadow'; diff --git a/packages/web-sdk-common/test/css-drop-shadow.test.ts b/packages/web-sdk-common/test/css-drop-shadow.test.ts new file mode 100644 index 000000000..bec21e038 --- /dev/null +++ b/packages/web-sdk-common/test/css-drop-shadow.test.ts @@ -0,0 +1,27 @@ + +import {Color, DropShadow, Point2D} from '@diez/prefabs'; +import {dropShadowToCss, dropShadowToFilterCss} from '../src/css-drop-shadow'; + +describe('dropShadowToCss', () => { + test('simple shadow', () => { + const shadow = new DropShadow({ + offset: Point2D.make(1, 2), + radius: 3, + color: Color.hsla(0.5, 0.25, 0.75, 0.9), + }); + expect(dropShadowToCss(shadow)) + .toBe('1px 2px 3px hsla(180, 25%, 75%, 0.9)'); + }); +}); + +describe('dropShadowToFilterCss', () => { + test('simple shadow', () => { + const shadow = new DropShadow({ + offset: Point2D.make(1, 2), + radius: 6, + color: Color.hsla(0.5, 0.25, 0.75, 0.9), + }); + expect(dropShadowToFilterCss(shadow)) + .toBe('drop-shadow(1px 2px 3px hsla(180, 25%, 75%, 0.9))'); + }); +});