From 4f0fc475451924cf9faf35335a46a3eb2bf03c84 Mon Sep 17 00:00:00 2001 From: Geoffrey Chong Date: Thu, 26 Oct 2023 10:37:34 +1100 Subject: [PATCH 1/7] add Content + Container --- .../src/Container/Container.module.scss | 13 ++++ .../components/src/Container/Container.tsx | 23 +++++++ .../src/Container/_docs/Container.mdx | 33 ++++++++++ .../src/Container/_docs/Container.stories.tsx | 66 +++++++++++++++++++ packages/components/src/Container/index.ts | 1 + .../src/Content/Content.module.scss | 12 ++++ packages/components/src/Content/Content.tsx | 52 +++++++++++++++ .../components/src/Content/_docs/Content.mdx | 31 +++++++++ .../src/Content/_docs/Content.stories.tsx | 63 ++++++++++++++++++ .../components/src/Content/_variables.scss | 3 + packages/components/src/Content/index.ts | 1 + packages/components/src/Skirt/index.ts | 1 + packages/components/src/index.ts | 2 + 13 files changed, 301 insertions(+) create mode 100644 packages/components/src/Container/Container.module.scss create mode 100644 packages/components/src/Container/Container.tsx create mode 100644 packages/components/src/Container/_docs/Container.mdx create mode 100644 packages/components/src/Container/_docs/Container.stories.tsx create mode 100644 packages/components/src/Container/index.ts create mode 100644 packages/components/src/Content/Content.module.scss create mode 100644 packages/components/src/Content/Content.tsx create mode 100644 packages/components/src/Content/_docs/Content.mdx create mode 100644 packages/components/src/Content/_docs/Content.stories.tsx create mode 100644 packages/components/src/Content/_variables.scss create mode 100644 packages/components/src/Content/index.ts create mode 100644 packages/components/src/Skirt/index.ts diff --git a/packages/components/src/Container/Container.module.scss b/packages/components/src/Container/Container.module.scss new file mode 100644 index 00000000000..69ea25208e1 --- /dev/null +++ b/packages/components/src/Container/Container.module.scss @@ -0,0 +1,13 @@ +.container { + display: flex; + width: 100%; + justify-content: center; + + * { + &, + &::after, + &::before { + box-sizing: border-box; + } + } +} diff --git a/packages/components/src/Container/Container.tsx b/packages/components/src/Container/Container.tsx new file mode 100644 index 00000000000..22f418c343e --- /dev/null +++ b/packages/components/src/Container/Container.tsx @@ -0,0 +1,23 @@ +import React from "react" +import classnames from "classnames" +import { ContentProps } from "~components/Content" +import styles from "./Container.module.scss" + +/** + * {@link https://cultureamp.atlassian.net/wiki/spaces/DesignSystem/pages/3086156812/Layout Guidance} | + * {@link https://cultureamp.design/?path=/docs/components-content--docs Storybook} + */ +export const Container = React.forwardRef( + ({ children, style, classNameOverride, ...restProps }, ref) => ( +
+ {children} +
+ ) +) + +Container.displayName = "Container" diff --git a/packages/components/src/Container/_docs/Container.mdx b/packages/components/src/Container/_docs/Container.mdx new file mode 100644 index 00000000000..607e6faa865 --- /dev/null +++ b/packages/components/src/Container/_docs/Container.mdx @@ -0,0 +1,33 @@ +import { Canvas, Controls, Meta } from "@storybook/blocks" +import { ResourceLinks, KaioNotification, Installation } from "~storybook/components" +import { LinkTo } from "~storybook/components/LinkTo" +import * as ContainerStories from "./Container.stories" + + + +# Container + + + + + + + + +## Overview + +Wraps your entire page. + + + + +## Use Case +Usually wraps a Content component. + diff --git a/packages/components/src/Container/_docs/Container.stories.tsx b/packages/components/src/Container/_docs/Container.stories.tsx new file mode 100644 index 00000000000..e608b96f847 --- /dev/null +++ b/packages/components/src/Container/_docs/Container.stories.tsx @@ -0,0 +1,66 @@ +import React from "react" +import { Meta, StoryObj } from "@storybook/react" +import { Content } from "~components/Content" +import { Text } from "~components/Text" +import { Container } from "../index" + +const meta = { + title: "Pages/Container", + component: Container, + args: { + children: ( +
+ + Bacon ipsum dolor amet andouille buffalo beef boudin kielbasa + drumstick fatback cow tongue ground round chicken. Jowl cow short + ribs, ham tongue turducken spare ribs pig drumstick chuck meatball. + Buffalo turducken pancetta tail salami chicken. Bresaola venison + pastrami beef. + + + Porchetta shankle ribeye, ground round beef filet mignon fatback + chislic boudin. Boudin burgdoggen spare ribs, meatloaf pastrami pork + loin meatball short ribs tenderloin ribeye cupim venison short loin + pork chop tongue. Andouille landjaeger bacon, picanha filet mignon + short ribs hamburger shank shoulder porchetta. Pork chop ground round + tenderloin, biltong kevin corned beef chuck frankfurter spare ribs + pork meatball pastrami fatback. Strip steak beef ribs pork loin kevin, + biltong fatback tongue salami brisket capicola flank tenderloin. + +
+ ), + }, + argTypes: { + children: { controls: "disabled" }, + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Playground: Story = { + parameters: { + docs: { + canvas: { + sourceState: "shown", + }, + }, + }, +} + +export const Example: Story = { + render: args => ( + + + + ), + parameters: { + chromatic: { disable: false }, + docs: { + canvas: { + sourceState: "shown", + }, + }, + }, +} diff --git a/packages/components/src/Container/index.ts b/packages/components/src/Container/index.ts new file mode 100644 index 00000000000..74d4d9e41cb --- /dev/null +++ b/packages/components/src/Container/index.ts @@ -0,0 +1 @@ +export * from "./Container" diff --git a/packages/components/src/Content/Content.module.scss b/packages/components/src/Content/Content.module.scss new file mode 100644 index 00000000000..ecccbdbf258 --- /dev/null +++ b/packages/components/src/Content/Content.module.scss @@ -0,0 +1,12 @@ +@import "~@kaizen/design-tokens/sass/layout"; +@import "./variables"; + +.content { + max-width: $layout-content-max-width; + margin: 0 $layout-content-side-margin; + width: 100%; + + @media (max-width: calc(#{$layout-breakpoints-large} - 1px)) { + margin: 0 $content-margin-width-on-medium-and-small; + } +} diff --git a/packages/components/src/Content/Content.tsx b/packages/components/src/Content/Content.tsx new file mode 100644 index 00000000000..fb5d1fe6084 --- /dev/null +++ b/packages/components/src/Content/Content.tsx @@ -0,0 +1,52 @@ +import React, { HTMLAttributes } from "react" +import classnames from "classnames" +import { OverrideClassName } from "~types/OverrideClassName" +import styles from "./Content.module.scss" + +export type ContentProps = { + children?: React.ReactNode + /** + * Not recommended. A short-circuit for dynamically overriding layout in a pinch + */ + style?: Pick< + React.CSSProperties, + | "bottom" + | "left" + | "margin" + | "marginBottom" + | "marginLeft" + | "marginRight" + | "marginTop" + | "padding" + | "paddingBottom" + | "paddingLeft" + | "paddingRight" + | "paddingTop" + | "position" + | "right" + | "top" + | "transform" + | "transformBox" + | "transformOrigin" + | "transformStyle" + > +} & OverrideClassName> + +/** + * {@link https://cultureamp.atlassian.net/wiki/spaces/DesignSystem/pages/3086156812/Layout Guidance} | + * {@link https://cultureamp.design/?path=/docs/components-content--docs Storybook} + */ +export const Content = React.forwardRef( + ({ children, style, classNameOverride, ...restProps }, ref) => ( +
+ {children} +
+ ) +) + +Content.displayName = "Content" diff --git a/packages/components/src/Content/_docs/Content.mdx b/packages/components/src/Content/_docs/Content.mdx new file mode 100644 index 00000000000..ed16e7cf15d --- /dev/null +++ b/packages/components/src/Content/_docs/Content.mdx @@ -0,0 +1,31 @@ +import { Canvas, Meta } from "@storybook/blocks" +import { ResourceLinks, KaioNotification, Installation } from "~storybook/components" +import { LinkTo } from "~storybook/components/LinkTo" +import * as ContentStories from "./Content.stories" + + + +# Content + + + + + + + +## Overview + +Wraps your content at a **page level** in the standard minimum width and margins. + + + +## Use Case +Usually wrapped in a Container + diff --git a/packages/components/src/Content/_docs/Content.stories.tsx b/packages/components/src/Content/_docs/Content.stories.tsx new file mode 100644 index 00000000000..1eff846f5dd --- /dev/null +++ b/packages/components/src/Content/_docs/Content.stories.tsx @@ -0,0 +1,63 @@ +import React from "react" +import { Meta, StoryObj } from "@storybook/react" +import { Container } from "~components/Container" +import { Text } from "~components/Text" +import { Content } from "../index" + +const meta = { + title: "Pages/Content", + component: Content, + args: { + children: ( + <> + + Bacon ipsum dolor amet andouille buffalo beef boudin kielbasa + drumstick fatback cow tongue ground round chicken. Jowl cow short + ribs, ham tongue turducken spare ribs pig drumstick chuck meatball. + Buffalo turducken pancetta tail salami chicken. Bresaola venison + pastrami beef. + + + Porchetta shankle ribeye, ground round beef filet mignon fatback + chislic boudin. Boudin burgdoggen spare ribs, meatloaf pastrami pork + loin meatball short ribs tenderloin ribeye cupim venison short loin + pork chop tongue. Andouille landjaeger bacon, picanha filet mignon + short ribs hamburger shank shoulder porchetta. Pork chop ground round + tenderloin, biltong kevin corned beef chuck frankfurter spare ribs + pork meatball pastrami fatback. Strip steak beef ribs pork loin kevin, + biltong fatback tongue salami brisket capicola flank tenderloin. + + + ), + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Playground: Story = { + parameters: { + docs: { + canvas: { + sourceState: "shown", + }, + }, + }, +} + +export const Example: Story = { + render: args => ( + + + + ), + parameters: { + chromatic: { disable: false }, + docs: { + canvas: { + sourceState: "shown", + }, + }, + }, +} diff --git a/packages/components/src/Content/_variables.scss b/packages/components/src/Content/_variables.scss new file mode 100644 index 00000000000..a4ea8cb54d1 --- /dev/null +++ b/packages/components/src/Content/_variables.scss @@ -0,0 +1,3 @@ +// This replicates the layout max-width logic in +// draft-packages/title-block-zen/KaizenDraft/TitleBlockZen/TitleBlockZen.scss +$content-margin-width-on-medium-and-small: 12px; diff --git a/packages/components/src/Content/index.ts b/packages/components/src/Content/index.ts new file mode 100644 index 00000000000..08d3aa4845a --- /dev/null +++ b/packages/components/src/Content/index.ts @@ -0,0 +1 @@ +export * from "./Content" diff --git a/packages/components/src/Skirt/index.ts b/packages/components/src/Skirt/index.ts new file mode 100644 index 00000000000..e5787ed0fce --- /dev/null +++ b/packages/components/src/Skirt/index.ts @@ -0,0 +1 @@ +// export * from "./Skirt" diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 7f39e5d3d89..2b81a1c58e1 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -10,6 +10,8 @@ export * from "./Card" export * from "./Checkbox" export * from "./ClearButton" export * from "./Collapsible" +export * from "./Container" +export * from "./Content" export * from "./DateInput" export * from "./DatePicker" export * from "./DateRangePicker" From fff3424127e498a3fbdc4510985f69a2d6256ad4 Mon Sep 17 00:00:00 2001 From: Geoffrey Chong Date: Thu, 26 Oct 2023 11:17:18 +1100 Subject: [PATCH 2/7] add Skirt + SkirtCard --- packages/components/package.json | 1 + .../components/src/Skirt/Skirt.module.scss | 32 +++++++ packages/components/src/Skirt/Skirt.tsx | 76 +++++++++++++++++ packages/components/src/Skirt/_docs/Skirt.mdx | 28 +++++++ .../_docs/Skirt.stickersheet.stories.tsx | 83 +++++++++++++++++++ .../src/Skirt/_docs/Skirt.stories.tsx | 50 +++++++++++ packages/components/src/Skirt/index.ts | 2 +- .../SkirtCard/SkirtCard.module.scss | 17 ++++ .../subcomponents/SkirtCard/SkirtCard.tsx | 18 ++++ .../Skirt/subcomponents/SkirtCard/index.ts | 1 + packages/components/src/index.ts | 1 + .../src/utils/useResizeObserver.spec.tsx | 49 +++++++++++ .../components/src/utils/useResizeObserver.ts | 75 +++++++++++++++++ 13 files changed, 432 insertions(+), 1 deletion(-) create mode 100644 packages/components/src/Skirt/Skirt.module.scss create mode 100644 packages/components/src/Skirt/Skirt.tsx create mode 100644 packages/components/src/Skirt/_docs/Skirt.mdx create mode 100644 packages/components/src/Skirt/_docs/Skirt.stickersheet.stories.tsx create mode 100644 packages/components/src/Skirt/_docs/Skirt.stories.tsx create mode 100644 packages/components/src/Skirt/subcomponents/SkirtCard/SkirtCard.module.scss create mode 100644 packages/components/src/Skirt/subcomponents/SkirtCard/SkirtCard.tsx create mode 100644 packages/components/src/Skirt/subcomponents/SkirtCard/index.ts create mode 100644 packages/components/src/utils/useResizeObserver.spec.tsx create mode 100644 packages/components/src/utils/useResizeObserver.ts diff --git a/packages/components/package.json b/packages/components/package.json index e0e16138c57..9d9ff505c9d 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -83,6 +83,7 @@ "react-popper": "^2.3.0", "react-select": "^5.7.7", "react-textfit": "^1.1.1", + "resize-observer-polyfill": "^1.5.1", "tslib": "^2.6.2", "use-debounce": "^9.0.4", "uuid": "^9.0.1" diff --git a/packages/components/src/Skirt/Skirt.module.scss b/packages/components/src/Skirt/Skirt.module.scss new file mode 100644 index 00000000000..d0075011399 --- /dev/null +++ b/packages/components/src/Skirt/Skirt.module.scss @@ -0,0 +1,32 @@ +@import "~@kaizen/design-tokens/sass/color"; + +$dt-color-background-color-default: $color-purple-600; +$dt-color-background-color-education: $color-blue-100; + +.container { + position: relative; +} + +// Actual height is determined in JavaScript +.underlay { + box-sizing: content-box; + position: absolute; + z-index: 1; + top: 0; + left: 0; + right: 0; + height: 100%; +} + +.defaultVariant { + background-color: $dt-color-background-color-default; +} + +.educationVariant { + background-color: $dt-color-background-color-education; +} + +.content { + position: relative; + z-index: 2; +} diff --git a/packages/components/src/Skirt/Skirt.tsx b/packages/components/src/Skirt/Skirt.tsx new file mode 100644 index 00000000000..9c89b88dda5 --- /dev/null +++ b/packages/components/src/Skirt/Skirt.tsx @@ -0,0 +1,76 @@ +import React from "react" +import classnames from "classnames" +import { Container } from "~components/Container" +import { Content, ContentProps } from "~components/Content" +import { useResizeObserver } from "~utils/useResizeObserver" +import styles from "./Skirt.module.scss" + +const spacing = 24 +const maxHeight = 222 +const fallbackPercentage = 0.8 + +type Variant = "default" | "education" + +export type SkirtProps = { + children: React.ReactNode + variant?: Variant + titleBlockHasNavigation?: boolean +} & ContentProps + +export const Skirt = ({ + children, + variant = "default", + titleBlockHasNavigation = true, + classNameOverride, + ...restProps +}: SkirtProps): JSX.Element => { + const [ref, skirtHeight] = useResizeObserver( + entry => { + if (entry.contentRect) { + return deriveSkirtHeight(entry.contentRect, titleBlockHasNavigation) + } + return undefined + } + ) + + return ( + +
+ {children} + + ) +} + +Skirt.displayName = "Skirt" + +const deriveSkirtHeight = ( + rect: DOMRectReadOnly, + titleBlockHasNavigation: boolean +): number => { + const { height, width } = rect + let responsiveOffset: number = 0 + if (width > 768) { + responsiveOffset = 2.8125 * 16 + } + + // This ensures the maximum height of the skirt is consistent between pages + // where the title block has/doesn’t have navigation + const derivedMaxHeight = titleBlockHasNavigation + ? maxHeight + : maxHeight + responsiveOffset + const maxHeightWithSpacing = derivedMaxHeight + spacing + + return Math.max( + spacing, + height > maxHeightWithSpacing + ? derivedMaxHeight + : height * fallbackPercentage - spacing + ) +} diff --git a/packages/components/src/Skirt/_docs/Skirt.mdx b/packages/components/src/Skirt/_docs/Skirt.mdx new file mode 100644 index 00000000000..611c0c6f804 --- /dev/null +++ b/packages/components/src/Skirt/_docs/Skirt.mdx @@ -0,0 +1,28 @@ +import { Canvas, Controls, Meta } from "@storybook/blocks" +import { ResourceLinks, KaioNotification, Installation } from "~storybook/components" +import * as SkirtStories from "./Skirt.stories" + + + +# Skirt + + + + + + + +## Overview + +A coloured shade that extends behind content. + + + diff --git a/packages/components/src/Skirt/_docs/Skirt.stickersheet.stories.tsx b/packages/components/src/Skirt/_docs/Skirt.stickersheet.stories.tsx new file mode 100644 index 00000000000..903a943e630 --- /dev/null +++ b/packages/components/src/Skirt/_docs/Skirt.stickersheet.stories.tsx @@ -0,0 +1,83 @@ +import React from "react" +import { Meta } from "@storybook/react" +import { + StickerSheet, + StickerSheetStory, +} from "~storybook/components/StickerSheet" +import { Skirt } from "../index" + +export default { + title: "Components/Skirt", + parameters: { + chromatic: { disable: false }, + controls: { disable: true }, + }, +} satisfies Meta + +const StickerSheetTemplate: StickerSheetStory = { + render: ({ isReversed }) => ( + /** @note: If you have multiple StickerSheets to display, you can add a `heading` */ + + + + + + + + + + + + ), + /** @note: Only required if you have pseudo states, otherwise this can be removed */ + parameters: { + /** @todo: Remove any inapplicable pseudo states */ + pseudo: { + hover: '[data-sb-pseudo-styles="hover"]', + active: '[data-sb-pseudo-styles="active"]', + focus: '[data-sb-pseudo-styles="focus"]', + focusVisible: '[data-sb-pseudo-styles="focus"]', + }, + }, +} + +export const StickerSheetDefault: StickerSheetStory = { + ...StickerSheetTemplate, + name: "Sticker Sheet (Default)", +} + +export const StickerSheetReversed: StickerSheetStory = { + ...StickerSheetTemplate, + name: "Sticker Sheet (Reversed)", + parameters: { + /** @note: Only required if template has parameters, otherwise this spread can be removed */ + ...StickerSheetTemplate.parameters, + backgrounds: { default: "Purple 700" }, + }, + args: { isReversed: true }, +} + +export const StickerSheetRTL: StickerSheetStory = { + ...StickerSheetTemplate, + name: "Sticker Sheet (RTL)", + parameters: { + /** @note: Only required if template has parameters, otherwise this spread can be removed */ + ...StickerSheetTemplate.parameters, + textDirection: "rtl", + }, +} diff --git a/packages/components/src/Skirt/_docs/Skirt.stories.tsx b/packages/components/src/Skirt/_docs/Skirt.stories.tsx new file mode 100644 index 00000000000..6bc333f8768 --- /dev/null +++ b/packages/components/src/Skirt/_docs/Skirt.stories.tsx @@ -0,0 +1,50 @@ +import React from "react" +import { Meta, StoryObj } from "@storybook/react" +import { Text } from "~components/Text" +import { Skirt } from "../index" +import { SkirtCard } from "../subcomponents/SkirtCard" + +const meta = { + title: "Components/Skirt", + component: Skirt, + args: { + children: ( + + + Bacon ipsum dolor amet andouille buffalo beef boudin kielbasa + drumstick fatback cow tongue ground round chicken. Jowl cow short + ribs, ham tongue turducken spare ribs pig drumstick chuck meatball. + Buffalo turducken pancetta tail salami chicken. Bresaola venison + pastrami beef. + + + Porchetta shankle ribeye, ground round beef filet mignon fatback + chislic boudin. Boudin burgdoggen spare ribs, meatloaf pastrami pork + loin meatball short ribs tenderloin ribeye cupim venison short loin + pork chop tongue. Andouille landjaeger bacon, picanha filet mignon + short ribs hamburger shank shoulder porchetta. Pork chop ground round + tenderloin, biltong kevin corned beef chuck frankfurter spare ribs + pork meatball pastrami fatback. Strip steak beef ribs pork loin kevin, + biltong fatback tongue salami brisket capicola flank tenderloin. + + + ), + }, + argTypes: { + children: { control: "disabled" } + } +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Playground: Story = { + parameters: { + docs: { + canvas: { + sourceState: "shown", + }, + }, + }, +} diff --git a/packages/components/src/Skirt/index.ts b/packages/components/src/Skirt/index.ts index e5787ed0fce..25ad577969d 100644 --- a/packages/components/src/Skirt/index.ts +++ b/packages/components/src/Skirt/index.ts @@ -1 +1 @@ -// export * from "./Skirt" +export * from "./Skirt" diff --git a/packages/components/src/Skirt/subcomponents/SkirtCard/SkirtCard.module.scss b/packages/components/src/Skirt/subcomponents/SkirtCard/SkirtCard.module.scss new file mode 100644 index 00000000000..da485b01199 --- /dev/null +++ b/packages/components/src/Skirt/subcomponents/SkirtCard/SkirtCard.module.scss @@ -0,0 +1,17 @@ +@import "~@kaizen/design-tokens/sass/layout"; +@import "../../../../Content/variables"; + +.wrapper { + @media (max-width: $layout-breakpoints-large) { + margin-left: $content-margin-width-on-medium-and-small * -1; + margin-right: $content-margin-width-on-medium-and-small * -1; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + + @media (max-width: $layout-breakpoints-medium) { + border-radius: 0; + margin-left: $content-margin-width-on-medium-and-small * -1; + margin-right: $content-margin-width-on-medium-and-small * -1; + } +} diff --git a/packages/components/src/Skirt/subcomponents/SkirtCard/SkirtCard.tsx b/packages/components/src/Skirt/subcomponents/SkirtCard/SkirtCard.tsx new file mode 100644 index 00000000000..057cd040fb3 --- /dev/null +++ b/packages/components/src/Skirt/subcomponents/SkirtCard/SkirtCard.tsx @@ -0,0 +1,18 @@ +import React from "react" +import classnames from "classnames" +import { Card, CardProps } from "~components/Card" +import styles from "./SkirtCard.module.scss" + +export type SkirtCardProps = CardProps + +export const SkirtCard = (props: SkirtCardProps): JSX.Element => { + const { classNameOverride, ...restProps } = props + return ( + + ) +} + +SkirtCard.displayName = "SkirtCard" diff --git a/packages/components/src/Skirt/subcomponents/SkirtCard/index.ts b/packages/components/src/Skirt/subcomponents/SkirtCard/index.ts new file mode 100644 index 00000000000..a0698b975c4 --- /dev/null +++ b/packages/components/src/Skirt/subcomponents/SkirtCard/index.ts @@ -0,0 +1 @@ +export * from "./SkirtCard" diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 2b81a1c58e1..c6d3b6437c0 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -39,6 +39,7 @@ export * from "./Popover" export * from "./Radio" export * from "./SearchField" export * from "./Select" +export * from "./Skirt" export * from "./Slider" export * from "./Text" export * from "./Workflow" diff --git a/packages/components/src/utils/useResizeObserver.spec.tsx b/packages/components/src/utils/useResizeObserver.spec.tsx new file mode 100644 index 00000000000..d80836abd0d --- /dev/null +++ b/packages/components/src/utils/useResizeObserver.spec.tsx @@ -0,0 +1,49 @@ +import { setImmediate } from "timers" +import { renderHook, act } from "@testing-library/react-hooks" +import { useResizeObserver } from "./useResizeObserver" + +function MockResizeObserver(callback: unknown): void { + // @ts-ignore + this.callback = callback +} + +MockResizeObserver.prototype.observe = async function observe(): Promise { + this.callback(["first"]) + await new Promise(r => setImmediate(r)) + this.callback(["second"]) + await new Promise(r => setImmediate(r)) + this.callback(["third"]) +} + +const disconnect = jest.fn() +MockResizeObserver.prototype.disconnect = disconnect + +jest.mock("resize-observer-polyfill", () => ({ + __esModule: true, + default: MockResizeObserver, +})) + +describe("useResizeObserver", () => { + it("Calls the callback with the expected entries", async () => { + const callback = jest.fn().mockImplementation(value => value) + const { result, waitForNextUpdate, unmount } = renderHook(() => + useResizeObserver(callback) + ) + await act(async () => { + // @ts-ignore + result.current[0]("node") + expect(result.current[1]).toBe(undefined) + await waitForNextUpdate() + expect(callback).toBeCalledTimes(1) + expect(result.current[1]).toBe("first") + await waitForNextUpdate() + expect(callback).toBeCalledTimes(2) + expect(result.current[1]).toBe("second") + await waitForNextUpdate() + expect(callback).toBeCalledTimes(3) + expect(result.current[1]).toBe("third") + }) + unmount() + expect(disconnect).toBeCalled() + }) +}) diff --git a/packages/components/src/utils/useResizeObserver.ts b/packages/components/src/utils/useResizeObserver.ts new file mode 100644 index 00000000000..4a383228bd1 --- /dev/null +++ b/packages/components/src/utils/useResizeObserver.ts @@ -0,0 +1,75 @@ +import React, { Ref, useCallback, useEffect, useRef, useState } from "react" +import ResizeObserver from "resize-observer-polyfill" + +const defaultCallback = (entry: ResizeObserverEntry): ResizeObserverEntry => + entry + +export interface DOMRectReadOnly { + readonly x: number + readonly y: number + readonly width: number + readonly height: number + readonly top: number + readonly right: number + readonly bottom: number + readonly left: number +} + +/** + * Hook for observing changes to a DOM element via ResizeObserver. + * + * @param {resolveEntryCallback} resolveEntry - Callback for resolving the + * desired value from each ResizeObserverEntry emitted by ResizeObserver + * @return {Array} An array containing a ref for binding to the observed DOM + * element, and the current value of the callback-resolved ResizeObserverEntry + * @callback resolveEntryCallback + */ +export const useResizeObserver = ( + resolveEntry: (entry: ResizeObserverEntry) => any = defaultCallback +): [Ref, T | undefined] => { + const destroyResizeObserverRef: React.MutableRefObject< + undefined | (() => void) + > = useRef(undefined) + const [dimensions, setDimensions] = useState(undefined) + const resolveEntryRef: React.MutableRefObject< + (entry: ResizeObserverEntry) => any + > = useRef(resolveEntry) + + const ref: Ref = useCallback( + (node: E) => { + if (node) { + const resizeObserver = new ResizeObserver( + (entries: ResizeObserverEntry[]) => { + for (const entry of entries) { + const value = resolveEntryRef.current(entry) + if (value) { + setDimensions(value) + } + } + } + ) + resizeObserver.observe(node) + destroyResizeObserverRef.current = (): void => { + resizeObserver.disconnect() + } + } + }, + [destroyResizeObserverRef, setDimensions, resolveEntryRef] + ) + + // Ensure the resolveEntryRef has the latest value + useEffect(() => { + resolveEntryRef.current = resolveEntry + }, [resolveEntry]) + + // Destroy the observer on unmount + useEffect( + () => () => { + if (destroyResizeObserverRef.current) { + destroyResizeObserverRef.current() + } + }, + [] + ) + return [ref, dimensions] +} From b2aaf266c04d1939c0a93d3821f3ab939b0d37fe Mon Sep 17 00:00:00 2001 From: Geoffrey Chong Date: Thu, 26 Oct 2023 11:20:21 +1100 Subject: [PATCH 3/7] fix lint --- .../_docs/Skirt.stickersheet.stories.tsx | 83 ------------------- .../src/Skirt/_docs/Skirt.stories.tsx | 4 +- .../SkirtCard/SkirtCard.module.scss | 2 +- 3 files changed, 3 insertions(+), 86 deletions(-) delete mode 100644 packages/components/src/Skirt/_docs/Skirt.stickersheet.stories.tsx diff --git a/packages/components/src/Skirt/_docs/Skirt.stickersheet.stories.tsx b/packages/components/src/Skirt/_docs/Skirt.stickersheet.stories.tsx deleted file mode 100644 index 903a943e630..00000000000 --- a/packages/components/src/Skirt/_docs/Skirt.stickersheet.stories.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from "react" -import { Meta } from "@storybook/react" -import { - StickerSheet, - StickerSheetStory, -} from "~storybook/components/StickerSheet" -import { Skirt } from "../index" - -export default { - title: "Components/Skirt", - parameters: { - chromatic: { disable: false }, - controls: { disable: true }, - }, -} satisfies Meta - -const StickerSheetTemplate: StickerSheetStory = { - render: ({ isReversed }) => ( - /** @note: If you have multiple StickerSheets to display, you can add a `heading` */ - - - - - - - - - - - - ), - /** @note: Only required if you have pseudo states, otherwise this can be removed */ - parameters: { - /** @todo: Remove any inapplicable pseudo states */ - pseudo: { - hover: '[data-sb-pseudo-styles="hover"]', - active: '[data-sb-pseudo-styles="active"]', - focus: '[data-sb-pseudo-styles="focus"]', - focusVisible: '[data-sb-pseudo-styles="focus"]', - }, - }, -} - -export const StickerSheetDefault: StickerSheetStory = { - ...StickerSheetTemplate, - name: "Sticker Sheet (Default)", -} - -export const StickerSheetReversed: StickerSheetStory = { - ...StickerSheetTemplate, - name: "Sticker Sheet (Reversed)", - parameters: { - /** @note: Only required if template has parameters, otherwise this spread can be removed */ - ...StickerSheetTemplate.parameters, - backgrounds: { default: "Purple 700" }, - }, - args: { isReversed: true }, -} - -export const StickerSheetRTL: StickerSheetStory = { - ...StickerSheetTemplate, - name: "Sticker Sheet (RTL)", - parameters: { - /** @note: Only required if template has parameters, otherwise this spread can be removed */ - ...StickerSheetTemplate.parameters, - textDirection: "rtl", - }, -} diff --git a/packages/components/src/Skirt/_docs/Skirt.stories.tsx b/packages/components/src/Skirt/_docs/Skirt.stories.tsx index 6bc333f8768..d9ccedc4364 100644 --- a/packages/components/src/Skirt/_docs/Skirt.stories.tsx +++ b/packages/components/src/Skirt/_docs/Skirt.stories.tsx @@ -31,8 +31,8 @@ const meta = { ), }, argTypes: { - children: { control: "disabled" } - } + children: { control: "disabled" }, + }, } satisfies Meta export default meta diff --git a/packages/components/src/Skirt/subcomponents/SkirtCard/SkirtCard.module.scss b/packages/components/src/Skirt/subcomponents/SkirtCard/SkirtCard.module.scss index da485b01199..a3880dadad5 100644 --- a/packages/components/src/Skirt/subcomponents/SkirtCard/SkirtCard.module.scss +++ b/packages/components/src/Skirt/subcomponents/SkirtCard/SkirtCard.module.scss @@ -1,5 +1,5 @@ @import "~@kaizen/design-tokens/sass/layout"; -@import "../../../../Content/variables"; +@import "../../../Content/variables"; .wrapper { @media (max-width: $layout-breakpoints-large) { From 98535b420bd7e7b30f25991004dcd6b26f3614f5 Mon Sep 17 00:00:00 2001 From: Geoffrey Chong Date: Thu, 26 Oct 2023 11:20:56 +1100 Subject: [PATCH 4/7] add changeset --- .changeset/tough-beers-carry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tough-beers-carry.md diff --git a/.changeset/tough-beers-carry.md b/.changeset/tough-beers-carry.md new file mode 100644 index 00000000000..2eb40ae448b --- /dev/null +++ b/.changeset/tough-beers-carry.md @@ -0,0 +1,5 @@ +--- +"@kaizen/components": minor +--- + +Migrate Skirt, Content, Container, Titleblock from `kaizen-legacy` From fe20d46090de45e57d6a3ff075526d95626d8be6 Mon Sep 17 00:00:00 2001 From: Geoffrey Chong Date: Thu, 26 Oct 2023 11:23:54 +1100 Subject: [PATCH 5/7] Update tough-beers-carry.md --- .changeset/tough-beers-carry.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tough-beers-carry.md b/.changeset/tough-beers-carry.md index 2eb40ae448b..d0f9615a281 100644 --- a/.changeset/tough-beers-carry.md +++ b/.changeset/tough-beers-carry.md @@ -2,4 +2,4 @@ "@kaizen/components": minor --- -Migrate Skirt, Content, Container, Titleblock from `kaizen-legacy` +Migrate Skirt, Content, Container from `kaizen-legacy` From 2e91be86419c8c6db0226df39cc9285bdb08a485 Mon Sep 17 00:00:00 2001 From: Geoffrey Chong Date: Thu, 26 Oct 2023 13:08:38 +1100 Subject: [PATCH 6/7] Update packages/components/src/Container/_docs/Container.mdx Co-authored-by: Cassandra --- packages/components/src/Container/_docs/Container.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/src/Container/_docs/Container.mdx b/packages/components/src/Container/_docs/Container.mdx index 607e6faa865..29996367560 100644 --- a/packages/components/src/Container/_docs/Container.mdx +++ b/packages/components/src/Container/_docs/Container.mdx @@ -7,7 +7,6 @@ import * as ContainerStories from "./Container.stories" # Container - Date: Thu, 26 Oct 2023 13:10:11 +1100 Subject: [PATCH 7/7] Remove duplicate to save some money --- packages/components/src/Container/_docs/Container.stories.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/src/Container/_docs/Container.stories.tsx b/packages/components/src/Container/_docs/Container.stories.tsx index e608b96f847..26cd7db5506 100644 --- a/packages/components/src/Container/_docs/Container.stories.tsx +++ b/packages/components/src/Container/_docs/Container.stories.tsx @@ -56,7 +56,6 @@ export const Example: Story = { ), parameters: { - chromatic: { disable: false }, docs: { canvas: { sourceState: "shown",