diff --git a/.storybook/main.ts b/.storybook/main.ts index 660899fd1c..8d181c8db4 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -20,7 +20,7 @@ import type { StorybookConfig } from '@storybook/react-webpack5'; const config: StorybookConfig = { stories: [ - '../examples/**/stories.@(ts|tsx|js|jsx)', + '../examples/**/{stories,*.stories}.@(ts|tsx|js|jsx)', ], addons: [ '@storybook/addon-a11y', diff --git a/examples/bpk-component-layout/box-examples.tsx b/examples/bpk-component-layout/box-examples.tsx new file mode 100644 index 0000000000..091208374b --- /dev/null +++ b/examples/bpk-component-layout/box-examples.tsx @@ -0,0 +1,203 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + BpkBox, + BpkSpacing, +} from '../../packages/bpk-component-layout'; + +import Wrapper from './layout-wrapper'; + +import STYLES from './examples.module.scss'; + +/** + * Core layout example – demonstrates basic spacing usage. + * + * @returns {JSX.Element} A box with padding and margin using Backpack spacing tokens. + */ +export const SpacingExample = () => ( + + + + Default box with padding and margin using Backpack spacing tokens. + + + +); + +/** + * RTL-friendly spacing example – demonstrates margin/padding logical props. + * + * @returns {JSX.Element} Box using marginInline & paddingInline in RTL context. + */ +export const RtlSpacingExample = () => ( + +
+ + + Box using marginInline & paddingInline in RTL context. + + +
+
+); + +/** + * Size example – demonstrates width/height using semantic values. + * + * @returns {JSX.Element} Box with 50% width and 6rem minHeight. + */ +export const SizeExample = () => ( + + + + Box with 50% width and 6rem minHeight. + + + +); + +/** + * Responsive example – demonstrates breakpoint-based responsive layout props. + * + * @returns {JSX.Element} A box whose spacing changes across breakpoints. + */ +export const ResponsiveExample = () => ( + + + + + Responsive item 1 + + + + + Responsive item 2 + + + + +); + +/** + * Position example – demonstrates top/left offsets using allowed values. + * + * @returns {JSX.Element} A relative box with an absolutely positioned child. + */ +export const PositionExample = () => ( + + + + + Relative box (10rem x 6rem) + + + + Positioned child (top/left from 12rem, 6rem) + + + + + +); + +/** + * Flexbox example – demonstrates using BpkBox as a flex container. + * + * @returns {JSX.Element} A flex row with evenly spaced items. + */ +export const FlexExample = () => ( + + + {[1, 2, 3].map((i) => ( + + + Flex item {i} + + + ))} + + +); + +/** + * Grid example – demonstrates using BpkBox as a grid container. + * + * @returns {JSX.Element} A simple three-column grid. + */ +export const GridExample = () => ( + + + {[1, 2, 3, 4, 5, 6].map((i) => ( + + + Grid cell {i} + + + ))} + + +); + +/** + * Mixed visual regression example – used for Percy/visual tests. + * + * @returns {JSX.Element} A wrapper containing all Box examples for visual regression. + */ +export const MixedExample = () => ( + + + + + + + + + +); + + diff --git a/examples/bpk-component-layout/box.stories.tsx b/examples/bpk-component-layout/box.stories.tsx new file mode 100644 index 0000000000..881db0c82a --- /dev/null +++ b/examples/bpk-component-layout/box.stories.tsx @@ -0,0 +1,67 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ArgTypes, Title, Markdown } from '@storybook/addon-docs/blocks'; + +import { BpkProvider, BpkBox } from '../../packages/bpk-component-layout'; + +import { + SpacingExample, + RtlSpacingExample, + SizeExample, + ResponsiveExample, + PositionExample, + FlexExample, + GridExample, +} from './box-examples'; + +export default { + title: 'bpk-component-layout/Box', + component: BpkBox, + decorators: [ + (Story: any) => ( + + + + ), + ], + parameters: { + docs: { + page: () => ( + <> + + <ArgTypes exclude={['zoomEnabled']} /> + <Markdown> + Notes: `BpkBox` is the base layout primitive. It exposes a curated, + structural prop surface and tokenised spacing. + </Markdown> + </> + ), + }, + }, +}; + +export const Spacing = () => <SpacingExample />; +export const RtlSpacing = () => <RtlSpacingExample />; +export const Size = () => <SizeExample />; +export const Responsive = () => <ResponsiveExample />; +export const Position = () => <PositionExample />; +export const FlexViaBox = () => <FlexExample />; +export const GridViaBox = () => <GridExample />; + + diff --git a/examples/bpk-component-layout/examples.module.scss b/examples/bpk-component-layout/examples.module.scss new file mode 100644 index 0000000000..6a174327b0 --- /dev/null +++ b/examples/bpk-component-layout/examples.module.scss @@ -0,0 +1,66 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@use '../../packages/bpk-mixins/tokens'; +@use '../../packages/bpk-mixins/typography'; + +.bpk-layout-examples { + &__frame { + padding: tokens.bpk-spacing-base(); + background-color: tokens.$bpk-canvas-contrast-day; + border-radius: tokens.$bpk-border-radius-md; + + // Subtle grid to make layout boundaries easier to see. + background-size: 8px 8px; + + // Outline the root element of each example (not the inner content). + // This avoids needing border props on layout primitives. + > :where(*) { + outline: tokens.$bpk-border-size-sm solid tokens.$bpk-line-day; + outline-offset: tokens.bpk-spacing-sm(); + background-color: tokens.$bpk-canvas-day; + } + } + + // Generic “item boundary” styling for Storybook examples. + // Use this on regular DOM elements (not layout primitives) to avoid exposing + // border props on the layout surface. + &__item { + outline: tokens.$bpk-border-size-sm solid tokens.$bpk-line-day; + outline-offset: 0; + border-radius: tokens.$bpk-border-radius-sm; + background-color: tokens.$bpk-canvas-day; + box-sizing: border-box; + } + + &__outline { + @include typography.bpk-body-default; + + display: block; + width: 100%; + padding: tokens.bpk-spacing-sm(); + + color: tokens.$bpk-text-primary-day; + background-color: tokens.$bpk-surface-highlight-day; + + outline: tokens.$bpk-border-size-sm dashed tokens.$bpk-line-day; + outline-offset: 0; + border-radius: tokens.$bpk-border-radius-sm; + box-sizing: border-box; + } +} diff --git a/examples/bpk-component-layout/flex-examples.tsx b/examples/bpk-component-layout/flex-examples.tsx new file mode 100644 index 0000000000..c5facf5633 --- /dev/null +++ b/examples/bpk-component-layout/flex-examples.tsx @@ -0,0 +1,170 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + BpkBox, + BpkFlex, + BpkSpacing, +} from '../../packages/bpk-component-layout'; + +import Wrapper from './layout-wrapper'; + +import STYLES from './examples.module.scss'; + +export const BpkFlexExample = () => ( + <Wrapper> + <BpkFlex + direction="row" + justify="space-between" + align="center" + padding={BpkSpacing.MD} + > + {[1, 2, 3].map((i) => ( + <BpkBox key={i} padding={BpkSpacing.SM}> + <span className={STYLES['bpk-layout-examples__outline']}> + Flex item {i} + </span> + </BpkBox> + ))} + </BpkFlex> + </Wrapper> +); + +export const BpkFlexDirectionExample = () => ( + <Wrapper> + <BpkFlex direction="column" gap={BpkSpacing.MD} padding={BpkSpacing.MD}> + <BpkBox padding={BpkSpacing.SM}> + <span className={STYLES['bpk-layout-examples__outline']}> + Column item 1 + </span> + </BpkBox> + <BpkBox padding={BpkSpacing.SM}> + <span className={STYLES['bpk-layout-examples__outline']}> + Column item 2 + </span> + </BpkBox> + </BpkFlex> + </Wrapper> +); + +export const BpkFlexWrapExample = () => ( + <Wrapper> + <BpkFlex + wrap="wrap" + gap={BpkSpacing.MD} + padding={BpkSpacing.MD} + width="100%" + > + {[1, 2, 3, 4, 5, 6].map((i) => ( + <BpkBox key={i} padding={BpkSpacing.MD} width="10rem"> + <span className={STYLES['bpk-layout-examples__outline']}> + Wrap Item {i} + </span> + </BpkBox> + ))} + </BpkFlex> + </Wrapper> +); + +export const BpkFlexResponsiveExample = () => ( + <Wrapper> + <BpkFlex + direction={{ mobile: 'column', tablet: 'row' }} + justify={{ mobile: 'flex-start', desktop: 'space-between' }} + gap={BpkSpacing.MD} + padding={BpkSpacing.MD} + > + {[1, 2, 3].map((i) => ( + <BpkBox key={i} padding={BpkSpacing.SM}> + <span className={STYLES['bpk-layout-examples__outline']}> + Responsive Item {i} + </span> + </BpkBox> + ))} + </BpkFlex> + </Wrapper> +); + +export const BpkFlexItemExample = () => ( + <Wrapper> + <BpkFlex gap={BpkSpacing.MD} padding={BpkSpacing.MD} width="100%"> + <BpkFlex + grow={1} + padding={BpkSpacing.MD} + justify="center" + align="center" + > + <BpkBox padding={BpkSpacing.SM} width="100%"> + <span className={STYLES['bpk-layout-examples__outline']}> + Grow: 1 + </span> + </BpkBox> + </BpkFlex> + <BpkFlex + grow={2} + padding={BpkSpacing.MD} + justify="center" + align="center" + > + <BpkBox padding={BpkSpacing.SM} width="100%"> + <span className={STYLES['bpk-layout-examples__outline']}> + Grow: 2 + </span> + </BpkBox> + </BpkFlex> + <BpkFlex + basis="12.5rem" + shrink={0} + padding={BpkSpacing.MD} + justify="center" + align="center" + > + <BpkBox padding={BpkSpacing.SM} width="100%"> + <span className={STYLES['bpk-layout-examples__outline']}> + Basis: 200px, Shrink: 0 + </span> + </BpkBox> + </BpkFlex> + </BpkFlex> + </Wrapper> +); + +export const BpkFlexInlineExample = () => ( + <Wrapper> + <BpkBox padding={BpkSpacing.MD}> + <BpkFlex + inline + gap={BpkSpacing.SM} + padding={BpkSpacing.SM} + > + <BpkBox padding={BpkSpacing.SM}> + <span className={STYLES['bpk-layout-examples__outline']}> + Inline Flex 1 + </span> + </BpkBox> + <BpkBox padding={BpkSpacing.SM}> + <span className={STYLES['bpk-layout-examples__outline']}> + Inline Flex 2 + </span> + </BpkBox> + </BpkFlex> + {' '} + <span>Text adjacent to inline flex</span> + </BpkBox> + </Wrapper> +); diff --git a/examples/bpk-component-layout/flex.stories.tsx b/examples/bpk-component-layout/flex.stories.tsx new file mode 100644 index 0000000000..5bffee2f34 --- /dev/null +++ b/examples/bpk-component-layout/flex.stories.tsx @@ -0,0 +1,45 @@ +import { ArgTypes, Title, Markdown } from '@storybook/addon-docs/blocks'; + +import { BpkFlex, BpkProvider } from '../../packages/bpk-component-layout'; + +import { + BpkFlexExample, + BpkFlexDirectionExample, + BpkFlexWrapExample, + BpkFlexResponsiveExample, + BpkFlexItemExample, + BpkFlexInlineExample, +} from './flex-examples'; + +export default { + title: 'bpk-component-layout/Flex', + component: BpkFlex, + decorators: [ + (Story: any) => ( + <BpkProvider> + <Story /> + </BpkProvider> + ), + ], + parameters: { + docs: { + page: () => ( + <> + <Title /> + <ArgTypes exclude={['zoomEnabled']} /> + <Markdown> + Notes: `BpkFlex` is a layout primitive for flexbox layouts. It + supports responsive values keyed by Backpack breakpoints. + </Markdown> + </> + ), + }, + }, +}; + +export const Default = BpkFlexExample; +export const Direction = BpkFlexDirectionExample; +export const Wrap = BpkFlexWrapExample; +export const Responsive = BpkFlexResponsiveExample; +export const ItemProps = BpkFlexItemExample; +export const Inline = BpkFlexInlineExample; diff --git a/examples/bpk-component-layout/grid-examples.tsx b/examples/bpk-component-layout/grid-examples.tsx new file mode 100644 index 0000000000..42a547fbef --- /dev/null +++ b/examples/bpk-component-layout/grid-examples.tsx @@ -0,0 +1,175 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + BpkBox, + BpkGrid, + BpkGridItem, + BpkSpacing, +} from '../../packages/bpk-component-layout'; + +import Wrapper from './layout-wrapper'; + +import STYLES from './examples.module.scss'; + +export const BpkGridExample = () => ( + <Wrapper> + <BpkGrid + templateColumns="repeat(3, minmax(0, 1fr))" + gap={BpkSpacing.MD} + padding={BpkSpacing.MD} + > + {[1, 2, 3, 4, 5, 6].map((i) => ( + <BpkBox key={i} padding={BpkSpacing.SM}> + <span className={STYLES['bpk-layout-examples__outline']}> + Grid cell {i} + </span> + </BpkBox> + ))} + </BpkGrid> + </Wrapper> +); + +export const BpkGridSpanExample = () => ( + <Wrapper> + <BpkGrid + templateColumns="repeat(3, 1fr)" + gap={BpkSpacing.MD} + padding={BpkSpacing.MD} + > + <BpkBox + gridColumn="span 2" + padding={BpkSpacing.MD} + > + <BpkBox width="100%" padding={BpkSpacing.SM}> + <span className={STYLES['bpk-layout-examples__outline']}> + Box: Span 2 Columns + </span> + </BpkBox> + </BpkBox> + <BpkBox padding={BpkSpacing.MD}> + <span className={STYLES['bpk-layout-examples__outline']}> + Cell 2 + </span> + </BpkBox> + <BpkBox + gridRow="span 2" + padding={BpkSpacing.MD} + > + <BpkBox width="100%" height="100%" padding={BpkSpacing.SM}> + <span className={STYLES['bpk-layout-examples__outline']}> + Box: Span 2 Rows + </span> + </BpkBox> + </BpkBox> + <BpkBox padding={BpkSpacing.MD}> + <span className={STYLES['bpk-layout-examples__outline']}> + Cell 4 + </span> + </BpkBox> + <BpkBox padding={BpkSpacing.MD}> + <span className={STYLES['bpk-layout-examples__outline']}> + Cell 5 + </span> + </BpkBox> + </BpkGrid> + </Wrapper> +); + +export const BpkGridWithItemExample = () => ( + <Wrapper> + <BpkGrid + templateRows="repeat(2, 1fr)" + templateColumns="repeat(5, 1fr)" + gap={BpkSpacing.MD} + padding={BpkSpacing.MD} + minHeight="12.5rem" + > + <BpkGridItem + rowSpan={2} + colSpan={1} + padding={BpkSpacing.SM} + > + <BpkBox width="100%" height="100%" padding={BpkSpacing.SM}> + <span className={STYLES['bpk-layout-examples__outline']}> + rowSpan=2 + </span> + </BpkBox> + </BpkGridItem> + <BpkGridItem + colSpan={2} + padding={BpkSpacing.SM} + > + <BpkBox width="100%" height="100%" padding={BpkSpacing.SM}> + <span className={STYLES['bpk-layout-examples__outline']}> + colSpan=2 + </span> + </BpkBox> + </BpkGridItem> + <BpkGridItem + colSpan={2} + padding={BpkSpacing.SM} + > + <BpkBox width="100%" height="100%" padding={BpkSpacing.SM}> + <span className={STYLES['bpk-layout-examples__outline']}> + colSpan=2 + </span> + </BpkBox> + </BpkGridItem> + + <BpkGridItem + colSpan={4} + padding={BpkSpacing.SM} + > + <BpkBox width="100%" height="100%" padding={BpkSpacing.SM}> + <span className={STYLES['bpk-layout-examples__outline']}> + colSpan=4 + </span> + </BpkBox> + </BpkGridItem> + </BpkGrid> + </Wrapper> +); + +export const BpkGridResponsiveExample = () => ( + <Wrapper> + <BpkGrid + templateColumns={{ + mobile: 'repeat(1, 1fr)', + tablet: 'repeat(2, 1fr)', + desktop: 'repeat(4, 1fr)', + }} + gap={{ + mobile: BpkSpacing.SM, + tablet: BpkSpacing.MD, + desktop: BpkSpacing.LG, + }} + padding={BpkSpacing.MD} + > + {[1, 2, 3, 4].map((i) => ( + <BpkBox key={i} padding={BpkSpacing.SM}> + <BpkBox width="100%" height="100%" padding={BpkSpacing.SM}> + <span className={STYLES['bpk-layout-examples__outline']}> + Responsive Col {i} + </span> + </BpkBox> + </BpkBox> + ))} + </BpkGrid> + </Wrapper> +); diff --git a/examples/bpk-component-layout/grid.stories.tsx b/examples/bpk-component-layout/grid.stories.tsx new file mode 100644 index 0000000000..dda1825d30 --- /dev/null +++ b/examples/bpk-component-layout/grid.stories.tsx @@ -0,0 +1,59 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ArgTypes, Title, Markdown } from '@storybook/addon-docs/blocks'; + +import { BpkGrid, BpkProvider } from '../../packages/bpk-component-layout'; + +import { + BpkGridExample, + BpkGridSpanExample, + BpkGridWithItemExample, + BpkGridResponsiveExample, +} from './grid-examples'; + +export default { + title: 'bpk-component-layout/Grid', + component: BpkGrid, + decorators: [ + (Story: any) => ( + <BpkProvider> + <Story /> + </BpkProvider> + ), + ], + parameters: { + docs: { + page: () => ( + <> + <Title /> + <ArgTypes exclude={['zoomEnabled']} /> + <Markdown> + Notes: `BpkGrid` and `BpkGridItem` are layout primitives for CSS + grid layouts. + </Markdown> + </> + ), + }, + }, +}; + +export const Default = BpkGridExample; +export const Span = BpkGridSpanExample; +export const WithItem = BpkGridWithItemExample; +export const Responsive = BpkGridResponsiveExample; diff --git a/examples/bpk-component-layout/layout-wrapper.tsx b/examples/bpk-component-layout/layout-wrapper.tsx new file mode 100644 index 0000000000..41d7f17e0d --- /dev/null +++ b/examples/bpk-component-layout/layout-wrapper.tsx @@ -0,0 +1,32 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ReactNode } from 'react'; + +import { BpkProvider } from '../../packages/bpk-component-layout'; + +import STYLES from './examples.module.scss'; + +const Wrapper = ({ children }: { children: ReactNode }) => ( + <BpkProvider> + <div className={STYLES['bpk-layout-examples__frame']}>{children}</div> + </BpkProvider> +); + +export default Wrapper; + diff --git a/examples/bpk-component-layout/stack-examples.tsx b/examples/bpk-component-layout/stack-examples.tsx new file mode 100644 index 0000000000..8351ecb81e --- /dev/null +++ b/examples/bpk-component-layout/stack-examples.tsx @@ -0,0 +1,193 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BpkButtonV2, BUTTON_TYPES } from '../../packages/bpk-component-button'; +import { + BpkBox, + BpkSpacing, + BpkStack, + BpkVStack, + BpkHStack, +} from '../../packages/bpk-component-layout'; + +import Wrapper from './layout-wrapper'; + +import STYLES from './examples.module.scss'; + +const Item = ({ label }: { label: string }) => ( + <div className={STYLES['bpk-layout-examples__item']}> + <BpkBox padding={BpkSpacing.SM} minHeight="3rem"> + <span className={STYLES['bpk-layout-examples__outline']}>{label}</span> + </BpkBox> + </div> +); +// Content block component using BpkBox +const ContentBlock = ({ + description, + title, +}: { + title: string; + description: string; +}) => ( + <div className={STYLES['bpk-layout-examples__item']}> + <BpkBox padding={BpkSpacing.MD} minHeight="6rem"> + <BpkVStack gap={BpkSpacing.SM}> + <BpkBox fontWeight="bold">{title}</BpkBox> + <BpkBox>{description}</BpkBox> + </BpkVStack> + </BpkBox> + </div> +); + +// 1) Vertical stack of content blocks +export const VerticalContentBlocksExample = () => ( + <Wrapper> + <BpkVStack gap={BpkSpacing.LG}> + <ContentBlock + title="Content Block 1" + description="Vertical stack with large gap" + /> + <ContentBlock + title="Content Block 2" + description="Vertical stack with large gap" + /> + <ContentBlock + title="Content Block 3" + description="Vertical stack with large gap" + /> + </BpkVStack> + </Wrapper> +); + +// 2) Horizontal stack of buttons +export const HorizontalButtonsExample = () => ( + <Wrapper> + <BpkHStack gap={BpkSpacing.MD}> + <BpkButtonV2 type={BUTTON_TYPES.primary}>Primary</BpkButtonV2> + <BpkButtonV2 type={BUTTON_TYPES.secondary}>Secondary</BpkButtonV2> + <BpkButtonV2 type={BUTTON_TYPES.link}>Link</BpkButtonV2> + <BpkButtonV2 type={BUTTON_TYPES.destructive}>Delete</BpkButtonV2> + </BpkHStack> + </Wrapper> +); + +// 3) Nested Stack example +export const NestedStackExample = () => ( + <Wrapper> + <BpkVStack gap={BpkSpacing.LG}> + <ContentBlock + title="Main Section" + description="Vertical stack with large gap containing nested stacks" + /> + <BpkHStack gap={BpkSpacing.MD} align="center"> + <BpkVStack gap={BpkSpacing.SM} justify="center"> + <ContentBlock + title="Nested Column 1" + description="Vertical stack with small gap and justify center" + /> + <ContentBlock + title="Nested Column 1 - Item 2" + description="Vertical stack with small gap and justify center" + /> + </BpkVStack> + <BpkVStack gap={BpkSpacing.SM} justify="center"> + <ContentBlock + title="Nested Column 2" + description="Vertical stack with small gap and justify center" + /> + <BpkHStack gap={BpkSpacing.SM}> + <BpkButtonV2 type={BUTTON_TYPES.secondary}>Horizontal stack with small gap</BpkButtonV2> + <BpkButtonV2 type={BUTTON_TYPES.secondary}>Horizontal stack with small gap</BpkButtonV2> + </BpkHStack> + </BpkVStack> + </BpkHStack> + </BpkVStack> + </Wrapper> +); + +// 4) Default vertical stack +export const StackDefaultExample = () => ( + <Wrapper> + <BpkStack gap={BpkSpacing.LG}> + <Item label="Default Stack Item 1" /> + <Item label="Default Stack Item 2" /> + <Item label="Default Stack Item 3" /> + </BpkStack> + </Wrapper> +); + +// 5) Horizontal stack (row) +export const StackHorizontalExample = () => ( + <Wrapper> + <BpkStack gap={BpkSpacing.MD} direction="row"> + <Item label="Horizontal Item 1" /> + <Item label="Horizontal Item 2" /> + <Item label="Horizontal Item 3" /> + </BpkStack> + </Wrapper> +); + +// 6) HStack (row with center align) +export const HStackExample = () => ( + <Wrapper> + <BpkHStack gap={BpkSpacing.SM}> + <Item label="HStack Item 1" /> + <Item label="HStack Item 2" /> + <Item label="HStack Item 3" /> + </BpkHStack> + </Wrapper> +); + +// 7) VStack (column) +export const VStackExample = () => ( + <Wrapper> + <BpkVStack gap={BpkSpacing.SM}> + <Item label="VStack Item 1" /> + <Item label="VStack Item 2" /> + <Item label="VStack Item 3" /> + </BpkVStack> + </Wrapper> +); + +// 8) Responsive direction +export const ResponsiveDirectionExample = () => ( + <Wrapper> + <BpkStack + gap={{ + "small-mobile": BpkSpacing.SM, + tablet: BpkSpacing.LG, + desktop: BpkSpacing.XL, + }} + width={{ + "small-mobile": '100%', + tablet: '50%', + desktop: '25%', + }} + direction={{ + "small-mobile": 'column', + mobile: 'column', + tablet: 'row', + desktop: 'row', + }} + > + <Item label="Item 1 - vertically arranged in mobile, horizontally in tablet and above" /> + <Item label="Item 2 - total width 100% on mobile, 50% on tablet, 25% on desktop" /> + <Item label="Item 3 - small gap on mobile, large gap on tablet, xl on desktop" /> + </BpkStack> + </Wrapper> +); diff --git a/examples/bpk-component-layout/stack.stories.tsx b/examples/bpk-component-layout/stack.stories.tsx new file mode 100644 index 0000000000..8b85e89a81 --- /dev/null +++ b/examples/bpk-component-layout/stack.stories.tsx @@ -0,0 +1,68 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ArgTypes, Title, Markdown } from '@storybook/addon-docs/blocks'; + +import { BpkProvider, BpkStack } from '../../packages/bpk-component-layout'; + +import { + StackDefaultExample, + StackHorizontalExample, + HStackExample, + VStackExample, + VerticalContentBlocksExample, + ResponsiveDirectionExample, + HorizontalButtonsExample, + NestedStackExample, +} from './stack-examples'; + +export default { + title: 'bpk-component-layout/Stack', + component: BpkStack, + decorators: [ + (Story: any) => ( + <BpkProvider> + <Story /> + </BpkProvider> + ), + ], + parameters: { + docs: { + page: () => ( + <> + <Title /> + <ArgTypes exclude={['zoomEnabled']} /> + <Markdown> + Notes: `BpkStack`, `BpkHStack` and `BpkVStack` are layout primitives + for stacking items with tokenised gaps. + </Markdown> + </> + ), + }, + }, +}; + +export const VerticalContentBlocks = () => <VerticalContentBlocksExample />; +export const HorizontalButtons = () => <HorizontalButtonsExample />; +export const NestedStack = () => <NestedStackExample />; +export const StackDefault = () => <StackDefaultExample />; +export const StackHorizontal = () => <StackHorizontalExample />; +export const StackH = () => <HStackExample />; +export const StackV = () => <VStackExample />; +export const StackResponsive = () => <ResponsiveDirectionExample />; + diff --git a/examples/bpk-component-layout/stories.tsx b/examples/bpk-component-layout/stories.tsx new file mode 100644 index 0000000000..e797d5c9a0 --- /dev/null +++ b/examples/bpk-component-layout/stories.tsx @@ -0,0 +1,122 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + BpkBox, + BpkFlex, + BpkGrid, + BpkGridItem, + BpkProvider, + BpkStack, +} from '../../packages/bpk-component-layout'; + +import { + FlexExample, + GridExample, + PositionExample, + ResponsiveExample, + RtlSpacingExample, + SizeExample, + SpacingExample, +} from './box-examples'; +import { + BpkFlexDirectionExample, + BpkFlexExample, + BpkFlexInlineExample, + BpkFlexItemExample, + BpkFlexResponsiveExample, + BpkFlexWrapExample, +} from './flex-examples'; +import { + BpkGridExample, + BpkGridResponsiveExample, + BpkGridSpanExample, + BpkGridWithItemExample, +} from './grid-examples'; +import { + HStackExample, + HorizontalButtonsExample, + NestedStackExample, + ResponsiveDirectionExample, + StackDefaultExample, + StackHorizontalExample, + VStackExample, + VerticalContentBlocksExample, +} from './stack-examples'; + +export default { + title: 'bpk-component-layout', + component: BpkProvider, + subcomponents: { + BpkBox, + BpkFlex, + BpkGrid, + BpkGridItem, + BpkStack, + }, + parameters: { + docs: { + // Use Storybook's default docs rendering. + page: undefined, + }, + }, +}; + +export const VisualTest = () => ( + <> + {/* Box examples */} + <SpacingExample /> + <RtlSpacingExample /> + <SizeExample /> + <ResponsiveExample /> + <PositionExample /> + <FlexExample /> + <GridExample /> + + {/* Flex examples */} + <BpkFlexExample /> + <BpkFlexDirectionExample /> + <BpkFlexWrapExample /> + <BpkFlexResponsiveExample /> + <BpkFlexItemExample /> + <BpkFlexInlineExample /> + + {/* Grid examples */} + <BpkGridExample /> + <BpkGridSpanExample /> + <BpkGridWithItemExample /> + <BpkGridResponsiveExample /> + + {/* Stack examples */} + <VerticalContentBlocksExample /> + <HorizontalButtonsExample /> + <NestedStackExample /> + <StackDefaultExample /> + <StackHorizontalExample /> + <HStackExample /> + <VStackExample /> + <ResponsiveDirectionExample /> + </> +); + +export const VisualTestWithZoom = { + render: VisualTest, + args: { + zoomEnabled: true, + }, +}; diff --git a/packages/bpk-component-layout/README.md b/packages/bpk-component-layout/README.md new file mode 100644 index 0000000000..2ad890319f --- /dev/null +++ b/packages/bpk-component-layout/README.md @@ -0,0 +1,159 @@ +# bpk-component-layout + +> Backpack layout components and tokens. + +## Overview + +`bpk-component-layout` provides **layout-only** primitives and layout tokens for Backpack: + +- `BpkProvider` – wraps your app or stories to provide the Backpack layout system. +- `BpkBox` – a low‑level layout container for spacing, sizing and structural composition. +- `BpkFlex` – a flexbox layout primitive with an ergonomic, responsive API. +- `BpkGrid` / `BpkGridItem` – grid layout primitives for container + item placement. +- `BpkStack` / `BpkHStack` / `BpkVStack` – stack layout primitives with tokenised gaps. +- Typed layout tokens – spacing and size. + +Under the hood, this package is implemented as a **facade over a layout system** and generates **CSS at runtime**, but the public API is Backpack‑flavoured and token‑driven. Consumers should only interact with the Backpack components and tokens described here, not with the underlying system. + +## Usage + +### BpkProvider + +`BpkProvider` must wrap any layout components so that they can resolve Backpack tokens correctly: + +```tsx +import { BpkProvider } from '@skyscanner/backpack-web/bpk-component-layout'; + +export default function App({ children }) { + return <BpkProvider>{children}</BpkProvider>; +} +``` + +### BpkBox + +`BpkBox` is a layout container that exposes a **restricted, tokenised** prop API. It is intended for spacing, sizing and structural layout only – not for typography, colors, borders or complex interaction. + +```tsx +import { + BpkBox, + BpkProvider, + BpkSpacing, +} from '@skyscanner/backpack-web/bpk-component-layout'; + +export default function Example() { + return ( + <BpkProvider> + <BpkBox + padding={BpkSpacing.MD} + margin={BpkSpacing.MD} + width="50%" + minHeight="6rem" + data-testid="layout-box" + > + Layout content + </BpkBox> + </BpkProvider> + ); +} +``` + +## Layout tokens and props + +The layout API is intentionally limited and strongly typed. The main groups are: + +- **Spacing** – `padding`, `margin`, logical props (`marginStart`, `marginEnd`, `paddingInline`), `gap`: + - Values: `BpkSpacing` tokens (`BpkSpacing.XS`, `BpkSpacing.SM`, `BpkSpacing.MD`, …) or percentages (e.g. `'50%'`). +- **Size** – `width`, `height`, `minWidth`, `minHeight`, `maxWidth`, `maxHeight`: + - Values: rem strings (e.g. `'6rem'`), percentages (e.g. `'50%'`) or semantic values (`'auto' | 'full' | 'fit-content'`). +- **Position** – `top`, `right`, `bottom`, `left`: + - Values: rem strings (e.g. `'1rem'`) or percentages (e.g. `'50%'`). +- **Testing attributes** – `data-testid`, `data-cy` for automation and testing. + +In addition, `BpkBox` forwards through a set of **flexbox and grid layout props** from the underlying layout system, for example: + +- `display="flex"`, `flexDirection`, `justifyContent`, `alignItems`, `flexWrap` +- `display="grid"`, `gridTemplateColumns`, `gridTemplateRows`, `gap` + +In addition, `BpkBox` re‑introduces a **minimal interaction surface**: + +- `onClick`, `onFocus`, `onBlur` + +No other event handlers are exposed on layout components. + +## Component roles + +- **`BpkProvider`**: Provides the runtime layout system (tokens + breakpoints) for all layout primitives. Wrap your app (or Storybook) with it. +- **`BpkBox`**: The base structural primitive. Use it for spacing/sizing/positioning and for composing simple flex/grid layouts via `display` + related props. +- **`BpkFlex`**: A dedicated flex container primitive. Prefer this when you want a clear, ergonomic flex API (`direction/align/justify/wrap/...`) with Backpack responsive values. +- **`BpkGrid` / `BpkGridItem`**: Dedicated grid primitives. Prefer these for grid layout composition; use `BpkGridItem` when you want explicit spans/placement. +- **`BpkStack` / `BpkHStack` / `BpkVStack`**: Dedicated stack primitives. Prefer these when you want consistent tokenised gaps and the simplest stacking API. + +### Responsive values + +Layout props support **responsive overrides keyed by Backpack breakpoints**. +Instead of Chakra’s default `sm/md/lg` keys or array syntax, use Backpack breakpoint tokens: + +- `small-mobile` +- `mobile` +- `small-tablet` +- `tablet` +- `desktop` + +Example: + +```tsx +<BpkBox + padding={{ + mobile: BpkSpacing.SM, + tablet: BpkSpacing.MD, + desktop: BpkSpacing.LG, + }} +/> +``` + +Under the hood these keys are mapped to Chakra’s breakpoint keys (`base`, `sm`, `md`, `lg`, `xl`, `2xl`) before generating CSS. + +> **Important:** Responsive values are **mobile-first (min-width)**. A key like `mobile` means “apply from the mobile breakpoint and above” (it is not an “only within mobile” range). + +> **Note:** Array-based responsive values (e.g. `padding={[...values]}`) are **not supported**. +> Passing an array will be ignored and will log a warning in non‑production environments. + +### Responsive support (high-level) + +- **Supported broadly (recommended)**: container-level layout props and spacing tokens (e.g. `padding/margin/gap`, `width/height`, `direction/templateColumns`, etc.) +- **Not automatically universal**: item placement props can be more complex; add responsive support based on real usage (PoC-driven). + +In particular: + +- **`BpkBox`** supports Backpack responsive values for: + - **Spacing/size/position** props (tokenised): `padding`, `margin`, `gap`, `width/height`, `top/right/bottom/left`, etc. + - **Key structural layout props**: `display`, flex container/item props, and grid container props (via Backpack breakpoint keys). +- **`BpkGridItem`** placement props like `colSpan/rowSpan` are currently scalar (non-responsive) and should be extended only when needed. + +## Constraints and design principles + +To keep layout predictable, performant and consistent with Backpack: + +- **No arbitrary class names** – `className` is not supported on layout components; use layout props and tokens instead. +- **No inline styles** – `style` is not supported on layout components to avoid ad‑hoc overrides of the design system. +- **No shorthand props** – Chakra shorthands such as `p`, `m`, `w`, `h`, `bg`, `rounded`, `shadow` are not exposed on Backpack layout components. +- **No colors, borders, radii or shadows** – visual props such as `color`, `backgroundColor`, `borderColor`, `borderWidth`, `borderRadius`, `boxShadow` are not part of the layout surface. +- **No composite border shorthands** – props like `border`, `borderX`, `borderInline`, `borderBlock` are not supported. +- **No typography props** – font family/size/line height/etc. should come from dedicated text components, not from layout primitives. +- **No transition/transform props** – layout components are purely structural; animations and transforms should live in higher‑level components. +- **Token‑driven spacing** – spacing props only accept Backpack spacing tokens (or percentages) to keep design consistent and avoid magic numbers. +- **Breakpoint‑driven responsiveness** – responsive overrides must use Backpack breakpoint keys in object form; array syntax is intentionally disabled. + +## Storybook and examples + +This package includes Storybook examples under `examples/bpk-component-layout` showing: + +- Basic spacing +- RTL‑friendly spacing (`marginInline`, `paddingInline`) +- Size props +- Position props +- Flexbox layout +- Grid layout +- Responsive layout using Backpack breakpoints + +Use these examples as a reference for how to compose layout props and tokens. As new layout components (e.g. `BpkFlex`, `BpkGrid`, `BpkStack`) are added, they should follow the same prop and constraints model. diff --git a/packages/bpk-component-layout/index.ts b/packages/bpk-component-layout/index.ts new file mode 100644 index 0000000000..b92453a0fc --- /dev/null +++ b/packages/bpk-component-layout/index.ts @@ -0,0 +1,54 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { BpkProvider } from './src/BpkProvider'; +export { BpkBox } from './src/BpkBox'; +export { BpkFlex } from './src/BpkFlex'; +export { BpkGrid } from './src/BpkGrid'; +export { BpkGridItem } from './src/BpkGridItem'; + +export type { BpkProviderProps } from './src/BpkProvider'; +export type { BpkBoxProps } from './src/BpkBox'; +export type { BpkFlexProps } from './src/BpkFlex'; +export type { BpkGridProps } from './src/BpkGrid'; +export type { BpkGridItemProps } from './src/BpkGridItem'; +export { BpkStack, BpkHStack, BpkVStack } from './src/BpkStack'; +export type { BpkStackProps } from './src/BpkStack'; + +export type { + BpkCommonLayoutProps, + BpkBoxSpecificProps, + BpkFlexSpecificProps, + BpkGridSpecificProps, + BpkGridItemSpecificProps, +} from './src/types'; +export type { BpkStackSpecificProps } from './src/types'; + +// Export token types and utilities +export type { + BpkSpacingToken, + BpkBreakpointToken, + BpkSpacingValue, + BpkBreakpointValue, +} from './src/tokens'; +export { + BpkSpacing, + BpkBreakpoint, + isValidSpacingValue, + isPercentage, +} from './src/tokens'; diff --git a/packages/bpk-component-layout/src/BpkBox-test.tsx b/packages/bpk-component-layout/src/BpkBox-test.tsx new file mode 100644 index 0000000000..39e88e202d --- /dev/null +++ b/packages/bpk-component-layout/src/BpkBox-test.tsx @@ -0,0 +1,97 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render, fireEvent } from '@testing-library/react'; + +import '@testing-library/jest-dom'; + +import { BpkBox } from './BpkBox'; +import { BpkProvider } from './BpkProvider'; +import { BpkSpacing } from './tokens'; + +describe('BpkBox', () => { + it('renders children content', () => { + const { getByText } = render( + <BpkProvider> + <BpkBox padding={BpkSpacing.MD}>Content</BpkBox> + </BpkProvider>, + ); + + expect(getByText('Content')).toBeInTheDocument(); + }); + + it('ignores className prop to prevent style overrides', () => { + const { container } = render( + <BpkProvider> + {/* @ts-expect-error className is intentionally not part of the public API */} + <BpkBox className="custom-class" padding={BpkSpacing.MD}> + Content + </BpkBox> + </BpkProvider>, + ); + + // Chakra renders a div as the Box root element + const div = container.querySelector('div'); + expect(div).not.toHaveClass('custom-class'); + }); + + it('applies layout props via Backpack tokens', () => { + const { container } = render( + <BpkProvider> + <BpkBox padding={BpkSpacing.MD}> + Token box + </BpkBox> + </BpkProvider>, + ); + + const div = container.querySelector('div'); + expect(div).toBeInTheDocument(); + // We don't assert exact styles because Chakra generates classes & vars, + // but we at least assert that the element rendered successfully. + }); + + it('supports basic interaction props: onClick, onFocus, onBlur', () => { + const handleClick = jest.fn(); + const handleFocus = jest.fn(); + const handleBlur = jest.fn(); + + const { getByText } = render( + <BpkProvider> + <BpkBox + padding={BpkSpacing.MD} + onClick={handleClick} + onFocus={handleFocus} + onBlur={handleBlur} + > + Clickable + </BpkBox> + </BpkProvider>, + ); + + const element = getByText('Clickable'); + + fireEvent.focus(element); + expect(handleFocus).toHaveBeenCalledTimes(1); + + fireEvent.click(element); + expect(handleClick).toHaveBeenCalledTimes(1); + + fireEvent.blur(element); + expect(handleBlur).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/bpk-component-layout/src/BpkBox.tsx b/packages/bpk-component-layout/src/BpkBox.tsx new file mode 100644 index 0000000000..9024c99d2b --- /dev/null +++ b/packages/bpk-component-layout/src/BpkBox.tsx @@ -0,0 +1,30 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Box } from '@chakra-ui/react'; + +import { processBpkComponentProps } from './tokenUtils'; + +import type { BpkBoxProps } from './types'; + +export const BpkBox = ({ children, ...props }: BpkBoxProps) => { + const processedProps = processBpkComponentProps(props, { component: 'BpkBox' }); + return <Box {...processedProps}>{children}</Box>; +}; + +export type { BpkBoxProps }; diff --git a/packages/bpk-component-layout/src/BpkFlex-test.tsx b/packages/bpk-component-layout/src/BpkFlex-test.tsx new file mode 100644 index 0000000000..fd1875698a --- /dev/null +++ b/packages/bpk-component-layout/src/BpkFlex-test.tsx @@ -0,0 +1,68 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom'; + +import { BpkFlex } from './BpkFlex'; +import { BpkProvider } from './BpkProvider'; +import { BpkSpacing } from './tokens'; + +describe('BpkFlex', () => { + it('renders children content', () => { + const { getByText } = render( + <BpkProvider> + <BpkFlex>Content</BpkFlex> + </BpkProvider>, + ); + expect(getByText('Content')).toBeInTheDocument(); + }); + + it('accepts flex props: direction, justify, align, wrap, gap', () => { + const { container } = render( + <BpkProvider> + <BpkFlex + direction="column" + justify="center" + align="center" + wrap="wrap" + gap={BpkSpacing.MD} + > + Content + </BpkFlex> + </BpkProvider>, + ); + expect(container.firstChild).toBeInTheDocument(); + expect(container.firstChild).toHaveStyle('flex-direction: column'); + expect(container.firstChild).toHaveStyle('justify-content: center'); + expect(container.firstChild).toHaveStyle('align-items: center'); + expect(container.firstChild).toHaveStyle('flex-wrap: wrap'); + expect(container.firstChild).toHaveStyle(`gap: .5rem`); + }); + + it('supports responsive direction', () => { + const { container } = render( + <BpkProvider> + <BpkFlex direction={{ mobile: 'column', tablet: 'row' }}> + Content + </BpkFlex> + </BpkProvider>, + ); + expect(container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/bpk-component-layout/src/BpkFlex.tsx b/packages/bpk-component-layout/src/BpkFlex.tsx new file mode 100644 index 0000000000..12b5b33873 --- /dev/null +++ b/packages/bpk-component-layout/src/BpkFlex.tsx @@ -0,0 +1,62 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Flex } from '@chakra-ui/react'; + +import { + processBpkComponentProps, +} from './tokenUtils'; + +import type { BpkFlexProps } from './types'; + +export const BpkFlex = ({ + align, + basis, + children, + direction, + grow, + inline, + justify, + shrink, + wrap, + ...props +}: BpkFlexProps) => { + const processedProps = processBpkComponentProps(props, { + component: 'BpkFlex', + responsiveProps: { + flexDirection: direction, + justifyContent: justify, + alignItems: align, + flexWrap: wrap, + flexGrow: grow, + flexShrink: shrink, + flexBasis: basis, + }, + }); + + return ( + <Flex + {...processedProps} + display={inline ? 'inline-flex' : undefined} + > + {children} + </Flex> + ); +}; + +export type { BpkFlexProps }; diff --git a/packages/bpk-component-layout/src/BpkGrid-test.tsx b/packages/bpk-component-layout/src/BpkGrid-test.tsx new file mode 100644 index 0000000000..1638f6be0b --- /dev/null +++ b/packages/bpk-component-layout/src/BpkGrid-test.tsx @@ -0,0 +1,49 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom'; + +import { BpkGrid } from './BpkGrid'; +import { BpkProvider } from './BpkProvider'; +import { BpkSpacing } from './tokens'; + +describe('BpkGrid', () => { + it('renders children content', () => { + const { getByText } = render( + <BpkProvider> + <BpkGrid>Content</BpkGrid> + </BpkProvider>, + ); + expect(getByText('Content')).toBeInTheDocument(); + }); + + it('accepts grid props: justify, align, gap', () => { + const { container } = render( + <BpkProvider> + <BpkGrid justify="center" align="center" gap={BpkSpacing.MD}> + Content + </BpkGrid> + </BpkProvider>, + ); + expect(container.firstChild).toBeInTheDocument(); + expect(container.firstChild).toHaveStyle('justify-content: center'); + expect(container.firstChild).toHaveStyle('align-items: center'); + expect(container.firstChild).toHaveStyle('gap: .5rem'); + }); +}); diff --git a/packages/bpk-component-layout/src/BpkGrid.tsx b/packages/bpk-component-layout/src/BpkGrid.tsx new file mode 100644 index 0000000000..9e762719dd --- /dev/null +++ b/packages/bpk-component-layout/src/BpkGrid.tsx @@ -0,0 +1,66 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Grid } from '@chakra-ui/react'; + +import { processBpkComponentProps } from './tokenUtils'; + +import type { BpkGridProps } from './types'; + +export const BpkGrid = ({ + align, + autoColumns, + autoFlow, + autoRows, + children, + column, + inline, + justify, + row, + templateAreas, + templateColumns, + templateRows, + ...props +}: BpkGridProps) => { + const processedProps = processBpkComponentProps(props, { + component: 'BpkGrid', + responsiveProps: { + justifyContent: justify, + alignItems: align, + gridTemplateColumns: templateColumns, + gridTemplateRows: templateRows, + gridTemplateAreas: templateAreas, + gridAutoFlow: autoFlow, + gridAutoRows: autoRows, + gridAutoColumns: autoColumns, + gridColumn: column, + gridRow: row, + }, + }); + + return ( + <Grid + {...processedProps} + display={inline ? 'inline-grid' : undefined} + > + {children} + </Grid> + ); +}; + +export type { BpkGridProps }; diff --git a/packages/bpk-component-layout/src/BpkGridItem-test.tsx b/packages/bpk-component-layout/src/BpkGridItem-test.tsx new file mode 100644 index 0000000000..cb7e2919a4 --- /dev/null +++ b/packages/bpk-component-layout/src/BpkGridItem-test.tsx @@ -0,0 +1,62 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom'; + +import { BpkGridItem } from './BpkGridItem'; +import { BpkProvider } from './BpkProvider'; +import { BpkSpacing } from './tokens'; + +describe('BpkGridItem', () => { + it('renders its children', () => { + const { getByText } = render( + <BpkProvider> + <BpkGridItem> + <span>Grid item</span> + </BpkGridItem> + </BpkProvider>, + ); + + expect(getByText('Grid item')).toBeInTheDocument(); + }); + + it('accepts grid span props', () => { + const { container } = render( + <BpkProvider> + <BpkGridItem colSpan={2} rowSpan={3}> + Spanning item + </BpkGridItem> + </BpkProvider>, + ); + + expect(container.firstChild).toBeInTheDocument(); + expect(container.firstChild).toHaveStyle('grid-column: span 2/span 2'); + expect(container.firstChild).toHaveStyle('grid-row: span 3/span 3'); + }); + + it('supports Backpack spacing tokens', () => { + const { container } = render( + <BpkProvider> + <BpkGridItem padding={BpkSpacing.MD}>Spacing item</BpkGridItem> + </BpkProvider>, + ); + + expect(container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/packages/bpk-component-layout/src/BpkGridItem.tsx b/packages/bpk-component-layout/src/BpkGridItem.tsx new file mode 100644 index 0000000000..9938cf09d8 --- /dev/null +++ b/packages/bpk-component-layout/src/BpkGridItem.tsx @@ -0,0 +1,55 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { GridItem } from '@chakra-ui/react'; + +import { processBpkProps } from './tokenUtils'; + +import type { BpkGridItemProps } from './types'; + +export const BpkGridItem = ({ + area, + children, + colEnd, + colSpan, + colStart, + rowEnd, + rowSpan, + rowStart, + ...props +}: BpkGridItemProps) => { + const processedProps = processBpkProps(props); + + return ( + <GridItem + {...processedProps} + area={area} + colEnd={colEnd} + colStart={colStart} + colSpan={colSpan} + rowEnd={rowEnd} + rowStart={rowStart} + rowSpan={rowSpan} + > + {children} + </GridItem> + ); +}; + +export type { BpkGridItemProps }; + diff --git a/packages/bpk-component-layout/src/BpkProvider-test.tsx b/packages/bpk-component-layout/src/BpkProvider-test.tsx new file mode 100644 index 0000000000..9b34dadfb4 --- /dev/null +++ b/packages/bpk-component-layout/src/BpkProvider-test.tsx @@ -0,0 +1,49 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render } from '@testing-library/react'; + +import '@testing-library/jest-dom'; + +import { BpkBox } from './BpkBox'; +import { BpkProvider } from './BpkProvider'; +import { BpkSpacing } from './tokens'; + +describe('BpkProvider', () => { + it('renders children inside Chakra system without crashing', () => { + const { getByText } = render( + <BpkProvider> + <BpkBox padding={BpkSpacing.MD}> + Layout content + </BpkBox> + </BpkProvider>, + ); + + expect(getByText('Layout content')).toBeInTheDocument(); + }); + + it('can render plain DOM children', () => { + const { getByText } = render( + <BpkProvider> + <div>Plain child</div> + </BpkProvider>, + ); + + expect(getByText('Plain child')).toBeInTheDocument(); + }); +}); diff --git a/packages/bpk-component-layout/src/BpkProvider.tsx b/packages/bpk-component-layout/src/BpkProvider.tsx new file mode 100644 index 0000000000..20cd2a95fe --- /dev/null +++ b/packages/bpk-component-layout/src/BpkProvider.tsx @@ -0,0 +1,46 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ReactNode } from 'react'; + +import { ChakraProvider, createSystem, defaultConfig } from '@chakra-ui/react'; + +import { createBpkConfig } from './theme'; + +export interface BpkProviderProps { + children: ReactNode; +} + +/** + * Creates a Chakra UI system with Backpack token mappings + * Chakra UI 3.0 uses `createSystem` with `defaultConfig` and custom config + */ +const bpkSystem = createSystem(defaultConfig, createBpkConfig()); + +/** + * BpkProvider - Provides Chakra UI context for Backpack layout components + * + * Chakra UI 3.0 requires the `value` prop to be set to a system object. + * We create a custom system with Backpack tokens using createSystem. + * + * @param {BpkProviderProps} props - The provider props. + * @returns {JSX.Element} The provider wrapping its children with Chakra context. + */ +export const BpkProvider = ({ children }: BpkProviderProps): JSX.Element => ( + <ChakraProvider value={bpkSystem}>{children}</ChakraProvider> +); diff --git a/packages/bpk-component-layout/src/BpkStack-test.tsx b/packages/bpk-component-layout/src/BpkStack-test.tsx new file mode 100644 index 0000000000..98cb4e47b2 --- /dev/null +++ b/packages/bpk-component-layout/src/BpkStack-test.tsx @@ -0,0 +1,124 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render } from '@testing-library/react'; + +import '@testing-library/jest-dom'; + +import { BpkProvider } from './BpkProvider'; +import { BpkStack, BpkHStack, BpkVStack } from './BpkStack'; +import { BpkSpacing } from './tokens'; + +describe('BpkStack', () => { + it('renders children content', () => { + const { getByText } = render( + <BpkProvider> + <BpkStack gap={BpkSpacing.MD}> + <div>Child 1</div> + <div>Child 2</div> + </BpkStack> + </BpkProvider>, + ); + + expect(getByText('Child 1')).toBeInTheDocument(); + expect(getByText('Child 2')).toBeInTheDocument(); + }); + + describe('BpkHStack', () => { + it('defaults to row direction', () => { + const { container } = render( + <BpkProvider> + <BpkHStack gap={BpkSpacing.MD}> + <div>Child 1</div> + <div>Child 2</div> + </BpkHStack> + </BpkProvider>, + ); + + // Chakra's HStack uses direction="row" and aligns items "center" by default + const stack = container.firstChild; + expect(stack).toHaveStyle('flex-direction: row'); + expect(stack).toHaveStyle('align-items: center'); + }); + }); + + describe('BpkVStack', () => { + it('defaults to column direction', () => { + const { container } = render( + <BpkProvider> + <BpkVStack gap={BpkSpacing.MD}> + <div>Child 1</div> + <div>Child 2</div> + </BpkVStack> + </BpkProvider>, + ); + + // Chakra's VStack uses direction="column" and aligns items "center" by default + const stack = container.firstChild; + expect(stack).toHaveStyle('flex-direction: column'); + expect(stack).toHaveStyle('align-items: center'); + }); + }); + + describe('BpkStack Props', () => { + it('accepts valid gap tokens', () => { + const { container } = render( + <BpkProvider> + <BpkStack gap={BpkSpacing.LG}> + <div>Child 1</div> + </BpkStack> + </BpkProvider>, + ); + + // We can check if the style attribute or class reflects the gap. + // Chakra usually applies 'gap' for Stack gap. + // The exact value depends on the theme, but it should be present. + const stack = container.firstChild; + // bpk-gap-lg usually maps to 1.5rem (24px) + expect(stack).toHaveStyle('gap: 1.5rem'); + }); + + it('supports align and justify props', () => { + const { container } = render( + <BpkProvider> + <BpkStack align="center" justify="space-between" gap={BpkSpacing.MD}> + <div>Child 1</div> + </BpkStack> + </BpkProvider>, + ); + + const stack = container.firstChild; + expect(stack).toHaveStyle('align-items: center'); + expect(stack).toHaveStyle('justify-content: space-between'); + }); + + it('filters out invalid props (e.g. className)', () => { + const { container } = render( + <BpkProvider> + {/* @ts-expect-error className is intentionally not part of the public API */} + <BpkStack className="forbidden-class" gap={BpkSpacing.MD}> + <div>Child</div> + </BpkStack> + </BpkProvider>, + ); + + const stack = container.firstChild; + expect(stack).not.toHaveClass('forbidden-class'); + }); + }); +}); diff --git a/packages/bpk-component-layout/src/BpkStack.constant.ts b/packages/bpk-component-layout/src/BpkStack.constant.ts new file mode 100644 index 0000000000..148bf181a9 --- /dev/null +++ b/packages/bpk-component-layout/src/BpkStack.constant.ts @@ -0,0 +1,28 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// these options align with Chakra's StackOption excluding separator +// TODO: add separator to Stack +const StackOptionKeys = [ + 'align', + 'justify', + 'wrap', + 'direction', +] as const; + +export default StackOptionKeys; diff --git a/packages/bpk-component-layout/src/BpkStack.tsx b/packages/bpk-component-layout/src/BpkStack.tsx new file mode 100644 index 0000000000..34aaf16fec --- /dev/null +++ b/packages/bpk-component-layout/src/BpkStack.tsx @@ -0,0 +1,41 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Stack, VStack, HStack } from '@chakra-ui/react'; + +import { processBpkComponentProps } from './tokenUtils'; + +import type { BpkStackProps } from './types'; + +export const BpkStack = ({ children, ...props }: BpkStackProps) => { + const processedProps = processBpkComponentProps(props, { component: 'BpkStack' }); + return <Stack {...processedProps}>{children}</Stack>; +}; + +export const BpkHStack = ({ children, ...props }: BpkStackProps) => { + const processedProps = processBpkComponentProps(props, { component: 'BpkStack' }); + return <HStack {...processedProps}>{children}</HStack>; +}; + +export const BpkVStack = ({ children, ...props }: BpkStackProps) => { + const processedProps = processBpkComponentProps(props, { component: 'BpkStack' }); + return <VStack {...processedProps}>{children}</VStack>; +}; + +export type { BpkStackProps }; + diff --git a/packages/bpk-component-layout/src/accessibility-test.tsx b/packages/bpk-component-layout/src/accessibility-test.tsx new file mode 100644 index 0000000000..3686a48635 --- /dev/null +++ b/packages/bpk-component-layout/src/accessibility-test.tsx @@ -0,0 +1,106 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; + +import '@testing-library/jest-dom'; + +import { BpkBox } from './BpkBox'; +import { BpkFlex } from './BpkFlex'; +import { BpkGrid } from './BpkGrid'; +import { BpkProvider } from './BpkProvider'; +import { BpkStack } from './BpkStack'; +import { BpkSpacing } from './tokens'; + +describe('bpk-component-layout accessibility tests', () => { + it('BpkBox basic usage should not have detectable accessibility issues', async () => { + const { container } = render( + <BpkProvider> + <BpkBox + role="region" + aria-label="Layout region" + padding={BpkSpacing.MD} + > + Accessible layout content + </BpkBox> + </BpkProvider>, + ); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('BpkStack basic usage should not have detectable accessibility issues', async () => { + const { container } = render( + <BpkProvider> + <BpkStack + role="group" + aria-label="Stack region" + gap={BpkSpacing.MD} + > + <div>Item one</div> + <div>Item two</div> + </BpkStack> + </BpkProvider>, + ); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('BpkFlex basic usage should not have detectable accessibility issues', async () => { + const { container } = render( + <BpkProvider> + <BpkFlex + role="group" + aria-label="Flex container" + direction="row" + gap={BpkSpacing.MD} + > + <BpkBox>Item 1</BpkBox> + <BpkBox>Item 2</BpkBox> + </BpkFlex> + </BpkProvider>, + ); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('BpkGrid basic usage should not have detectable accessibility issues', async () => { + const { container } = render( + <BpkProvider> + <BpkGrid + role="region" + aria-label="Layout Grid" + templateColumns="repeat(2, 1fr)" + gap={BpkSpacing.MD} + > + <BpkBox>Cell 1</BpkBox> + <BpkBox>Cell 2</BpkBox> + <BpkBox>Cell 3</BpkBox> + <BpkBox>Cell 4</BpkBox> + </BpkGrid> + </BpkProvider>, + ); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); +}); diff --git a/packages/bpk-component-layout/src/commonProps.ts b/packages/bpk-component-layout/src/commonProps.ts new file mode 100644 index 0000000000..cc23fcdf93 --- /dev/null +++ b/packages/bpk-component-layout/src/commonProps.ts @@ -0,0 +1,142 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { + BpkSpacingValue, + BpkSizeValue, + BpkPositionValue, + BpkResponsiveValue, +} from './tokens'; + +/** + * Common spacing-related props shared by all Backpack layout components + * All spacing props must use Backpack spacing tokens or percentages + */ +export interface BpkSpacingProps { + // Padding props + padding?: BpkResponsiveValue<BpkSpacingValue>; + paddingTop?: BpkResponsiveValue<BpkSpacingValue>; + paddingRight?: BpkResponsiveValue<BpkSpacingValue>; + paddingBottom?: BpkResponsiveValue<BpkSpacingValue>; + paddingLeft?: BpkResponsiveValue<BpkSpacingValue>; + + // Margin props + margin?: BpkResponsiveValue<BpkSpacingValue>; + marginTop?: BpkResponsiveValue<BpkSpacingValue>; + marginRight?: BpkResponsiveValue<BpkSpacingValue>; + marginBottom?: BpkResponsiveValue<BpkSpacingValue>; + marginLeft?: BpkResponsiveValue<BpkSpacingValue>; + marginStart?: BpkResponsiveValue<BpkSpacingValue>; + marginEnd?: BpkResponsiveValue<BpkSpacingValue>; + paddingStart?: BpkResponsiveValue<BpkSpacingValue>; + paddingEnd?: BpkResponsiveValue<BpkSpacingValue>; + marginInline?: BpkResponsiveValue<BpkSpacingValue>; + paddingInline?: BpkResponsiveValue<BpkSpacingValue>; + + // Gap + gap?: BpkResponsiveValue<BpkSpacingValue>; + + // Size props + // width, height etc allow rem values, percentages and a small set of + // semantic strings. We intentionally do not use spacing tokens here to + // avoid coupling layout sizes to the spacing scale. + width?: BpkResponsiveValue<BpkSizeValue>; + height?: BpkResponsiveValue<BpkSizeValue>; + minWidth?: BpkResponsiveValue<BpkSizeValue>; + minHeight?: BpkResponsiveValue<BpkSizeValue>; + maxWidth?: BpkResponsiveValue<BpkSizeValue>; + maxHeight?: BpkResponsiveValue<BpkSizeValue>; + + // Position props (use rem values or percentages) + top?: BpkResponsiveValue<BpkPositionValue>; + right?: BpkResponsiveValue<BpkPositionValue>; + bottom?: BpkResponsiveValue<BpkPositionValue>; + left?: BpkResponsiveValue<BpkPositionValue>; +} + +/** + * Common props for all Backpack layout components + * Combines spacing and color props, and explicitly excludes className and + * certain props (e.g. typography, composite borders, transitions) to keep + * layout components purely structural. + * + * NOTE: + * - Layout components other than BpkBox do not expose event handlers. + * - BpkBox reintroduces a minimal set of events (onClick, onFocus, onBlur) + * on its own props type. + */ +export interface BpkCommonLayoutProps extends BpkSpacingProps { + // Explicitly exclude className + className?: never; + + // Explicitly exclude style to avoid ad-hoc inline styling on layout primitives. + style?: never; + + // Testing & automation attributes + 'data-testid'?: string; + 'data-cy'?: string; + + // Explicitly exclude color-related props to keep layout purely structural. + // These props still exist on the underlying Chakra Box, so we mark them as + // never here to prevent them from leaking into public layout APIs. + color?: never; + background?: never; + backgroundColor?: never; + borderColor?: never; + borderTopColor?: never; + borderRightColor?: never; + borderBottomColor?: never; + borderLeftColor?: never; + + // Explicitly exclude border width props from the public layout API for now. + borderWidth?: never; + borderTopWidth?: never; + borderRightWidth?: never; + borderBottomWidth?: never; + borderLeftWidth?: never; + + // Explicitly exclude border radius props from the public layout API for now. + borderRadius?: never; + borderTopLeftRadius?: never; + borderTopRightRadius?: never; + borderBottomLeftRadius?: never; + borderBottomRightRadius?: never; + + // Explicitly exclude shadow props from the public layout API for now. + boxShadow?: never; + + // Explicitly exclude composite border shorthand props to enforce tokenised API + border?: never; + borderTop?: never; + borderRight?: never; + borderBottom?: never; + borderLeft?: never; + borderInline?: never; + borderBlock?: never; + borderX?: never; + borderY?: never; + + // Explicitly exclude transition & transform related props for performance reasons + transition?: never; + transitionProperty?: never; + transitionDuration?: never; + transitionTimingFunction?: never; + transitionDelay?: never; + transform?: never; + transformOrigin?: never; +} diff --git a/packages/bpk-component-layout/src/theme.ts b/packages/bpk-component-layout/src/theme.ts new file mode 100644 index 0000000000..4c67123ef9 --- /dev/null +++ b/packages/bpk-component-layout/src/theme.ts @@ -0,0 +1,214 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { defineConfig } from '@chakra-ui/react'; + +import type { ChakraBreakpointKey } from './tokens'; + +// Import tokens from Backpack foundations +// Note: Some tokens may not be in TypeScript definitions but exist at runtime + +const bpkTokens = require('@skyscanner/bpk-foundations-web/tokens/base.es6'); + +// NOTE: +// We intentionally do not use the raw breakpoint *values* from foundations here. +// Foundations exports breakpoint values such as `breakpointMobile = "32rem"` which +// are used primarily for max-width queries (e.g. `(max-width: 32rem)`). +// +// Backpack layout responsive values in this package are mobile-first and behave +// like Chakra breakpoints (min-width thresholds). To align with Backpack’s +// intended breakpoint ranges we define lower-bound (min-width) thresholds: +// +// - small-mobile: 320px+ +// - mobile: 360px+ +// - small-tablet: 513px+ +// - tablet: 769px+ +// - desktop: 1025px+ + +// Note: Spacing tokens are defined as SCSS functions in Backpack foundations, +// not as direct values. We need to use the actual rem values from the SCSS functions. +// Based on @skyscanner/bpk-foundations-web/tokens/base.default.scss: +// - bpk-spacing-sm() returns .25rem +// - bpk-spacing-md() returns .5rem +// - bpk-spacing-lg() returns 1.5rem +// - bpk-spacing-xl() returns 2rem (needs verification) +// - bpk-spacing-xxl() returns 2.5rem +// - bpk-spacing-base() returns 1rem (standard base spacing) +// TODO: CLOV-1021 - will add spacing tokens to Backpack Foundations package and use them here after we ship the PoC +const spacingXs = '.125rem'; // 2px +const spacingSm = '.25rem'; +const spacingBase = '1rem'; // Standard base spacing +const spacingMd = '.5rem'; +const spacingLg = '1.5rem'; +const spacingXl = '2rem'; +const spacingXxl = '2.5rem'; + +/** + * Backpack Theme Configuration for Chakra UI + * + * This theme maps Backpack design tokens from @skyscanner/bpk-foundations-web + * to Chakra UI's theme structure. + */ + +/** + * Maps Backpack spacing tokens to actual rem values. + * These come directly from @skyscanner/bpk-foundations-web. + */ +// Spacing tokens - directly imported from foundations +const spacingMap: Record<string, { value: string }> = { + 'bpk-spacing-none': { value: '0' }, + // Temporary: Foundations does not yet export a 2px spacing token. This will be + // replaced with a foundations value once available. + 'bpk-spacing-xs': { value: spacingXs }, + 'bpk-spacing-sm': { value: spacingSm }, + 'bpk-spacing-base': { value: spacingBase }, + 'bpk-spacing-md': { value: spacingMd }, + 'bpk-spacing-lg': { value: spacingLg }, + 'bpk-spacing-xl': { value: spacingXl }, + 'bpk-spacing-xxl': { value: spacingXxl }, +}; + +/** + * Maps Backpack border size tokens to actual border width values + * These come directly from @skyscanner/bpk-foundations-web + */ +const borderSizeMap: Record<string, string> = { + 'bpk-border-size-sm': bpkTokens.borderSizeSm, + 'bpk-border-size-lg': bpkTokens.borderSizeLg, + 'bpk-border-size-xl': bpkTokens.borderSizeXl, +}; + +/** + * Maps Backpack border radius tokens to actual radius values. + * These come directly from @skyscanner/bpk-foundations-web. + */ +const borderRadiusMap: Record<string, string> = { + 'bpk-border-radius-none': '0', + 'bpk-border-radius-xs': bpkTokens.borderRadiusXs, + 'bpk-border-radius-sm': bpkTokens.borderRadiusSm, + 'bpk-border-radius-md': bpkTokens.borderRadiusMd, + 'bpk-border-radius-lg': bpkTokens.borderRadiusLg, + 'bpk-border-radius-xl': bpkTokens.borderRadiusXl, + 'bpk-border-radius-full': bpkTokens.borderRadiusFull, +}; + +/** + * Maps Backpack shadow tokens to actual box-shadow values + * These come directly from @skyscanner/bpk-foundations-web + */ +const shadowMap: Record<string, string> = { + 'bpk-shadow-sm': bpkTokens.boxShadowSm, + 'bpk-shadow-lg': bpkTokens.boxShadowLg, + 'bpk-shadow-xl': bpkTokens.boxShadowXl, +}; + +/** + * Chakra expects raw width values (e.g. "48rem"), not full media queries. + * The media query construction is handled internally by Chakra's system. + * + * We align Backpack breakpoint tokens to Chakra's keys like this: + * - base: 0 (implicit) + * - sm: small-mobile (>= 320px) + * - md: mobile (>= 360px) + * - lg: small-tablet (>= 513px) + * - xl: tablet (>= 769px) + * - 2xl: desktop (>= 1025px) + */ +// TODO: CLOV-1021 - will add breakpoint boundary tokens to Backpack Foundations package and use them here after we ship the PoC +const breakpointMap: Record<ChakraBreakpointKey, string> = { + base: '0rem', + sm: '20rem', // 320px + md: '22.5rem', // 360px + lg: '32.0625rem', // 513px + xl: '48.0625rem', // 769px + '2xl': '64.0625rem', // 1025px +}; + +/** + * Exports spacing map for use in tokenUtils + * This allows tokenUtils to look up actual spacing values + * + * @returns {Record<string, string>} A map of spacing token names to values. + */ +export function getSpacingMap(): Record<string, string> { + // Return simple string values for backward compatibility with utilities + const simpleMap: Record<string, string> = {}; + Object.entries(spacingMap).forEach(([key, obj]) => { + simpleMap[key] = obj.value; + }); + return simpleMap; +} + +/** + * Gets the actual spacing value for a Backpack spacing token + * + * @param {string} token - Backpack spacing token name. + * @returns {string | undefined} The actual spacing value. + */ +export function getSpacingValue(token: string): string | undefined { + return spacingMap[token]?.value; +} + +/** + * Gets the actual border width value for a Backpack border size token + * + * @param {string} token - Backpack border size token name. + * @returns {string | undefined} The actual border width value. + */ +export function getBorderSizeValue(token: string): string | undefined { + return borderSizeMap[token]; +} + +/** + * Gets the actual border radius value for a Backpack border radius token + * + * @param {string} token - Backpack border radius token name. + * @returns {string | undefined} The actual border radius value. + */ +export function getBorderRadiusValue(token: string): string | undefined { + return borderRadiusMap[token]; +} + +/** + * Gets the actual box-shadow value for a Backpack shadow token + * + * @param {string} token - Backpack shadow token name. + * @returns {string | undefined} The actual box-shadow value. + */ +export function getShadowValue(token: string): string | undefined { + return shadowMap[token]; +} + +export function createBpkConfig() { + // Convert breakpoint map to Chakra UI format + // Breakpoints in Chakra v3 are typically simple strings in the breakpoints object + const chakraBreakpoints: Record<string, string> = {}; + Object.entries(breakpointMap).forEach(([token, value]) => { + chakraBreakpoints[token] = value; + }); + + return defineConfig({ + cssVarsPrefix: 'bpk', + theme: { + tokens: { + spacing: spacingMap, + }, + breakpoints: chakraBreakpoints, + }, + }); +} diff --git a/packages/bpk-component-layout/src/tokenUtils-test.ts b/packages/bpk-component-layout/src/tokenUtils-test.ts new file mode 100644 index 0000000000..1f3f31a01d --- /dev/null +++ b/packages/bpk-component-layout/src/tokenUtils-test.ts @@ -0,0 +1,200 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + convertBpkSpacingToChakra, + processBpkComponentProps, + processBpkProps, + processResponsiveProps, +} from './tokenUtils'; +import { BpkSpacing } from './tokens'; + +describe('processBpkProps', () => { + it('converts spacing tokens to rem values', () => { + const result = processBpkProps({ padding: BpkSpacing.MD }); + + // bpk-spacing-md is mapped to .5rem in theme.ts + expect(result.padding).toBe('.5rem'); + }); + + it('supports the temporary 2px spacing token (bpk-spacing-xs)', () => { + const result = processBpkProps({ padding: BpkSpacing.XS }); + + // bpk-spacing-xs is mapped to .125rem (2px) in theme.ts + expect(result.padding).toBe('.125rem'); + }); + + it('passes through percentage spacing values unchanged', () => { + const result = processBpkProps({ margin: '50%' }); + + expect(result.margin).toBe('50%'); + }); + + it('converts RTL and inline spacing props', () => { + const result = processBpkProps({ + marginInline: BpkSpacing.Base, + paddingStart: BpkSpacing.SM, + paddingEnd: BpkSpacing.LG, + }); + + expect(result.marginInline).toBe('1rem'); + expect(result.paddingStart).toBe('.25rem'); + expect(result.paddingEnd).toBe('1.5rem'); + }); + + it('validates and passes through size props (rem and percentages)', () => { + const result = processBpkProps({ + width: '10rem', + height: '50%', + minWidth: '1.5rem', + maxHeight: '100%', + }); + + expect(result.width).toBe('10rem'); + expect(result.height).toBe('50%'); + expect(result.minWidth).toBe('1.5rem'); + expect(result.maxHeight).toBe('100%'); + }); + + it('removes invalid size values', () => { + const result = processBpkProps({ + width: 'not-a-valid-size', + }); + + expect(result.width).toBeUndefined(); + }); + + it('removes className and logs a warning in non-production', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + + const result = processBpkProps({ + className: 'custom-class', + padding: BpkSpacing.Base, + }); + + expect('className' in result).toBe(false); + expect(result.padding).toBe('1rem'); + + // In Jest, NODE_ENV is "test" so the warning should be emitted. + expect(warnSpy).toHaveBeenCalled(); + warnSpy.mockRestore(); + }); + + it('removes style and logs a warning in non-production', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + + const result = processBpkProps({ + // style is not part of the public API but should still be stripped + // and warned about at runtime if passed through accidentally. + style: { dummy: 'value' }, + padding: BpkSpacing.Base, + }); + + expect('style' in result).toBe(false); + expect(result.padding).toBe('1rem'); + + // In Jest, NODE_ENV is "test" so the warning should be emitted. + expect(warnSpy).toHaveBeenCalled(); + warnSpy.mockRestore(); + }); + + it('converts Backpack breakpoint keys to Chakra keys for responsive objects', () => { + const result = processBpkProps({ + padding: { + mobile: BpkSpacing.SM, + tablet: BpkSpacing.MD, + }, + }); + + expect(result.padding).toEqual({ + md: '.25rem', + xl: '.5rem', + }); + }); + + it('maps Backpack breakpoint keys for non-spacing layout props via processResponsiveProps', () => { + const result = processResponsiveProps({ + display: { mobile: 'flex', desktop: 'grid' }, + flexDirection: { mobile: 'column', tablet: 'row' }, + gridTemplateColumns: { tablet: 'repeat(2, 1fr)', desktop: 'repeat(4, 1fr)' }, + }); + + expect(result).toEqual({ + display: { md: 'flex', '2xl': 'grid' }, + flexDirection: { md: 'column', xl: 'row' }, + gridTemplateColumns: { xl: 'repeat(2, 1fr)', '2xl': 'repeat(4, 1fr)' }, + }); + }); + + it('does not let allowlisted Box layout props fall through unprocessed (e.g. array responsive values)', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + + const result = processBpkComponentProps( + { + // Chakra array syntax is intentionally not supported in the public API. + display: ['flex', 'grid'], + } as any, + { component: 'BpkBox' }, + ); + + expect(result.display).toBeUndefined(); + expect(warnSpy).toHaveBeenCalled(); + warnSpy.mockRestore(); + }); + + it('removes array-based responsive values and warns in non-production', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + + const result = processBpkProps({ + // array-based responsive values are intentionally not supported + padding: [BpkSpacing.SM, BpkSpacing.MD] as any, + }); + + expect(result.padding).toBeUndefined(); + expect(warnSpy).toHaveBeenCalled(); + warnSpy.mockRestore(); + }); + + it('warns and drops unknown breakpoint keys from responsive objects', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + + const result = processBpkProps({ + padding: { + // unknown key should be ignored + phablet: BpkSpacing.SM, + mobile: BpkSpacing.MD, + } as any, + }); + + expect(result.padding).toEqual({ + md: '.5rem', + }); + expect(warnSpy).toHaveBeenCalled(); + warnSpy.mockRestore(); + }); + + it('warns and returns unknown spacing tokens as-is (dev fallback)', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + + const result = convertBpkSpacingToChakra('bpk-spacing-unknown'); + + expect(result).toBe('bpk-spacing-unknown'); + expect(warnSpy).toHaveBeenCalled(); + warnSpy.mockRestore(); + }); +}); diff --git a/packages/bpk-component-layout/src/tokenUtils.ts b/packages/bpk-component-layout/src/tokenUtils.ts new file mode 100644 index 0000000000..69ff2a23c7 --- /dev/null +++ b/packages/bpk-component-layout/src/tokenUtils.ts @@ -0,0 +1,486 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import StackOptionKeys from './BpkStack.constant'; +import { getSpacingValue } from './theme'; +import { + BpkBreakpointToChakraKey, + isValidSpacingValue, + isValidSizeValue, + isValidPositionValue, + isPercentage, +} from './tokens'; + +import type { BpkBreakpointToken } from './tokens'; + +export type BpkLayoutComponentName = 'BpkBox' | 'BpkFlex' | 'BpkGrid' | 'BpkStack'; + +/** + * Allowlisted, component-scoped prop groups that are eligible for Backpack responsive value + * processing (Backpack breakpoint keys -> Chakra breakpoint keys). + * + * NOTE: + * - Spacing/size/position props are processed separately via `processBpkProps` and therefore + * are intentionally NOT included here. + * - These groups are meant to keep the responsive surface predictable per component, while + * avoiding duplicated per-component breakpoint mapping logic. + */ +type BpkResponsivePropGroups = { + /** + * Container-level layout props (how children are laid out). + */ + container: readonly string[]; + /** + * Item-level layout props (how this element participates in a parent layout). + */ + item?: readonly string[]; +}; + +export const BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT: Record< + BpkLayoutComponentName, + BpkResponsivePropGroups +> = { + BpkBox: { + container: [ + // Display + 'display', + // Flex container props + 'flexDirection', + 'flexWrap', + 'justifyContent', + 'alignItems', + 'alignContent', + // Grid container props + 'gridTemplateColumns', + 'gridTemplateRows', + 'gridTemplateAreas', + 'gridAutoFlow', + 'gridAutoRows', + 'gridAutoColumns', + ], + item: [ + // Flex item props + 'flex', + 'flexGrow', + 'flexShrink', + 'flexBasis', + 'order', + 'alignSelf', + 'justifySelf', + // Grid item placement props (useful on Box when composing grids) + 'gridColumn', + 'gridRow', + ], + }, + // Note: BpkFlex maps its public API props to these Chakra keys. + BpkFlex: { + container: [ + 'flexDirection', + 'justifyContent', + 'alignItems', + 'flexWrap', + ], + item: [ + 'flexGrow', + 'flexShrink', + 'flexBasis', + ], + }, + // Note: BpkGrid maps its public API props to these Chakra keys. + BpkGrid: { + container: [ + 'justifyContent', + 'alignItems', + 'gridTemplateColumns', + 'gridTemplateRows', + 'gridTemplateAreas', + 'gridAutoFlow', + 'gridAutoRows', + 'gridAutoColumns', + ], + item: [ + // Used when placing the grid itself within a parent grid. + 'gridColumn', + 'gridRow', + ], + }, + // Note: BpkStack uses Chakra Stack option prop names directly. + BpkStack: { + container: StackOptionKeys as unknown as readonly string[], + }, +}; + +export const BPK_RESPONSIVE_PROP_KEYS_BY_COMPONENT: Record< + BpkLayoutComponentName, + readonly string[] +> = { + BpkBox: [ + ...BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT.BpkBox.container, + ...(BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT.BpkBox.item ?? []), + ], + BpkFlex: [ + ...BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT.BpkFlex.container, + ...(BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT.BpkFlex.item ?? []), + ], + BpkGrid: [ + ...BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT.BpkGrid.container, + ...(BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT.BpkGrid.item ?? []), + ], + BpkStack: [...BPK_RESPONSIVE_PROP_GROUPS_BY_COMPONENT.BpkStack.container], +}; + +export type ProcessBpkComponentPropsOptions = { + component: BpkLayoutComponentName; + /** + * Optional map of responsive props. When provided, it will be filtered to the + * allowlist for the given component, then breakpoint-normalised. + * + * This is useful for components like `BpkFlex` and `BpkGrid` that expose a + * public API with different prop names. + */ + responsiveProps?: Record<string, any>; + /** + * Optional mapping of source prop name -> target prop name. + * Primarily kept for parity with `processResponsiveProps`. + */ + propNameMap?: Record<string, string>; +}; + +function filterToAllowlist( + props: Record<string, any>, + allowlist: readonly string[], +): Record<string, any> { + const allowed = new Set(allowlist); + const result: Record<string, any> = {}; + Object.keys(props).forEach((key) => { + if (allowed.has(key) && props[key] !== undefined) { + result[key] = props[key]; + } + }); + return result; +} + +/** + * Process a component's props in one place: + * - strip className/style + * - process spacing/size/position props (including breakpoint mapping + token conversion) + * - process allowlisted non-spacing responsive layout props (breakpoint mapping only) + * + * The allowlist is grouped by component via `BPK_RESPONSIVE_PROP_KEYS_BY_COMPONENT`. + * + * @param {T} props - The component props to process. + * @param {ProcessBpkComponentPropsOptions} options - Component processing options (allowlist group + mapping). + * @returns {Record<string, any>} The processed props ready to pass to Chakra primitives. + */ +export function processBpkComponentProps<T extends Record<string, any>>( + props: T, + options: ProcessBpkComponentPropsOptions, +): Record<string, any> { + const processed = processBpkProps(props); + + const allowlist = BPK_RESPONSIVE_PROP_KEYS_BY_COMPONENT[options.component]; + const responsiveSource = options.responsiveProps + ? filterToAllowlist(options.responsiveProps, allowlist) + : filterToAllowlist(processed, allowlist); + + if (Object.keys(responsiveSource).length === 0) { + return processed; + } + + // Ensure allowlisted layout props do NOT fall through unprocessed (e.g. array responsive values). + // These props must be provided via the responsive processing pipeline only. + const cleanedProcessed: Record<string, any> = { ...processed }; + allowlist.forEach((key) => { + if (key in cleanedProcessed) { + delete cleanedProcessed[key]; + } + }); + + const responsiveProcessed = processResponsiveProps( + responsiveSource, + options.propNameMap, + ); + + // Remove keys that ended up as `undefined` (e.g. array responsive values are rejected). + Object.keys(responsiveProcessed).forEach((key) => { + if (responsiveProcessed[key] === undefined) { + delete responsiveProcessed[key]; + } + }); + + return { ...cleanedProcessed, ...responsiveProcessed }; +} + +/** + * Converts Backpack spacing token to Chakra UI compatible value + * Returns the actual spacing value from the theme, not a token path + * + * @param {string} value - Backpack spacing token (e.g., 'bpk-spacing-base') or percentage + * @returns {string} The actual spacing value in rem or the percentage string + */ +export function convertBpkSpacingToChakra(value: string): string { + if (isPercentage(value)) { + return value; // Percentages pass through + } + + // Look up the actual spacing value from the theme + const spacingValue = getSpacingValue(value); + if (spacingValue !== undefined) { + return spacingValue; + } + + // Fallback: if token not found, return the value as-is (will cause a warning) + if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line no-console + console.warn( + `Spacing token "${value}" not found in theme. Returning as-is.` + ); + } + return value; +} + +/** + * Recursively processes responsive values (arrays or objects) to validate and convert tokens + * + * @param {*} value - The value to process (could be string, array, or object) + * @param {Function} converter - Function to convert valid tokens to actual values + * @param {Function} validator - Function to validate if a token is allowed + * @param {string} propName - The name of the prop being processed (for warning messages) + * @returns {*} The processed value with tokens converted, or undefined for invalid tokens + */ +export function normalizeResponsiveObject<T>(value: Record<string, T>): Record<string, T> { + const normalized: Record<string, T> = {}; + Object.entries(value).forEach(([key, val]) => { + if (key === 'base') { + normalized.base = val; + return; + } + + const chakraKey = BpkBreakpointToChakraKey[key as BpkBreakpointToken]; + if (chakraKey) { + normalized[chakraKey] = val; + } else if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line no-console + console.warn( + `Unknown breakpoint "${key}" used in responsive prop. ` + + 'Use Backpack breakpoint tokens such as mobile, tablet or desktop.' + ); + } + }); + return normalized; +} + +export function processResponsiveValue( + value: any, + converter: (v: string) => string, + validator: (v: string) => boolean, + propName: string +): any { + if (value === undefined || value === null) { + return value; + } + + if (Array.isArray(value)) { + if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line no-console + console.warn( + `Array-based responsive values are not supported for prop "${propName}". ` + + `Please use Backpack breakpoint keys instead.` + ); + } + return undefined; + } + + if (typeof value === 'object') { + const normalized = normalizeResponsiveObject(value); + const result: Record<string, any> = {}; + Object.keys(normalized).forEach((key) => { + const processedValue = processResponsiveValue( + normalized[key], + converter, + validator, + propName + ); + if (processedValue !== undefined) { + result[key] = processedValue; + } + }); + return Object.keys(result).length > 0 ? result : undefined; + } + + const strValue = String(value); + if (!validator(strValue)) { + if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line no-console + console.warn( + `Invalid value "${strValue}" for prop "${propName}". ` + + `Only Backpack tokens are allowed.` + ); + } + return undefined; // Invalid values are removed + } + + return converter(strValue); +} + +/** + * Validates and converts spacing props for Chakra UI + * Handles all spacing-related properties including padding, margin, gap, size, border radius and position + * + * @param {T} props - Component props object + * @returns {Record<string, any>} Processed props with spacing tokens converted to actual values + */ +export function processSpacingProps<T extends Record<string, any>>( + props: T +): Record<string, any> { + const spacingKeys = [ + // Padding props + 'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', + 'paddingStart', 'paddingEnd', 'paddingInline', + // Margin props + 'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft', + 'marginStart', 'marginEnd', 'marginInline', + // Gap and spacing + 'gap', 'spacing', + 'rowGap', 'columnGap', + // Size props + 'width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight', + // Position props + 'top', 'right', 'bottom', 'left', + ]; + + const processed: Record<string, any> = { ...props }; + + spacingKeys.forEach((key) => { + if (key in processed && processed[key] !== undefined) { + const sizeKeys = ['width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight']; + const positionKeys = ['top', 'right', 'bottom', 'left']; + + const isSizeProp = sizeKeys.includes(key); + const isPositionProp = positionKeys.includes(key); + + let converter: (v: string) => string; + if (isSizeProp || isPositionProp) { + converter = (v: string) => v; + } else { + converter = convertBpkSpacingToChakra; + } + + let validator: (v: string) => boolean; + if (isSizeProp) { + validator = isValidSizeValue; + } else if (isPositionProp) { + validator = isValidPositionValue; + } else { + validator = isValidSpacingValue; + } + + const processedValue = processResponsiveValue( + processed[key], + converter, + validator, + key + ); + + if (processedValue !== undefined) { + processed[key] = processedValue; + } else { + delete processed[key]; + } + } + }); + + return processed; +} + +/** + * Processes all props to convert Backpack tokens to Chakra UI format + * Also explicitly removes className and style to prevent ad-hoc overrides + * + * Processing order: + * 1. Remove className & style + * 2. Process spacing props (includes position) + * + * @param {T} props - Component props object + * @returns {Record<string, any>} Processed props with tokens converted and disallowed props removed + */ +export function processBpkProps<T extends Record<string, any>>( + props: T +): Record<string, any> { + // Explicitly remove className and style to prevent style overrides + const { className, style, ...cleanProps } = props; + + if (className !== undefined && process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line no-console + console.warn( + 'className prop is not allowed on Backpack layout components. ' + + 'It has been removed to maintain design system consistency.' + ); + } + + if (style !== undefined && process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line no-console + console.warn( + 'style prop is not allowed on Backpack layout components. ' + + 'It has been removed to maintain design system consistency.' + ); + } + + // Process spacing props (includes position) + return processSpacingProps(cleanProps); +} + +/** + * Processes responsive props that are simple string/enum values (non-spacing) + * using Backpack breakpoint keys. Array syntax is rejected as in spacing. + * + * @param {*} value - The value to process + * @param {string} propName - The name of the prop being processed + * @returns {*} The processed value with breakpoint keys mapped to Chakra keys + */ +export function processResponsiveStringProp(value: any, propName: string): any { + return processResponsiveValue( + value, + (v: string) => v, + () => true, + propName + ); +} + +/** + * Processes a collection of responsive props. + * @param {Record<string, any>} props - Object containing prop values. + * @param {Record<string, string>} propNameMap - Map of prop name to CSS/Chakra property name (for error messages and mapping). + * @returns {Record<string, any>} Processed props object. + */ +export function processResponsiveProps( + props: Record<string, any>, + propNameMap?: Record<string, string> +): Record<string, any> { + const processed: Record<string, any> = {}; + Object.keys(props).forEach((key) => { + if (props[key] !== undefined) { + const targetPropName = propNameMap ? propNameMap[key] || key : key; + processed[targetPropName] = processResponsiveStringProp( + props[key], + targetPropName + ); + } + }); + return processed; +} diff --git a/packages/bpk-component-layout/src/tokens.ts b/packages/bpk-component-layout/src/tokens.ts new file mode 100644 index 0000000000..26b8c10fa0 --- /dev/null +++ b/packages/bpk-component-layout/src/tokens.ts @@ -0,0 +1,172 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Backpack Design Tokens for Layout Components + * + * This file provides token mappings from Backpack design tokens to Chakra UI theme. + * All tokens are sourced from @skyscanner/bpk-foundations-web + */ + +/** + * Backpack Spacing Tokens + * Use these constants to ensure type safety when passing spacing props + */ +export const BpkSpacing = { + None: 'bpk-spacing-none', + XS: 'bpk-spacing-xs', + SM: 'bpk-spacing-sm', + Base: 'bpk-spacing-base', + MD: 'bpk-spacing-md', + LG: 'bpk-spacing-lg', + XL: 'bpk-spacing-xl', + XXL: 'bpk-spacing-xxl', +} as const; + +export type BpkSpacingToken = typeof BpkSpacing[keyof typeof BpkSpacing]; + +/** + * Backpack Breakpoint Tokens + * Use these constants to ensure type safety when defining responsive overrides + * These map to the simplified keys defined in the Chakra theme + */ +export const BpkBreakpoint = { + SmallMobile: 'small-mobile', + Mobile: 'mobile', + SmallTablet: 'small-tablet', + Tablet: 'tablet', + Desktop: 'desktop', +} as const; + +export type BpkBreakpointToken = typeof BpkBreakpoint[keyof typeof BpkBreakpoint]; + +export type ChakraBreakpointKey = 'base' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + +export const BpkBreakpointToChakraKey: Record<BpkBreakpointToken, ChakraBreakpointKey> = { + // Keep this mapping in sync with the breakpoints configured in `theme.ts`. + // `base` is reserved for "default value" and is not a breakpoint token. + 'small-mobile': 'sm', + mobile: 'md', + 'small-tablet': 'lg', + tablet: 'xl', + desktop: '2xl', +}; + +/** + * Helper type for values that can be Backpack tokens or percentages + * but NOT px/rem values + */ +export type BpkSpacingValue = BpkSpacingToken | `${number}%`; +export type BpkBreakpointValue = BpkBreakpointToken; + +/** + * Helper type for size props that can use rem, percentages or semantic values. + * This is intentionally separate from BpkSpacingValue to avoid encouraging + * spacing tokens for explicit sizes. + */ +export type BpkSizeValue = + | `${number}rem` + | `${number}%` + | 'auto' + | 'full' + | 'fit-content'; + +/** + * Helper type for position props that can use rem or percentages. + * We intentionally do not allow semantic values like 'auto' here. + */ +export type BpkPositionValue = + | `${number}rem` + | `${number}%`; + +/** + * Helper type for flex-basis prop that can use rem, percentages or semantic values. + * Excludes 'px' values to enforce design system constraints. + */ +export type BpkBasisValue = + | `${number}rem` + | `${number}%` + | 'auto' + | 'content' + | 'fit-content' + | 'max-content' + | 'min-content' + | 'initial' + | 'inherit'; + +/** + * Helper type for responsive values based on Backpack breakpoints. + * + * We intentionally only support: + * - a single scalar value (non-responsive) + * - an object keyed by Backpack breakpoint tokens (and optional base) + * + * We do NOT support array-based responsive values in the public API. + */ +export type BpkResponsiveValue<T> = + | T + | Partial<Record<BpkBreakpointToken | 'base', T>>; + +/** + * Validates if a value is a percentage string + * + * @param {string} value - The value to validate + * @returns {boolean} True if the value is a valid percentage string + */ +export function isPercentage(value: string): boolean { + return /^\d+(\.\d+)?%$/.test(value); +} + +/** + * Validates if a spacing value is valid (token or percentage) + * + * @param {string} value - The spacing value to validate + * @returns {boolean} True if the value is a valid Backpack spacing token or percentage + */ +export function isValidSpacingValue(value: string): boolean { + return Object.values(BpkSpacing).includes(value as BpkSpacingToken) || isPercentage(value); +} + +/** + * Validates if a size value is valid + * + * @param {string} value - The size value to validate + * @returns {boolean} True if the value is a valid rem/percentage/semantic size + */ +export function isValidSizeValue(value: string): boolean { + return ( + /^-?\d+(\.\d+)?rem$/.test(value) || // rem values + isPercentage(value) || // percentage values + value === 'auto' || + value === 'full' || + value === 'fit-content' + ); +} + +/** + * Validates if a position value is valid + * + * @param {string} value - The position value to validate + * @returns {boolean} True if the value is a valid rem or percentage + */ +export function isValidPositionValue(value: string): boolean { + return ( + /^-?\d+(\.\d+)?rem$/.test(value) || // rem values + isPercentage(value) // percentage values + ); +} diff --git a/packages/bpk-component-layout/src/types.ts b/packages/bpk-component-layout/src/types.ts new file mode 100644 index 0000000000..e9e53afd1c --- /dev/null +++ b/packages/bpk-component-layout/src/types.ts @@ -0,0 +1,309 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ReactNode } from 'react'; + +import type StackOptionKeys from './BpkStack.constant'; +import type { BpkCommonLayoutProps } from './commonProps'; +import type { BpkSpacingValue, BpkResponsiveValue, BpkBasisValue } from './tokens'; +import type { + BoxProps, + FlexProps, + GridProps, + GridItemProps, + StackProps, +} from '@chakra-ui/react'; + + +/** + * Layout-level event props that should not be exposed on layout components + * by default. BpkBox will reintroduce a minimal subset (onClick, onFocus, + * onBlur) explicitly on its own props type. + */ +type LayoutEventProps = + | 'onClick' + | 'onMouseEnter' + | 'onMouseLeave' + | 'onMouseOver' + | 'onMouseOut' + | 'onMouseDown' + | 'onMouseUp' + | 'onFocus' + | 'onBlur' + | 'onKeyDown' + | 'onKeyUp' + | 'onKeyPress'; + +/** + * Shorthand props from the underlying layout system that we do NOT expose on + * Backpack layout components. These mostly mirror longer-form spacing, + * sizing and visual props that we already model explicitly via + * BpkCommonLayoutProps and BpkFlexGridProps. + */ +type DisallowedShorthandProps = + // Spacing shorthands + | 'p' + | 'pt' + | 'pr' + | 'pb' + | 'pl' + | 'px' + | 'py' + | 'm' + | 'mt' + | 'mr' + | 'mb' + | 'ml' + | 'mx' + | 'my' + // Size shorthands + | 'w' + | 'h' + | 'minW' + | 'maxW' + | 'minH' + | 'maxH' + // Visual shorthands that map to props we have intentionally excluded + | 'bg' + | 'rounded' + | 'shadow'; + +/** + * Flexbox & grid layout props that we explicitly support on Backpack layout + * components. These are a curated subset of the underlying Box flex/grid API + * that are useful for structural layout. + */ +export interface BpkFlexGridProps { + // Flex layout props + display?: BoxProps['display']; + flexDirection?: BoxProps['flexDirection']; + flexWrap?: BoxProps['flexWrap']; + justifyContent?: BoxProps['justifyContent']; + alignItems?: BoxProps['alignItems']; + alignContent?: BoxProps['alignContent']; + + flex?: BoxProps['flex']; + flexGrow?: BoxProps['flexGrow']; + flexShrink?: BoxProps['flexShrink']; + flexBasis?: BoxProps['flexBasis']; + order?: BoxProps['order']; + alignSelf?: BoxProps['alignSelf']; + justifySelf?: BoxProps['justifySelf']; + + // Grid layout props + gridTemplateColumns?: BoxProps['gridTemplateColumns']; + gridTemplateRows?: BoxProps['gridTemplateRows']; + gridTemplateAreas?: BoxProps['gridTemplateAreas']; + gridAutoFlow?: BoxProps['gridAutoFlow']; + gridAutoRows?: BoxProps['gridAutoRows']; + gridAutoColumns?: BoxProps['gridAutoColumns']; + rowGap?: BoxProps['rowGap']; + columnGap?: BoxProps['columnGap']; +} + +export type FlexGridPropKeys = keyof BpkFlexGridProps; + +/** + * Curated subset of Box layout props that support Backpack responsive values. + * + * NOTE: + * - These are structural layout props (flex/grid/display) that we want to allow + * on `BpkBox`, but using Backpack breakpoint keys rather than Chakra's + * array syntax or Chakra breakpoint keys. + * - Spacing/size/position props are handled separately via `BpkCommonLayoutProps`. + */ +type BpkBoxResponsiveLayoutProps = { + // Display + display?: BpkResponsiveValue<BoxProps['display']>; + + // Flex container props + flexDirection?: BpkResponsiveValue<BoxProps['flexDirection']>; + flexWrap?: BpkResponsiveValue<BoxProps['flexWrap']>; + justifyContent?: BpkResponsiveValue<BoxProps['justifyContent']>; + alignItems?: BpkResponsiveValue<BoxProps['alignItems']>; + alignContent?: BpkResponsiveValue<BoxProps['alignContent']>; + + // Flex item props + flex?: BpkResponsiveValue<BoxProps['flex']>; + flexGrow?: BpkResponsiveValue<BoxProps['flexGrow']>; + flexShrink?: BpkResponsiveValue<BoxProps['flexShrink']>; + flexBasis?: BpkResponsiveValue<BoxProps['flexBasis']>; + order?: BpkResponsiveValue<BoxProps['order']>; + alignSelf?: BpkResponsiveValue<BoxProps['alignSelf']>; + justifySelf?: BpkResponsiveValue<BoxProps['justifySelf']>; + + // Grid container props + gridTemplateColumns?: BpkResponsiveValue<BoxProps['gridTemplateColumns']>; + gridTemplateRows?: BpkResponsiveValue<BoxProps['gridTemplateRows']>; + gridTemplateAreas?: BpkResponsiveValue<BoxProps['gridTemplateAreas']>; + gridAutoFlow?: BpkResponsiveValue<BoxProps['gridAutoFlow']>; + gridAutoRows?: BpkResponsiveValue<BoxProps['gridAutoRows']>; + gridAutoColumns?: BpkResponsiveValue<BoxProps['gridAutoColumns']>; + + // Grid item placement props (useful on Box when composing grids) + gridColumn?: BpkResponsiveValue<BoxProps['gridColumn']>; + gridRow?: BpkResponsiveValue<BoxProps['gridRow']>; +}; + +type BpkBoxResponsiveLayoutPropKeys = keyof BpkBoxResponsiveLayoutProps; + +/** + * Base type that removes common layout props, reserved props (className, + * children) and all layout-level event props from Chakra UI props. + * + * These will be replaced with Backpack-specific types. + */ +export type RemoveCommonProps<T> = Omit< + T, + | keyof BpkCommonLayoutProps + | 'className' + | 'children' + | LayoutEventProps + | FlexGridPropKeys + | DisallowedShorthandProps +>; + +/** + * Component-specific props for BpkBox + * Includes all Box props except those in BpkCommonLayoutProps + */ +export interface BpkBoxSpecificProps + extends Omit<RemoveCommonProps<BoxProps>, BpkBoxResponsiveLayoutPropKeys>, + BpkBoxResponsiveLayoutProps, + Omit<BpkFlexGridProps, BpkBoxResponsiveLayoutPropKeys> {} + +/** + * Props for BpkBox component + * Combines Box-specific props with Backpack common layout props + * and reintroduces a minimal set of interaction props. + */ +type BoxEventProps = Pick<BoxProps, + 'onClick' | 'onFocus' | 'onBlur' +>; + +export interface BpkBoxProps extends BpkCommonLayoutProps, BpkBoxSpecificProps { + children?: ReactNode; + onClick?: BoxEventProps['onClick']; + onFocus?: BoxEventProps['onFocus']; + onBlur?: BoxEventProps['onBlur']; +} + +/** + * Component-specific props for BpkFlex + * Includes all Flex props except those in BpkCommonLayoutProps + */ +export interface BpkFlexSpecificProps extends RemoveCommonProps<FlexProps> { + direction?: BpkResponsiveValue<FlexProps['flexDirection']>; + justify?: BpkResponsiveValue<FlexProps['justifyContent']>; + align?: BpkResponsiveValue<FlexProps['alignItems']>; + wrap?: BpkResponsiveValue<FlexProps['flexWrap']>; + grow?: BpkResponsiveValue<FlexProps['flexGrow']>; + shrink?: BpkResponsiveValue<FlexProps['flexShrink']>; + basis?: BpkResponsiveValue<BpkBasisValue>; + inline?: boolean; +} + +/** + * Props for BpkFlex component + * Combines Flex-specific props with Backpack common layout props + */ +export interface BpkFlexProps extends BpkCommonLayoutProps, BpkFlexSpecificProps { + children?: ReactNode; +} + +/** + * Component-specific props for BpkGrid + * Includes all Grid props except those in BpkCommonLayoutProps + */ +export interface BpkGridSpecificProps extends RemoveCommonProps<GridProps> { + justify?: BpkResponsiveValue<GridProps['justifyContent']>; + align?: BpkResponsiveValue<GridProps['alignItems']>; + templateColumns?: BpkResponsiveValue<GridProps['gridTemplateColumns']>; + templateRows?: BpkResponsiveValue<GridProps['gridTemplateRows']>; + templateAreas?: BpkResponsiveValue<GridProps['gridTemplateAreas']>; + autoFlow?: BpkResponsiveValue<GridProps['gridAutoFlow']>; + autoRows?: BpkResponsiveValue<GridProps['gridAutoRows']>; + autoColumns?: BpkResponsiveValue<GridProps['gridAutoColumns']>; + rowGap?: BpkResponsiveValue<BpkSpacingValue>; + columnGap?: BpkResponsiveValue<BpkSpacingValue>; + column?: BpkResponsiveValue<GridProps['gridColumn']>; + row?: BpkResponsiveValue<GridProps['gridRow']>; + inline?: boolean; +} + +/** + * Props for BpkGrid component + * Combines Grid-specific props with Backpack common layout props + */ +export interface BpkGridProps extends BpkCommonLayoutProps, BpkGridSpecificProps { + children?: ReactNode; +} + +/** + * Component-specific props for BpkGridItem + * Includes all GridItem props except those in BpkCommonLayoutProps + */ +export interface BpkGridItemSpecificProps extends RemoveCommonProps<GridItemProps> { + area?: GridItemProps['area']; + colEnd?: GridItemProps['colEnd']; + colStart?: GridItemProps['colStart']; + colSpan?: GridItemProps['colSpan']; + rowEnd?: GridItemProps['rowEnd']; + rowStart?: GridItemProps['rowStart']; + rowSpan?: GridItemProps['rowSpan']; +} + +/** + * Props for BpkGridItem component + * Combines GridItem-specific props with Backpack common layout props + */ +export interface BpkGridItemProps extends BpkCommonLayoutProps, BpkGridItemSpecificProps { + children?: ReactNode; +} + +// ---- Stack (moved from BpkStack.types.ts) ---- +type StackOptionKeysType = typeof StackOptionKeys[number]; + +/** + * Overrides StackOptions to support BpkResponsiveValue + */ +type BpkStackOptions = { + [K in StackOptionKeysType]?: K extends keyof StackProps + ? BpkResponsiveValue<StackProps[K]> | StackProps[K] + : never; +}; + +/** + * Component-specific props for BpkStack + * Includes all Stack props except those in BpkCommonLayoutProps + * Overrides StackOptions to support BpkResponsiveValue + */ +export interface BpkStackSpecificProps + extends Omit<RemoveCommonProps<StackProps>, StackOptionKeysType>, + BpkStackOptions, + BpkFlexGridProps {} + +/** + * Props for BpkStack component + * Combines Stack-specific props with Backpack common layout props + */ +export interface BpkStackProps extends BpkCommonLayoutProps, BpkStackSpecificProps { + children?: ReactNode; +} + +export type { BpkCommonLayoutProps }; diff --git a/packages/package-lock.json b/packages/package-lock.json index 5324e6b282..a226cf0915 100644 --- a/packages/package-lock.json +++ b/packages/package-lock.json @@ -9,6 +9,7 @@ "version": "21.0.1", "license": "Apache-2.0", "dependencies": { + "@chakra-ui/react": "^3.30.0", "@floating-ui/react": "^0.26.12", "@popperjs/core": "^2.11.8", "@radix-ui/react-compose-refs": "^1.1.1", @@ -50,6 +51,173 @@ } } }, + "node_modules/@ark-ui/react": { + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/@ark-ui/react/-/react-5.29.1.tgz", + "integrity": "sha512-HY6plob4CuDBMXqeYBSqjDzKziWoiTb5atDjBEw+jJIfwRdZcChdRHm1IPCFZ9LiQ5toa67748JFzo683UzqVg==", + "license": "MIT", + "dependencies": { + "@internationalized/date": "3.10.0", + "@zag-js/accordion": "1.29.1", + "@zag-js/anatomy": "1.29.1", + "@zag-js/angle-slider": "1.29.1", + "@zag-js/async-list": "1.29.1", + "@zag-js/auto-resize": "1.29.1", + "@zag-js/avatar": "1.29.1", + "@zag-js/bottom-sheet": "1.29.1", + "@zag-js/carousel": "1.29.1", + "@zag-js/checkbox": "1.29.1", + "@zag-js/clipboard": "1.29.1", + "@zag-js/collapsible": "1.29.1", + "@zag-js/collection": "1.29.1", + "@zag-js/color-picker": "1.29.1", + "@zag-js/color-utils": "1.29.1", + "@zag-js/combobox": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/date-picker": "1.29.1", + "@zag-js/date-utils": "1.29.1", + "@zag-js/dialog": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/editable": "1.29.1", + "@zag-js/file-upload": "1.29.1", + "@zag-js/file-utils": "1.29.1", + "@zag-js/floating-panel": "1.29.1", + "@zag-js/focus-trap": "1.29.1", + "@zag-js/highlight-word": "1.29.1", + "@zag-js/hover-card": "1.29.1", + "@zag-js/i18n-utils": "1.29.1", + "@zag-js/image-cropper": "1.29.1", + "@zag-js/json-tree-utils": "1.29.1", + "@zag-js/listbox": "1.29.1", + "@zag-js/marquee": "1.29.1", + "@zag-js/menu": "1.29.1", + "@zag-js/number-input": "1.29.1", + "@zag-js/pagination": "1.29.1", + "@zag-js/password-input": "1.29.1", + "@zag-js/pin-input": "1.29.1", + "@zag-js/popover": "1.29.1", + "@zag-js/presence": "1.29.1", + "@zag-js/progress": "1.29.1", + "@zag-js/qr-code": "1.29.1", + "@zag-js/radio-group": "1.29.1", + "@zag-js/rating-group": "1.29.1", + "@zag-js/react": "1.29.1", + "@zag-js/scroll-area": "1.29.1", + "@zag-js/select": "1.29.1", + "@zag-js/signature-pad": "1.29.1", + "@zag-js/slider": "1.29.1", + "@zag-js/splitter": "1.29.1", + "@zag-js/steps": "1.29.1", + "@zag-js/switch": "1.29.1", + "@zag-js/tabs": "1.29.1", + "@zag-js/tags-input": "1.29.1", + "@zag-js/timer": "1.29.1", + "@zag-js/toast": "1.29.1", + "@zag-js/toggle": "1.29.1", + "@zag-js/toggle-group": "1.29.1", + "@zag-js/tooltip": "1.29.1", + "@zag-js/tour": "1.29.1", + "@zag-js/tree-view": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", @@ -62,21 +230,219 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@chakra-ui/react": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-3.30.0.tgz", + "integrity": "sha512-eIRRAilqY4f2zN8GWRnjcciBYsvy3GZDOmzGD9xk596LBxCTNCJaivdBiHCcgNlqA3y1wMyM1jepy2b2vQC4QA==", + "license": "MIT", + "dependencies": { + "@ark-ui/react": "^5.29.1", + "@emotion/is-prop-valid": "^1.4.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@pandacss/is-valid-prop": "^1.4.2", + "csstype": "^3.2.3" + }, + "peerDependencies": { + "@emotion/react": ">=11", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT", + "peer": true + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT", + "peer": true + }, "node_modules/@floating-ui/core": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.5.tgz", - "integrity": "sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.5" + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.8.tgz", - "integrity": "sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.5" + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/react": { @@ -111,9 +477,10 @@ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, "node_modules/@floating-ui/utils": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz", - "integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==" + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" }, "node_modules/@googlemaps/js-api-loader": { "version": "1.16.2", @@ -132,6 +499,69 @@ "supercluster": "^8.0.1" } }, + "node_modules/@internationalized/date": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz", + "integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.5.tgz", + "integrity": "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT", + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@pandacss/is-valid-prop": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@pandacss/is-valid-prop/-/is-valid-prop-1.6.1.tgz", + "integrity": "sha512-adOSTq2JekBkvblToblRtrGjEJUxDTH40HTo5Lm5+l2jygKIb1fXHgwRG6JnO/z1QIA4DaCZftMRyVcOfQhagQ==", + "license": "MIT" + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -379,66 +809,977 @@ } } }, - "node_modules/@react-google-maps/api": { - "version": "2.19.3", - "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.19.3.tgz", - "integrity": "sha512-jiLqvuOt5lOowkLeq7d077AByTyJp+s6hZVlLhlq7SBacBD37aUNpXBz2OsazfeR6Aw4a+9RRhAEjEFvrR1f5A==", + "node_modules/@react-google-maps/api": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.19.3.tgz", + "integrity": "sha512-jiLqvuOt5lOowkLeq7d077AByTyJp+s6hZVlLhlq7SBacBD37aUNpXBz2OsazfeR6Aw4a+9RRhAEjEFvrR1f5A==", + "dependencies": { + "@googlemaps/js-api-loader": "1.16.2", + "@googlemaps/markerclusterer": "2.5.3", + "@react-google-maps/infobox": "2.19.2", + "@react-google-maps/marker-clusterer": "2.19.2", + "@types/google.maps": "3.55.2", + "invariant": "2.2.4" + }, + "peerDependencies": { + "react": "^16.8 || ^17 || ^18", + "react-dom": "^16.8 || ^17 || ^18" + } + }, + "node_modules/@react-google-maps/infobox": { + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.19.2.tgz", + "integrity": "sha512-6wvBqeJsQ/eFSvoxg+9VoncQvNoVCdmxzxRpLvmjPD+nNC6mHM0vJH1xSqaKijkMrfLJT0nfkTGpovrF896jwg==" + }, + "node_modules/@react-google-maps/marker-clusterer": { + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.19.2.tgz", + "integrity": "sha512-x9ibmsP0ZVqzyCo1Pitbw+4b6iEXRw/r1TCy3vOUR3eKrzWLnHYZMR325BkZW2r8fnuWE/V3Fp4QZOP9qYORCw==" + }, + "node_modules/@skyscanner/bpk-foundations-common": { + "version": "24.0.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@skyscanner/bpk-foundations-common/-/bpk-foundations-common-24.0.0.tgz", + "integrity": "sha512-Ye5sRBhi3vrpOZOjxRiziLDghttmGEwTZuPA5+FOwX5d75DDSfxIQ3YMUMZzZN8WEeTUJO3Y5oB/69y2GGmAUw==", + "license": "Apache-2.0", + "dependencies": { + "color": "^5.0.0" + } + }, + "node_modules/@skyscanner/bpk-foundations-web": { + "version": "24.0.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@skyscanner/bpk-foundations-web/-/bpk-foundations-web-24.0.0.tgz", + "integrity": "sha512-YRYtS6kJScToJH7UxJbZgFcKEA4qzlb7fY/jNES82/QyQApFRdTkbcHkRhh+bCnvMJdqacGVhzt5sHTPjY4l8Q==", + "license": "Apache-2.0", + "dependencies": { + "@skyscanner/bpk-foundations-common": "^24.0.0", + "color": "^5.0.0" + } + }, + "node_modules/@skyscanner/bpk-svgs": { + "version": "20.11.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@skyscanner/bpk-svgs/-/bpk-svgs-20.11.0.tgz", + "integrity": "sha512-nSYoZyYCiMEGLZSu1mbPJKHqlnWcK9wnVOOB8ZfsExl7/sn3aFUJZRYjxThFyml//5g8zw0TeV8Q4UfRMctVXg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.16.0", + "npm": ">=9.5.1" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/google.maps": { + "version": "3.55.2", + "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.55.2.tgz", + "integrity": "sha512-JcTwzkxskR8DN/nnX96Pie3gGN3WHiPpuxzuQ9z3516o1bB243d8w8DHUJ8BohuzoT1o3HUFta2ns/mkZC8KRw==" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT", + "peer": true + }, + "node_modules/@zag-js/accordion": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/accordion/-/accordion-1.29.1.tgz", + "integrity": "sha512-3laCyoAsInYPooQU5+tgwxiejU25M20etHbbZ6FIql8VRhKemYakpLaVdcXoFQXpwnnsVfyRv88fHYse+eR8vQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/anatomy": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/anatomy/-/anatomy-1.29.1.tgz", + "integrity": "sha512-Yq2E/32mwh4MxQ5jeP3NlweoqsO6Q2UFawyrCwyzbOUovbcoC74H4/2i/qjVlhpfEuVRRWDiqn31z/OWc4w3dw==", + "license": "MIT" + }, + "node_modules/@zag-js/angle-slider": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/angle-slider/-/angle-slider-1.29.1.tgz", + "integrity": "sha512-U+6ihVRiFSFodJSbJXTxsyH697bvmYoGLRjo7w14B2WBumbKxa/tXXPuUZdS5MBfJHKo1XUwX1HKQpBmSX8WWA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/rect-utils": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/aria-hidden": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/aria-hidden/-/aria-hidden-1.29.1.tgz", + "integrity": "sha512-Q8JRvyOjEplKv4xjrJvHvvaGCc/8wa29B7vxck1QBcLqtzSxI003WeFg7fYf4J9NxQmKuFx9iwoh/iD4JmLIbw==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.29.1" + } + }, + "node_modules/@zag-js/async-list": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/async-list/-/async-list-1.29.1.tgz", + "integrity": "sha512-0PVllpwxt9ZT8wSwQiARq4eLj7SKJg2y5TwczgytV89TUezQLYYnLW5K7A8+3YxDDbsEsN5qArdAoZ8azkvkhA==", + "license": "MIT", + "dependencies": { + "@zag-js/core": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/auto-resize": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/auto-resize/-/auto-resize-1.29.1.tgz", + "integrity": "sha512-ZAUqd3Mj9J9/SoeAJw9QtWAQgyf/66I2mXfVBIQK5VpgeDzOZ+J75zOaKr1h0abVlvi001+fFBMDj7N8MmqgTA==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.29.1" + } + }, + "node_modules/@zag-js/avatar": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/avatar/-/avatar-1.29.1.tgz", + "integrity": "sha512-dkL6kk4Q4BvhJ6gDF+lb6rpmLkbFahFbXHyekDWQ003Ud+uW+MR3jIqIPuNnrKeGxts8Cl5q7ieI3sCneTWXyg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/bottom-sheet": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/bottom-sheet/-/bottom-sheet-1.29.1.tgz", + "integrity": "sha512-LaXGuu9jw1k5+/sWHk9XWcusykTVDT00fqRRmeVIL32BrgZF0o4286QvUWZrW3vyOLT4nJZVBIsuSz/4nSEqSw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/aria-hidden": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dismissable": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/focus-trap": "1.29.1", + "@zag-js/remove-scroll": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/carousel": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/carousel/-/carousel-1.29.1.tgz", + "integrity": "sha512-Duyt9pTOWqoTX++XOfoZCsdb5MsPOybnQ0DQZz61jApsyKwd9C6I361az3nkTm7uMgq2T1/pk5Zd3YgBQLxjGg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/scroll-snap": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/checkbox": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/checkbox/-/checkbox-1.29.1.tgz", + "integrity": "sha512-+dWWLRzOVzuIdJ3BkO6zi525umeKx1/tlq3WnRR5ok5bGN/zSYWWUFl/bctWlTCuLO9sMpraHEnHZzYnjJoY/A==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/focus-visible": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/clipboard": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/clipboard/-/clipboard-1.29.1.tgz", + "integrity": "sha512-oYIokwwgOr6a4v33l+AS2pao9yxDpwESu/p3oRbO2fNVPrbUVLj3b4pct+UJt2sR+CWAHl1d4QRI+DLmG2ybPQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/collapsible": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/collapsible/-/collapsible-1.29.1.tgz", + "integrity": "sha512-g7iIMLHHYVnR729jZ7ZeQsldvpFcSUOeNAFyeFYhsWdAl+NoRhlNkeH5sAFxIT115s2FKJOOWEbPeu8xgVSgNg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/collection": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/collection/-/collection-1.29.1.tgz", + "integrity": "sha512-Yz1ElOm56as/IRRh9lW2eTndHeHBaxVNjS0cGTWFmrSOTdjY4+ilTcHTv3FtyUw5sZurChEgKmFs7oUbHm7RaA==", + "license": "MIT", + "dependencies": { + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/color-picker": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/color-picker/-/color-picker-1.29.1.tgz", + "integrity": "sha512-hxEt2fM0o8t2lw+Lt8qIGFEk0v5u/kc+MkF0RpBACtRjN7+xZ4pm6WOe6a1cW1NUa+VbHlKXfalst+hnEYML2A==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/color-utils": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dismissable": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/popper": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/color-utils": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/color-utils/-/color-utils-1.29.1.tgz", + "integrity": "sha512-FZCvvjzyA2vkbX9ifv6xF+oL7M2vNmFEAgWpVDy9O671ofEvb/yryjxHBpK3wcTMcJwbFORC5hsDMbX2Tw5MTg==", + "license": "MIT", + "dependencies": { + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/combobox": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/combobox/-/combobox-1.29.1.tgz", + "integrity": "sha512-7w5XFjjk/kp/8kDbPe3rw4G/zTAKtH4H6e7xvl6Bo5kpEJw/aq7yt05o8tAa2WNqT+491aXiQePYqr5PkPpGgQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/aria-hidden": "1.29.1", + "@zag-js/collection": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dismissable": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/popper": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/core": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/core/-/core-1.29.1.tgz", + "integrity": "sha512-5Qw3VbLo+jqqyXrUon/LIqJT/+SGHwx5sI1/qseOZBqYj46oabM/WiEoRztFq+FDJuL9VeHnVD6WB683Si5qwg==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/date-picker": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/date-picker/-/date-picker-1.29.1.tgz", + "integrity": "sha512-uus+kuZ+dEHfGYr3QukIkVzYB/skh2EWnlDk/3hOAEw8KSzi3GQzpRIJFfGWaVoFBGvXvLRf8Vj/4ufrfLSsoQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/date-utils": "1.29.1", + "@zag-js/dismissable": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/live-region": "1.29.1", + "@zag-js/popper": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + }, + "peerDependencies": { + "@internationalized/date": ">=3.0.0" + } + }, + "node_modules/@zag-js/date-utils": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/date-utils/-/date-utils-1.29.1.tgz", + "integrity": "sha512-NLEMNs2tRxRoJsobqajwAb+zuhx69MuA1UA1SxJAoauM6p8MulX8bJ4aqd3ZDPKlkGQbXu6e62fuTRkbjJDRXw==", + "license": "MIT", + "peerDependencies": { + "@internationalized/date": ">=3.0.0" + } + }, + "node_modules/@zag-js/dialog": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/dialog/-/dialog-1.29.1.tgz", + "integrity": "sha512-fDNgeXqpY576L/PtRQn08XscY1nrL4jBvpw9JGq/w/PWeicM7K+kM9gnoEBz5MB7W+bMR+11AJXz/iKGE1GBzA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/aria-hidden": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dismissable": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/focus-trap": "1.29.1", + "@zag-js/remove-scroll": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/dismissable": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/dismissable/-/dismissable-1.29.1.tgz", + "integrity": "sha512-4EsVsPudQ17KaInrLQdeZyU8apjzXinfPjgSNBR7CPMU60O0J/zV9mXbn4lwXEE5Hy35lXq8s4V+W6wD0CwbLg==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.29.1", + "@zag-js/interact-outside": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/dom-query": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/dom-query/-/dom-query-1.29.1.tgz", + "integrity": "sha512-GGN+Kt/+J9eiPeEqU+PsRYoNoRdFTNYP2ENCCaBSeypCsaxaG4wo99nbsoBwJwhr/c8zeUmULErgrGGoSh0F1Q==", + "license": "MIT", + "dependencies": { + "@zag-js/types": "1.29.1" + } + }, + "node_modules/@zag-js/editable": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/editable/-/editable-1.29.1.tgz", + "integrity": "sha512-NpZNRF0cF1AA9OHQpIpU4Jlo4hSPomZ5FpMWmVX4kXbo49YywkPfSDgFCdcsGUIyTLXCmfirI9PWRP4B2IxlVw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/interact-outside": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/file-upload": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/file-upload/-/file-upload-1.29.1.tgz", + "integrity": "sha512-n321mbdiE6yeUvfDr6sTKxQMJz/BHDvYJvyCaO+MirXdrD80iSop7u4/caekqBFcerxtXg4FcjpPl1fvCGHr+w==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/file-utils": "1.29.1", + "@zag-js/i18n-utils": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/file-utils": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/file-utils/-/file-utils-1.29.1.tgz", + "integrity": "sha512-nS6549/SkqFldlheXWSMiT+4NMVyB9PMg1DII36JANjgfoceVN/jBM21a6u7CssdpNnSYwqnD4Ozjeqkb3ZO5Q==", + "license": "MIT", + "dependencies": { + "@zag-js/i18n-utils": "1.29.1" + } + }, + "node_modules/@zag-js/floating-panel": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/floating-panel/-/floating-panel-1.29.1.tgz", + "integrity": "sha512-fcUKp0NfbTijU8FyA9BI3qNM/YlwFuuS8ixghiaweT/GlbJF9YUlyWzLXKE24I3rE+o0ykq53NEHdQGTco/2hg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/popper": "1.29.1", + "@zag-js/rect-utils": "1.29.1", + "@zag-js/store": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/focus-trap": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/focus-trap/-/focus-trap-1.29.1.tgz", + "integrity": "sha512-dDp/nuptTp1OJbEjSkLPNy6DxOSfYHKX292uvBV80xyLZUQ4s38wi8VCOuywpgF607WYIRozHI5PB8kaoz0sWA==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.29.1" + } + }, + "node_modules/@zag-js/focus-visible": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-1.29.1.tgz", + "integrity": "sha512-3zkxNQ0Gx8Xp45y7tfwqZZfJWLYwZhf9rEeMJT49InR9egWqtHCw/RjOQGR/2vydrPv7mfa14ikY/Gql2AX4TQ==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.29.1" + } + }, + "node_modules/@zag-js/highlight-word": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/highlight-word/-/highlight-word-1.29.1.tgz", + "integrity": "sha512-54FVVE4NlixIzUTpaJvR7O+fNg9jJomWr3F3LoOkgaKJYuRxitHp1hLmSsdjxRkusMs+1qNHsYN4E9lWNv7kow==", + "license": "MIT" + }, + "node_modules/@zag-js/hover-card": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/hover-card/-/hover-card-1.29.1.tgz", + "integrity": "sha512-neKWMHaxL5yIno2BrbhUPm1zQD1o0+ydoYNoUucFDxexZQwcrjORwsgeBfYP6cle6Ne0Aw6OsSE4xowR9LEZVA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dismissable": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/popper": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/i18n-utils": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/i18n-utils/-/i18n-utils-1.29.1.tgz", + "integrity": "sha512-c1N5evLLkQpGizPZ8HSek14gaOJgRr7/vlXwWlaC1aSaGrRjZmi/YMmuTThCP4nja/6zKPNg9NJMbuwi/o3UTw==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.29.1" + } + }, + "node_modules/@zag-js/image-cropper": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/image-cropper/-/image-cropper-1.29.1.tgz", + "integrity": "sha512-Xgwt/GwGZ8dT4fM/CRrSZhBhDIWdJiBlsCxp2vz1d9v/6Wju2uVtcM8iaeKUjKZ2NXsnEXTi6/gexlqyeuRjTQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/interact-outside": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/interact-outside/-/interact-outside-1.29.1.tgz", + "integrity": "sha512-hqZYr+OcnW+egU8W297pVK+6YMa+HOyFA0GHF45+29cB+mmTnMPTRcrdqNDFKA+f+ABQl3RH32E1WZjkluJc6A==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/json-tree-utils": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/json-tree-utils/-/json-tree-utils-1.29.1.tgz", + "integrity": "sha512-SKHXFDh92iFUaU/pIgL3j03L/OJMvF+ZiUVY9bitHdBxHE9aJX4ZjdjArYnQIUX3KIFhb4hkyfuW3mxLtvTfGA==", + "license": "MIT" + }, + "node_modules/@zag-js/listbox": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/listbox/-/listbox-1.29.1.tgz", + "integrity": "sha512-UShb0caYtLshSHIwnVWz9QOvzm6WDb5+uogNHObt+6ALk77TZfKDxl29jmQ6/14H9ErYHLVsA6akschIaBswUg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/collection": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/focus-visible": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/live-region": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/live-region/-/live-region-1.29.1.tgz", + "integrity": "sha512-6+e5BQdzj/nuIK4Uxr3Tv5tKR9X3wP8DbLZPhAVF78XYPamuO19NhRjV4ph6Sp3Jme9gjP8BbaPGyXN4D6lDhA==", + "license": "MIT" + }, + "node_modules/@zag-js/marquee": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/marquee/-/marquee-1.29.1.tgz", + "integrity": "sha512-dGyQCPHvwhzVxGKyugqMzvhA5/1d8PS+OoNPxDo1ozKrvNvcsEtDG6lsNMy+jolllthw+m87pcqhA1AHZvpe9Q==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/menu": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/menu/-/menu-1.29.1.tgz", + "integrity": "sha512-+L/J+nHlw0N3vwDqGFm7KAu3sbC0l4OVPziTjInlvrliwFbmMX86g17sVKvD/Ke/yc3YvTtJt48AAhidK1EWtA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dismissable": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/popper": "1.29.1", + "@zag-js/rect-utils": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/number-input": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/number-input/-/number-input-1.29.1.tgz", + "integrity": "sha512-tme/FOl+jdPy0lYiKo60XdIYheAmfNXPvGb2W4SQtPO2YT3mESdPC/TpCCOVvgIY93k5+5aa8MLEX6GJsTjL+A==", + "license": "MIT", + "dependencies": { + "@internationalized/number": "3.6.5", + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/pagination": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/pagination/-/pagination-1.29.1.tgz", + "integrity": "sha512-7KKCdUKPQNK6VuroRfxmxpNcWpuAUy6ZFvMUnaYFFBmCB7FGkOUAO1yEsYuJ9diAZvfprqw+8xnL5g93Xx/RtQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/password-input": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/password-input/-/password-input-1.29.1.tgz", + "integrity": "sha512-fbHzf2r3nW32ANj+/3SFKXLh6RYNe1udPPje8VlTmAgBPFKQ7f57S/G26EaFZHU7651B1VFzpJl1ERfnIty9UA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/pin-input": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/pin-input/-/pin-input-1.29.1.tgz", + "integrity": "sha512-i9umQG1QEH4RmX9U+YGj0YiBjb7q8jRRC1OtKUJj5vesHAN553eg0WLbHcmfgyF6NwfM73/S+0JRJ9v92neWWw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/popover": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/popover/-/popover-1.29.1.tgz", + "integrity": "sha512-MQ83k6JmvnvbvExZUvytNDUFZN7e4HHMdpq9meT5z1K+D9HaQ+gatHNk76cvv0H+yO+q90DDs5OUQ4ulzK/u2A==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/aria-hidden": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dismissable": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/focus-trap": "1.29.1", + "@zag-js/popper": "1.29.1", + "@zag-js/remove-scroll": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/popper": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/popper/-/popper-1.29.1.tgz", + "integrity": "sha512-elVi8eWMMrmOvtv627cc3+1bAeKM1VIrB4enpd6ccponXcPosaSTXHMR+lSxy9uOWaHZ/GkqYs+fWzguUJznSA==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "1.7.4", + "@zag-js/dom-query": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/presence": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/presence/-/presence-1.29.1.tgz", + "integrity": "sha512-xJj9BT5YX2Pb7VnrABYXrU35BOoiM5yT9Y1baGqfQLkginZ+Cp2CwszL6856f2ZUw3xnxBfDsSTPznoH+p9Z7w==", + "license": "MIT", + "dependencies": { + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1" + } + }, + "node_modules/@zag-js/progress": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/progress/-/progress-1.29.1.tgz", + "integrity": "sha512-UxyfFl+7dKKIqVxbyDjlXnAQSQt5gx0tWP7pt3KWuz6PSdU23fpq1dgv4YYBl8rX5EjX81B9uykE3WP8TRsz2w==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/qr-code": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/qr-code/-/qr-code-1.29.1.tgz", + "integrity": "sha512-n8EpfB0QVN2AhhSQZEN3jfqnsuXmeW5jH7e7TA8as2RMYZXx1dSQLF1fiaKtx8VlS6/mKfMjokZqnhOGtIOXzw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1", + "proxy-memoize": "3.0.1", + "uqr": "0.1.2" + } + }, + "node_modules/@zag-js/radio-group": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/radio-group/-/radio-group-1.29.1.tgz", + "integrity": "sha512-KFgF+8T+0nT6igPdCGmpsU5KxVsJVIsseVuABl3/IY679FZog0wAitbCHu9j/QoZxuS/kXj1eD2SbG/+92eDLQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/focus-visible": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/rating-group": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/rating-group/-/rating-group-1.29.1.tgz", + "integrity": "sha512-Vcqv9FvsxCGaIVlA9LucDiLbttLapyil8Jc8KpKLAODsj1FSVVwgK50AkJnLw7n7SRoD+zx8HTIB1txfT9AQiw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/react": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/react/-/react-1.29.1.tgz", + "integrity": "sha512-nvy7BruQojqQ0GLpHbP1BewJXVdqBLOkSzA2JA1BNRCCN19hZ8qCvpjAhZPYXoq1t9eecOju7K33lBFjpck9KA==", + "license": "MIT", + "dependencies": { + "@zag-js/core": "1.29.1", + "@zag-js/store": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@zag-js/rect-utils": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/rect-utils/-/rect-utils-1.29.1.tgz", + "integrity": "sha512-3gxfOQb6JlxSbhoX7ULax79gRA3mz9U7A9MduG0GAABgbIXp8SIawNMQBd+ZjfXjVOGeEoA8bEVvDsWnpQ5SIw==", + "license": "MIT" + }, + "node_modules/@zag-js/remove-scroll": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/remove-scroll/-/remove-scroll-1.29.1.tgz", + "integrity": "sha512-qv/Ipa0apWE20BMTGfvigSOgPn930fXRsdKvMMuJVzaamoGkubfcs1h3HkNG1g/IB1Bx4N7GwD6oWiCMaeHdlQ==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.29.1" + } + }, + "node_modules/@zag-js/scroll-area": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/scroll-area/-/scroll-area-1.29.1.tgz", + "integrity": "sha512-IVrX6GidcHSmxlTMCBRnQLyOwt6JFrwSlrXB3NptSO72OXk/Lm8GSXAQwek8ijmCHDQtbjHWDLufG5ACEvMNaQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/scroll-snap": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/scroll-snap/-/scroll-snap-1.29.1.tgz", + "integrity": "sha512-M/fZDx1IGB6D1IWhouR06q7XAYxpv85ag8Gvz+JVXG4mpo6UBg6t4Ur+DJ4CEfS6KyNmR8pnImZ4aoqmkhiMag==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.29.1" + } + }, + "node_modules/@zag-js/select": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/select/-/select-1.29.1.tgz", + "integrity": "sha512-LtQqZ2Psu6x8LmJhJh5RI0H8imgzmXCvupaGXIm3SDbKhnmT561RHVeupi5KUaz4OUN/qz3FSMVZzpex5ndfAw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/collection": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dismissable": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/popper": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/signature-pad": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/signature-pad/-/signature-pad-1.29.1.tgz", + "integrity": "sha512-N+ej4a99voyR+Xm5w4ma0DsDoSEP/nYrwL9mYSik02/rZs/qPz5ve+qbuUJkLeuzNa3gvzoZhaaVjZb9IuyQbw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1", + "perfect-freehand": "^1.2.2" + } + }, + "node_modules/@zag-js/slider": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/slider/-/slider-1.29.1.tgz", + "integrity": "sha512-BHT3GqM54TjnzuqJfVjcreDFfkXLQNKXBKdTRKQtOkSNsQ7M9Lci8UBHn4WcvQJN5RZ37zsc+Z7zHfHEe/1KSg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/splitter": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/splitter/-/splitter-1.29.1.tgz", + "integrity": "sha512-Ky5xddGoSxhinNl4XuJRCWfBYsV4JVPZ7k/o49KZb1+dtD2gGyKW7aJmFV7oGAtB3TBm96CTNsC/vraGVJrr/A==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/steps": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/steps/-/steps-1.29.1.tgz", + "integrity": "sha512-Bd6Fx1jii9SWjweKISjRh2Wi8OdZJgreH71gNOAjY7BlANhBD+V/euaGX2CwrQXNh1UnBYXYpy664p5aQbkbjg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/store": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/store/-/store-1.29.1.tgz", + "integrity": "sha512-SDyYek8BRtsRPz/CbxmwlXt6B0j6rCezeZN6uAswE4kkmO4bfAjIErrgnImx3TqfjMXlTm4oFUFqeqRJpdnJRg==", + "license": "MIT", + "dependencies": { + "proxy-compare": "3.0.1" + } + }, + "node_modules/@zag-js/switch": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/switch/-/switch-1.29.1.tgz", + "integrity": "sha512-/Ztm/QDAQBFDcERadobfDuJufXHCBqPh/Mmuau1OTeZ+6EfwRCsPOzHsPmKUpQHOqerMXkYvDbFkNHjS7pfAYg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/focus-visible": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/tabs": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/tabs/-/tabs-1.29.1.tgz", + "integrity": "sha512-aicopH3c9Nf+HiybboNPtpdL7iNue48BJn4buBm/6cJ+6Xw/rqHaPpodayS2JNWro7tVdT2erf5+My/sD96MUg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/tags-input": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/tags-input/-/tags-input-1.29.1.tgz", + "integrity": "sha512-izj0IVpBIRKGvd/RlO5zhupmZIHhlH96hBSWNQ1jwETmJRFnsV8RihyQ4P5XzQ9pfFlQozff58YoffunHk2KsA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/auto-resize": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/interact-outside": "1.29.1", + "@zag-js/live-region": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/timer": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/timer/-/timer-1.29.1.tgz", + "integrity": "sha512-v2pFcO7VHlVFdRXkW6zRNWt7VWArxbpD3id2MkaRWQ2FOi1kFfvOD/Vyy0pG5ymreclULgP9Mm1P22Fg8r++JA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } + }, + "node_modules/@zag-js/toast": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/toast/-/toast-1.29.1.tgz", + "integrity": "sha512-x3gTqe9bRcqEnfwCFlugFmde5n0sYqHw01zNrp38s9zi4OZ8zeUJLK1tF0JSmEWClXECjV25E3V4Fm1ECRgRsA==", + "license": "MIT", "dependencies": { - "@googlemaps/js-api-loader": "1.16.2", - "@googlemaps/markerclusterer": "2.5.3", - "@react-google-maps/infobox": "2.19.2", - "@react-google-maps/marker-clusterer": "2.19.2", - "@types/google.maps": "3.55.2", - "invariant": "2.2.4" - }, - "peerDependencies": { - "react": "^16.8 || ^17 || ^18", - "react-dom": "^16.8 || ^17 || ^18" + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dismissable": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" } }, - "node_modules/@react-google-maps/infobox": { - "version": "2.19.2", - "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.19.2.tgz", - "integrity": "sha512-6wvBqeJsQ/eFSvoxg+9VoncQvNoVCdmxzxRpLvmjPD+nNC6mHM0vJH1xSqaKijkMrfLJT0nfkTGpovrF896jwg==" + "node_modules/@zag-js/toggle": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/toggle/-/toggle-1.29.1.tgz", + "integrity": "sha512-pWjHq19RASVOmVi+S34pftBwCVZX676BZEgn/JmVq93Zn8VtOZRzqtRfgeios15Q+1acJkW0EmEZZW38CAQ7cQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } }, - "node_modules/@react-google-maps/marker-clusterer": { - "version": "2.19.2", - "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.19.2.tgz", - "integrity": "sha512-x9ibmsP0ZVqzyCo1Pitbw+4b6iEXRw/r1TCy3vOUR3eKrzWLnHYZMR325BkZW2r8fnuWE/V3Fp4QZOP9qYORCw==" + "node_modules/@zag-js/toggle-group": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/toggle-group/-/toggle-group-1.29.1.tgz", + "integrity": "sha512-Yava/DsXl7zRN0zPjVw4NO9HBh3cFEIyW0GXcm6BCmBpoD3eLUktUHskeCAIxnErLhAcL5NxZwAmt4+FB60Nsw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" + } }, - "node_modules/@skyscanner/bpk-foundations-common": { - "version": "24.0.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@skyscanner/bpk-foundations-common/-/bpk-foundations-common-24.0.0.tgz", - "integrity": "sha512-Ye5sRBhi3vrpOZOjxRiziLDghttmGEwTZuPA5+FOwX5d75DDSfxIQ3YMUMZzZN8WEeTUJO3Y5oB/69y2GGmAUw==", - "license": "Apache-2.0", + "node_modules/@zag-js/tooltip": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/tooltip/-/tooltip-1.29.1.tgz", + "integrity": "sha512-oKtfLEPwoX1PERVknfQjBh6H6IQRMeQjF+cmyf7ix0vSbPjCMx7ZniyRzeujk/4McG9HISnhRvkQCReiBiDMiA==", + "license": "MIT", "dependencies": { - "color": "^5.0.0" + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/focus-visible": "1.29.1", + "@zag-js/popper": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" } }, - "node_modules/@skyscanner/bpk-foundations-web": { - "version": "24.0.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@skyscanner/bpk-foundations-web/-/bpk-foundations-web-24.0.0.tgz", - "integrity": "sha512-YRYtS6kJScToJH7UxJbZgFcKEA4qzlb7fY/jNES82/QyQApFRdTkbcHkRhh+bCnvMJdqacGVhzt5sHTPjY4l8Q==", - "license": "Apache-2.0", + "node_modules/@zag-js/tour": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/tour/-/tour-1.29.1.tgz", + "integrity": "sha512-wjqSN+iMD5GomNVOc/bKOleCGbxGxErxtbKPXqQpqheADHXm1wl55O4gl2QpOsJuLRUiXhS8YJn2efULRPEA9g==", + "license": "MIT", "dependencies": { - "@skyscanner/bpk-foundations-common": "^24.0.0", - "color": "^5.0.0" + "@zag-js/anatomy": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dismissable": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/focus-trap": "1.29.1", + "@zag-js/interact-outside": "1.29.1", + "@zag-js/popper": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" } }, - "node_modules/@skyscanner/bpk-svgs": { - "version": "20.11.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@skyscanner/bpk-svgs/-/bpk-svgs-20.11.0.tgz", - "integrity": "sha512-nSYoZyYCiMEGLZSu1mbPJKHqlnWcK9wnVOOB8ZfsExl7/sn3aFUJZRYjxThFyml//5g8zw0TeV8Q4UfRMctVXg==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.16.0", - "npm": ">=9.5.1" + "node_modules/@zag-js/tree-view": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/tree-view/-/tree-view-1.29.1.tgz", + "integrity": "sha512-0QMKpVY5xXq6sLf4aYgIHUMbtnmuhOgkQLYkEqN3rVnEfZRIr7YeIlLtPPad+oY8VetHRTBe4EfM80yrFHviLQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.29.1", + "@zag-js/collection": "1.29.1", + "@zag-js/core": "1.29.1", + "@zag-js/dom-query": "1.29.1", + "@zag-js/types": "1.29.1", + "@zag-js/utils": "1.29.1" } }, - "node_modules/@types/google.maps": { - "version": "3.55.2", - "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.55.2.tgz", - "integrity": "sha512-JcTwzkxskR8DN/nnX96Pie3gGN3WHiPpuxzuQ9z3516o1bB243d8w8DHUJ8BohuzoT1o3HUFta2ns/mkZC8KRw==" + "node_modules/@zag-js/types": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/types/-/types-1.29.1.tgz", + "integrity": "sha512-/TVhGOxfakEF0IGA9s9Z+5hhzB5PJhLiGsr+g+nj8B2cpZM4HMQGi1h5N2EDXzTTRVEADqCB9vHwL4nw9gsBIw==", + "license": "MIT", + "dependencies": { + "csstype": "3.1.3" + } + }, + "node_modules/@zag-js/types/node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/@zag-js/utils": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@zag-js/utils/-/utils-1.29.1.tgz", + "integrity": "sha512-qxGlQPcNn9QeP/F/KynnP2aPPUhjfVM0FrEiTzRTnt62kF+aLJBoYmLzoSnU8WqUq7dW5El71POW6lYyI7WQkg==", + "license": "MIT" }, "node_modules/a11y-focus-scope": { "version": "1.1.3", @@ -454,6 +1795,32 @@ "resolved": "https://registry.npmjs.org/a11y-focus-store/-/a11y-focus-store-1.0.0.tgz", "integrity": "sha512-N07kBzfvJuQrYFck3C+N7QFrzqaIZB+gVcm9apVGAjDHfaMkPjgex6EpRRz12mGps898Og5AYycH2xDdbbuaLg==" }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "5.0.3", "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/color/-/color-5.0.3.tgz", @@ -506,12 +1873,36 @@ "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", "license": "MIT" }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT", "peer": true }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -602,6 +1993,24 @@ "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==", "peer": true }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -634,16 +2043,96 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "license": "MIT" }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT", + "peer": true + }, "node_modules/focusin": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/focusin/-/focusin-2.0.0.tgz", "integrity": "sha512-yChGflLcqxiImpR1ibm59DjM7YCsyKIFszlmftLy+RNzYrXqiiPCMNtnEaj4DbD0oFj4fp8Jj7GLHYf1JWDvOg==" }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -666,16 +2155,66 @@ "loose-envify": "^1.0.0" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT", + "peer": true + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "peer": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT", + "peer": true + }, "node_modules/kdbush": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==" }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT", + "peer": true + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -707,6 +2246,13 @@ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "peer": true + }, "node_modules/normalize.css": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-4.2.0.tgz", @@ -720,6 +2266,68 @@ "node": ">=0.10.0" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT", + "peer": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/perfect-freehand": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/perfect-freehand/-/perfect-freehand-1.2.2.tgz", + "integrity": "sha512-eh31l019WICQ03pkF3FSzHxB8n07ItqIQ++G5UV8JX0zVOXzgTGCqnRR0jJ2h9U8/2uW4W4mtGJELt9kEV0CFQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC", + "peer": true + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -730,6 +2338,21 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.1.tgz", + "integrity": "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==", + "license": "MIT" + }, + "node_modules/proxy-memoize": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/proxy-memoize/-/proxy-memoize-3.0.1.tgz", + "integrity": "sha512-VDdG/VYtOgdGkWJx7y0o7p+zArSf2383Isci8C+BP3YXgMYDoPd3cCBjw0JdWb6YBb9sFiOPbAADDVTPJnh+9g==", + "license": "MIT", + "dependencies": { + "proxy-compare": "^3.0.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -856,6 +2479,37 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -875,6 +2529,23 @@ "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT", + "peer": true + }, "node_modules/supercluster": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", @@ -883,6 +2554,19 @@ "kdbush": "^4.0.2" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tabbable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-1.1.3.tgz", @@ -893,6 +2577,22 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" + }, + "node_modules/uqr": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/uqr/-/uqr-0.1.2.tgz", + "integrity": "sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==", + "license": "MIT" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">= 6" + } } } } diff --git a/packages/package.json b/packages/package.json index 0d7cdba48c..2536d321f8 100644 --- a/packages/package.json +++ b/packages/package.json @@ -22,6 +22,7 @@ "access": "public" }, "dependencies": { + "@chakra-ui/react": "^3.30.0", "@floating-ui/react": "^0.26.12", "@popperjs/core": "^2.11.8", "@radix-ui/react-compose-refs": "^1.1.1",