diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index fcc8d70e18..56bcd4c0c1 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -24,3 +24,4 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} # 👇 Chromatic projectToken, refer to the manage page to obtain it. projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + exitZeroOnChanges: false diff --git a/packages/frame/README.md b/packages/frame/README.md index 9a589f89ad..a89572e9f7 100644 --- a/packages/frame/README.md +++ b/packages/frame/README.md @@ -42,7 +42,8 @@ For styling purposes, you can select `data-bedrock-frame`. ## API -| Property | Description | Type | Default | -| :------: | :---------------------------------------------------------------------------------------------------------------------------: | :----------------: | :---------------: | -| ratio | Aspect ratio that you want the child element to maintain | `[number, number]` | medium breakpoint | -| position | Alignment of the child element. Use [object-position value](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) | string | `50%` | +∏ +| Property | Description | Type | Default | +| :------: | :---------------------------------------------------------------------------------------------------------------------------: | :---------------: | :-------------------: | ----------------- | +| ratio | Aspect ratio that you want the child element to maintain | `[number, number] | ${number}/${number}` | medium breakpoint | +| position | Alignment of the child element. Use [object-position value](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) | string | `50%` | diff --git a/packages/frame/__tests__/__snapshots__/frame.test.js.snap b/packages/frame/__tests__/__snapshots__/frame.test.js.snap index 7fd7d6bccc..5b3d45af2a 100644 --- a/packages/frame/__tests__/__snapshots__/frame.test.js.snap +++ b/packages/frame/__tests__/__snapshots__/frame.test.js.snap @@ -2,14 +2,15 @@ exports[`Frame correct usage renders custom position 1`] = ` .c0 { - --n: 16; - --d: 9; box-sizing: border-box; display: block; inline-size: 100%; position: relative; overflow: hidden; - aspect-ratio: var(--n) / var(--d); +} + +.c0[style*="--ratio"] { + aspect-ratio: var(--ratio); } .c0 > * { @@ -51,6 +52,76 @@ exports[`Frame correct usage renders custom position 1`] = `
+ random thing +
+`; + +exports[`Frame correct usage renders with ratio as a string 1`] = ` +.c0 { + box-sizing: border-box; + display: block; + inline-size: 100%; + position: relative; + overflow: hidden; +} + +.c0[style*="--ratio"] { + aspect-ratio: var(--ratio); +} + +.c0 > * { + position: absolute; + -webkit-inset-block-start: 0; + -ms-intb-rlock-start: 0; + inset-block-start: 0; + -webkit-inset-block-end: 0; + -ms-inlrock-end: 0; + inset-block-end: 0; + inset-inline-start: 0; + inset-inline-end: 0; + inset-block: 0; + inset-inline: 0; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c0 > :is(img,video) { + inline-size: 100%; + -webkit-block-size: 100%; + -ms-flex-block-size: 100%; + block-size: 100%; + size: 100%; + object-fit: cover; + object-position: 50%; +} + +
random thing `; -exports[`Frame correct usage renders with ratio 1`] = ` +exports[`Frame correct usage renders with ratio as an array 1`] = ` .c0 { - --n: 16; - --d: 9; box-sizing: border-box; display: block; inline-size: 100%; position: relative; overflow: hidden; - aspect-ratio: var(--n) / var(--d); +} + +.c0[style*="--ratio"] { + aspect-ratio: var(--ratio); } .c0 > * { @@ -110,6 +182,11 @@ exports[`Frame correct usage renders with ratio 1`] = `
random thing * { + position: absolute; + -webkit-inset-block-start: 0; + -ms-intb-rlock-start: 0; + inset-block-start: 0; + -webkit-inset-block-end: 0; + -ms-inlrock-end: 0; + inset-block-end: 0; + inset-inline-start: 0; + inset-inline-end: 0; + inset-block: 0; + inset-inline: 0; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c0 > :is(img,video) { + inline-size: 100%; + -webkit-block-size: 100%; + -ms-flex-block-size: 100%; + block-size: 100%; + size: 100%; + object-fit: cover; + object-position: 50%; +} + +
+ random thing +
+`; + +exports[`Frame incorrect usage renders default position with incorrect value 1`] = ` +.c0 { + box-sizing: border-box; + display: block; + inline-size: 100%; + position: relative; + overflow: hidden; +} + +.c0[style*="--ratio"] { + aspect-ratio: var(--ratio); +} + .c0 > * { position: absolute; -webkit-inset-block-start: 0; @@ -168,6 +312,11 @@ exports[`Frame correct usage renders without ratio 1`] = `
random thing `; -exports[`Frame incorrect usage falls back to 1 with error if array of length <1 provided 1`] = ` +exports[`Frame incorrect usage renders without ratio if ratio is not correct type 1`] = ` .c0 { - --n: 16; - --d: 1; box-sizing: border-box; display: block; inline-size: 100%; position: relative; overflow: hidden; - aspect-ratio: var(--n) / var(--d); +} + +.c0[style*="--ratio"] { + aspect-ratio: var(--ratio); } .c0 > * { @@ -227,6 +377,11 @@ exports[`Frame incorrect usage falls back to 1 with error if array of length <1
random thing `; -exports[`Frame incorrect usage falls back to 1 with error if array of length >2 provided 1`] = ` +exports[`Frame incorrect usage renders without ratio with error if array of length <1 provided 1`] = ` .c0 { - --n: 16; - --d: 9; box-sizing: border-box; display: block; inline-size: 100%; position: relative; overflow: hidden; - aspect-ratio: var(--n) / var(--d); +} + +.c0[style*="--ratio"] { + aspect-ratio: var(--ratio); } .c0 > * { @@ -286,6 +442,11 @@ exports[`Frame incorrect usage falls back to 1 with error if array of length >2
random thing2
`; -exports[`Frame incorrect usage falls back to 1 with error if array of not numbers provided 1`] = ` +exports[`Frame incorrect usage renders without ratio with error if array of length >2 provided 1`] = ` .c0 { - --n: 1; - --d: 1; box-sizing: border-box; display: block; inline-size: 100%; position: relative; overflow: hidden; - aspect-ratio: var(--n) / var(--d); +} + +.c0[style*="--ratio"] { + aspect-ratio: var(--ratio); } .c0 > * { @@ -345,6 +507,11 @@ exports[`Frame incorrect usage falls back to 1 with error if array of not number
random thing `; -exports[`Frame incorrect usage renders default position with incorrect value 1`] = ` +exports[`Frame incorrect usage renders without ratio with error if array of not numbers provided 1`] = ` .c0 { - --n: 16; - --d: 9; box-sizing: border-box; display: block; inline-size: 100%; position: relative; overflow: hidden; - aspect-ratio: var(--n) / var(--d); +} + +.c0[style*="--ratio"] { + aspect-ratio: var(--ratio); } .c0 > * { @@ -404,6 +572,11 @@ exports[`Frame incorrect usage renders default position with incorrect value 1`]
random thing `; -exports[`Frame incorrect usage renders ratio of 1:1 with error if ratio is not an array 1`] = ` +exports[`Frame incorrect usage renders without ratio with error if ratio string is not correct format 1`] = ` .c0 { - --n: 16; - --d: 9; box-sizing: border-box; display: block; inline-size: 100%; position: relative; overflow: hidden; - aspect-ratio: var(--n) / var(--d); +} + +.c0[style*="--ratio"] { + aspect-ratio: var(--ratio); } .c0 > * { @@ -463,6 +637,11 @@ exports[`Frame incorrect usage renders ratio of 1:1 with error if ratio is not a
random thing { expect(Frame).toBeTruthy(); }); - it("renders with ratio", () => { + it("renders with ratio as an array", () => { const frame = create( random thing @@ -18,6 +18,15 @@ describe("Frame", () => { expect(frame.toJSON()).toMatchSnapshot(); }); + it("renders with ratio as a string", () => { + const frame = create( + + random thing + + ); + expect(frame.toJSON()).toMatchSnapshot(); + }); + it("renders without ratio", () => { const frame = create( @@ -59,7 +68,7 @@ describe("Frame", () => { expect(errorStack.toJSON()).toMatchSnapshot(); }); - it("renders ratio of 1:1 with error if ratio is not an array", () => { + it("renders without ratio if ratio is not correct type", () => { expect(console.error).not.toBeCalled(); const errorStack = create( @@ -72,7 +81,7 @@ describe("Frame", () => { expect(errorStack.toJSON()).toMatchSnapshot(); }); - it("falls back to 1 with error if array of length <1 provided", () => { + it("renders without ratio with error if array of length <1 provided", () => { expect(console.error).not.toBeCalled(); const errorStack = create( @@ -85,7 +94,7 @@ describe("Frame", () => { expect(errorStack.toJSON()).toMatchSnapshot(); }); - it("falls back to 1 with error if array of length >2 provided", () => { + it("renders without ratio with error if array of length >2 provided", () => { expect(console.error).not.toBeCalled(); const errorStack = create( @@ -98,7 +107,7 @@ describe("Frame", () => { expect(errorStack.toJSON()).toMatchSnapshot(); }); - it("falls back to 1 with error if array of not numbers provided", () => { + it("renders without ratio with error if array of not numbers provided", () => { expect(console.error).not.toBeCalled(); const errorStack = create( @@ -110,5 +119,18 @@ describe("Frame", () => { expect(console.error).toBeCalled(); expect(errorStack.toJSON()).toMatchSnapshot(); }); + + it("renders without ratio with error if ratio string is not correct format", () => { + expect(console.error).not.toBeCalled(); + + const errorStack = create( + + random thing + + ); + + expect(console.error).toBeCalled(); + expect(errorStack.toJSON()).toMatchSnapshot(); + }); }); }); diff --git a/packages/frame/examples/Frame.stories.mdx b/packages/frame/examples/Frame.stories.mdx index 0307b3ce95..dd9eb33c5b 100644 --- a/packages/frame/examples/Frame.stories.mdx +++ b/packages/frame/examples/Frame.stories.mdx @@ -25,14 +25,28 @@ yarn add @bedrock-layout/frame -## ratio +## ratio as string -The `ratio` prop takes an array of two numbers, which prepresent the ratio of width to height of the desired aspect ratio. +The `ratio` prop takes a string the is in the format of `${number}/${number}`, which represents the ratio of width to height of the desired aspect ratio. + +In the example below, the frame will maintain a 16:9 aspect ratio and will crop the image to fit. + + + + + computer with data + + + + +## ratio as array + +The `ratio` prop can also take an array of two numbers, which represent the ratio of width to height of the desired aspect ratio. In the example below, the frame will maintain a 4:3 aspect ratio and will crop the image to fit. - + computer with data diff --git a/packages/frame/examples/argTypes.ts b/packages/frame/examples/argTypes.ts index 7c8f39b234..cef789cfcf 100644 --- a/packages/frame/examples/argTypes.ts +++ b/packages/frame/examples/argTypes.ts @@ -3,7 +3,8 @@ export const argTypes = { description: "Aspect ratio that you want the child element to maintain", type: { name: "[number, number]" }, table: { - type: { summary: "[number, number]" }, + // eslint-disable-next-line no-template-curly-in-string + type: { summary: "[number, number] | `${number}/${number}`" }, }, control: "array", }, diff --git a/packages/frame/src/index.tsx b/packages/frame/src/index.tsx index f8f713ba41..f965e21654 100644 --- a/packages/frame/src/index.tsx +++ b/packages/frame/src/index.tsx @@ -1,38 +1,50 @@ import PropTypes from "prop-types"; -import styled, { css } from "styled-components"; +import styled from "styled-components"; + +type Ratio = [number, number] | `${number}/${number}`; export interface FrameProps { - ratio?: [number, number]; + ratio?: Ratio; position?: string; } -export const Frame = styled.div.attrs(() => { +type RatioString = `${number}/${number}`; + +function checkIsRatio(ratio: unknown): ratio is Ratio { + const isCorrectArray = + Array.isArray(ratio) && ratio.length === 2 && ratio.every(Number.isFinite); + return ( + isCorrectArray || + (typeof ratio === "string" && /^\d{1,1000}\/\d{1,1000}$/.test(ratio)) + ); +} + +function getRatioString(ratio: Ratio): RatioString { + return Array.isArray(ratio) ? (ratio.join("/") as RatioString) : ratio; +} + +function getSafeRatio(ratio: unknown): RatioString | undefined { + const isRatio = checkIsRatio(ratio); + + return isRatio ? getRatioString(ratio) : undefined; +} + +export const Frame = styled.div.attrs(({ ratio, style }) => { + const safeRatio = getSafeRatio(ratio); return { "data-bedrock-frame": "", + style: { ...style, "--ratio": safeRatio }, }; })` - --n: ${(props) => - props.ratio && props.ratio[0] && Number.isInteger(props.ratio[0]) - ? props.ratio[0] - : 1}; - - --d: ${(props) => - props.ratio && props.ratio[1] && Number.isInteger(props.ratio[1]) - ? props.ratio[1] - : 1}; - box-sizing: border-box; display: block; inline-size: 100%; position: relative; overflow: hidden; - ${(props) => - props.ratio === undefined - ? "" - : css` - aspect-ratio: var(--n) / var(--d); - `} + &[style*="--ratio"] { + aspect-ratio: var(--ratio); + } > * { position: absolute; @@ -63,22 +75,18 @@ export const Frame = styled.div.attrs(() => { Frame.displayName = "Frame"; -type TwoNumbers = (props: FrameProps, propName: string) => Error | undefined; - -const twoNumbers: TwoNumbers = ({ ratio }, propName) => { +function validateIsArray({ ratio }: FrameProps, propName: string) { if (ratio === undefined) return undefined; - if ( - !Array.isArray(ratio) || - ratio.length !== 2 || - !ratio.every(Number.isInteger) - ) { - console.error(`${propName} needs to be an array of two numbers`); + const isRatio = checkIsRatio(ratio); + if (!isRatio) { + console.error( + `${propName} needs to be an array of two numbers or a string in the form of "width/height"` + ); } return undefined; -}; +} Frame.propTypes = { - //It's valid propType but can't get the type of Validator<[number, number]> to work - ratio: twoNumbers as unknown as React.Validator<[number, number]>, + ratio: validateIsArray as unknown as React.Validator, position: PropTypes.string, }; diff --git a/packages/primitives/__tests__/__snapshots__/primitives.test.js.snap b/packages/primitives/__tests__/__snapshots__/primitives.test.js.snap index ddaf8188a7..2dc6af8997 100644 --- a/packages/primitives/__tests__/__snapshots__/primitives.test.js.snap +++ b/packages/primitives/__tests__/__snapshots__/primitives.test.js.snap @@ -294,23 +294,15 @@ Object { "isStatic": false, "rules": Array [ " - --n: ", - [Function], - "; - - --d: ", - [Function], - "; - box-sizing: border-box; display: block; inline-size: 100%; position: relative; overflow: hidden; - ", - [Function], - " + &[style*=\\"--ratio\\"] { + aspect-ratio: var(--ratio); + } > * { position: absolute; diff --git a/packages/primitives/examples/primitives.stories.mdx b/packages/primitives/examples/primitives.stories.mdx index 09612a1a92..ae48da9f5f 100644 --- a/packages/primitives/examples/primitives.stories.mdx +++ b/packages/primitives/examples/primitives.stories.mdx @@ -14,7 +14,7 @@ The `@bedrock-layout/primitives` package includes all the primitives, hooks, and or -`yarn add @bedrock-laylock/primitives` +`yarn add @bedrock-layout/primitives` ---