@kaizen/components 1.80.2 → 1.80.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/codemods/README.md +12 -0
  2. package/codemods/renameV2ComponentImportsAndUsages/index.ts +19 -0
  3. package/codemods/renameV2ComponentImportsAndUsages/renameV2ComponentImportsAndUsages.spec.ts +390 -0
  4. package/codemods/renameV2ComponentImportsAndUsages/renameV2ComponentImportsAndUsages.ts +230 -0
  5. package/codemods/utils/index.ts +1 -0
  6. package/codemods/utils/updateJsxElementTagName.spec.ts +129 -0
  7. package/codemods/utils/updateJsxElementTagName.ts +56 -0
  8. package/codemods/utils/updateKaioImports.spec.ts +82 -0
  9. package/codemods/utils/updateKaioImports.ts +16 -7
  10. package/dist/cjs/src/__alpha__/SingleSelect/SingleSelect.cjs +69 -16
  11. package/dist/cjs/src/__alpha__/SingleSelect/context/SingleSelectContext.cjs +13 -0
  12. package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.cjs +54 -0
  13. package/dist/cjs/src/__alpha__/SingleSelect/{SingleSelect.module.css.cjs → subcomponents/Popover/Popover.module.css.cjs} +1 -1
  14. package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePopoverPositioning.cjs +94 -0
  15. package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePositioningStyles.cjs +69 -0
  16. package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Popover/utils/useSupportsAnchorPositioning.cjs +12 -0
  17. package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.cjs +41 -5
  18. package/dist/esm/src/__alpha__/SingleSelect/SingleSelect.mjs +60 -10
  19. package/dist/esm/src/__alpha__/SingleSelect/context/SingleSelectContext.mjs +10 -0
  20. package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.mjs +49 -0
  21. package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.module.css.mjs +4 -0
  22. package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePopoverPositioning.mjs +92 -0
  23. package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePositioningStyles.mjs +67 -0
  24. package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Popover/utils/useSupportsAnchorPositioning.mjs +10 -0
  25. package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.mjs +43 -7
  26. package/dist/styles.css +43 -21
  27. package/dist/types/__alpha__/SingleSelect/SingleSelect.d.ts +7 -9
  28. package/dist/types/__alpha__/SingleSelect/context/SingleSelectContext.d.ts +12 -0
  29. package/dist/types/__alpha__/SingleSelect/context/index.d.ts +1 -0
  30. package/dist/types/__alpha__/SingleSelect/subcomponents/List/List.d.ts +2 -1
  31. package/dist/types/__alpha__/SingleSelect/subcomponents/ListItem/ListItem.d.ts +2 -1
  32. package/dist/types/__alpha__/SingleSelect/subcomponents/ListSection/ListSection.d.ts +2 -1
  33. package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/Popover.d.ts +6 -0
  34. package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/index.d.ts +1 -0
  35. package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/utils/index.d.ts +2 -0
  36. package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/utils/usePopoverPositioning.d.ts +4 -0
  37. package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/utils/usePositioningStyles.d.ts +4 -0
  38. package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/utils/useSupportsAnchorPositioning.d.ts +1 -0
  39. package/dist/types/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.d.ts +2 -1
  40. package/dist/types/__alpha__/SingleSelect/subcomponents/index.d.ts +1 -0
  41. package/dist/types/__alpha__/SingleSelect/types.d.ts +45 -0
  42. package/package.json +4 -4
  43. package/src/__alpha__/SingleSelect/SingleSelect.tsx +79 -14
  44. package/src/__alpha__/SingleSelect/_docs/SingleSelect.mdx +5 -2
  45. package/src/__alpha__/SingleSelect/_docs/SingleSelect.spec.stories.tsx +100 -0
  46. package/src/__alpha__/SingleSelect/_docs/SingleSelect.stickersheet.stories.tsx +4 -4
  47. package/src/__alpha__/SingleSelect/_docs/SingleSelect.stories.tsx +21 -2
  48. package/src/__alpha__/SingleSelect/context/SingleSelectContext.tsx +21 -0
  49. package/src/__alpha__/SingleSelect/context/index.ts +1 -0
  50. package/src/__alpha__/SingleSelect/subcomponents/List/List.module.css +0 -1
  51. package/src/__alpha__/SingleSelect/subcomponents/List/List.tsx +2 -1
  52. package/src/__alpha__/SingleSelect/subcomponents/ListItem/ListItem.module.css +7 -0
  53. package/src/__alpha__/SingleSelect/subcomponents/ListItem/ListItem.tsx +2 -1
  54. package/src/__alpha__/SingleSelect/subcomponents/ListSection/ListSection.tsx +3 -1
  55. package/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.module.css +24 -0
  56. package/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.tsx +54 -0
  57. package/src/__alpha__/SingleSelect/subcomponents/Popover/index.ts +1 -0
  58. package/src/__alpha__/SingleSelect/subcomponents/Popover/utils/index.ts +2 -0
  59. package/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePopoverPositioning.ts +108 -0
  60. package/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePositioningStyles.ts +75 -0
  61. package/src/__alpha__/SingleSelect/subcomponents/Popover/utils/useSupportsAnchorPositioning.ts +13 -0
  62. package/src/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.module.css +1 -0
  63. package/src/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.tsx +29 -7
  64. package/src/__alpha__/SingleSelect/subcomponents/index.ts +1 -0
  65. package/src/__alpha__/SingleSelect/types.ts +58 -0
  66. package/dist/esm/src/__alpha__/SingleSelect/SingleSelect.module.css.mjs +0 -4
  67. package/src/__alpha__/SingleSelect/SingleSelect.module.css +0 -9
  68. package/src/__alpha__/SingleSelect/SingleSelect.spec.tsx +0 -26
@@ -0,0 +1 @@
1
+ export * from './Popover';
@@ -0,0 +1,2 @@
1
+ export * from './usePopoverPositioning';
2
+ export * from './useSupportsAnchorPositioning';
@@ -0,0 +1,4 @@
1
+ import { type Position, type UsePopoverPositioningProps } from '../../../types';
2
+ export declare function usePopoverPositioning({ triggerRef, popoverRef, direction, offset, preferredPlacement, }: UsePopoverPositioningProps): Position & {
3
+ isPositioned: boolean;
4
+ };
@@ -0,0 +1,4 @@
1
+ export declare const usePositioningStyles: (buttonRef: React.RefObject<HTMLElement>, popoverRef: React.RefObject<HTMLDivElement>, anchorName: string) => {
2
+ popoverStyle: React.CSSProperties;
3
+ isPositioned: boolean;
4
+ };
@@ -0,0 +1 @@
1
+ export declare const useSupportsAnchorPositioning: () => boolean;
@@ -1 +1,2 @@
1
- export declare const Trigger: () => JSX.Element;
1
+ import { type TriggerProps } from '../../types';
2
+ export declare const Trigger: ({ buttonRef }: TriggerProps) => JSX.Element;
@@ -2,3 +2,4 @@ export * from './List';
2
2
  export * from './ListSection';
3
3
  export * from './ListItem';
4
4
  export * from './Trigger';
5
+ export * from './Popover';
@@ -0,0 +1,45 @@
1
+ import { type RefObject } from 'react';
2
+ import { type Key } from '@react-types/shared';
3
+ export type SelectItem = {
4
+ label: string;
5
+ value: string;
6
+ };
7
+ export type SelectSection = {
8
+ label: string;
9
+ options: SelectItem[];
10
+ };
11
+ export type SingleSelectProps = {
12
+ children?: React.ReactNode;
13
+ items: (SelectItem | SelectSection)[];
14
+ onSelectionChange?: (key: Key | null) => void;
15
+ };
16
+ export type TriggerProps = {
17
+ buttonRef: React.RefObject<HTMLButtonElement>;
18
+ };
19
+ export type PopoverProps = {
20
+ buttonRef: React.RefObject<HTMLElement>;
21
+ popoverRef: React.RefObject<HTMLDivElement>;
22
+ racPopoverRef: React.Ref<any>;
23
+ };
24
+ type PositionDataProp = number | string | undefined;
25
+ export type PositionData = {
26
+ top: PositionDataProp;
27
+ bottom: PositionDataProp;
28
+ insetInlineStart: PositionDataProp;
29
+ maxHeight: PositionDataProp;
30
+ };
31
+ export type LogicalPosition = number | 'auto' | undefined;
32
+ export type Position = {
33
+ top: LogicalPosition;
34
+ bottom: LogicalPosition;
35
+ insetInlineStart: number;
36
+ maxHeight?: number;
37
+ };
38
+ export type UsePopoverPositioningProps = {
39
+ triggerRef: RefObject<HTMLElement>;
40
+ popoverRef: RefObject<HTMLElement>;
41
+ direction?: 'ltr' | 'rtl';
42
+ offset?: number;
43
+ preferredPlacement?: 'top' | 'bottom';
44
+ };
45
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaizen/components",
3
- "version": "1.80.2",
3
+ "version": "1.80.3",
4
4
  "description": "Kaizen component library",
5
5
  "author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
6
6
  "homepage": "https://cultureamp.design",
@@ -102,8 +102,8 @@
102
102
  "kaizen-codemod": "./bin/codemod.sh"
103
103
  },
104
104
  "dependencies": {
105
- "@floating-ui/react-dom": "^2.1.4",
106
- "@headlessui/react": "^2.2.6",
105
+ "@floating-ui/react-dom": "^2.1.5",
106
+ "@headlessui/react": "^2.2.7",
107
107
  "@internationalized/date": "^3.8.2",
108
108
  "@popperjs/core": "^2.11.8",
109
109
  "@reach/tabs": "^0.18.0",
@@ -179,7 +179,7 @@
179
179
  "react-dom": "^19.1.0",
180
180
  "react-highlight": "^0.15.0",
181
181
  "react-intl": "^7.1.11",
182
- "rollup": "^4.46.1",
182
+ "rollup": "^4.46.2",
183
183
  "sass": "1.79.6",
184
184
  "serialize-query-params": "^2.0.2",
185
185
  "svgo": "^3.3.2",
@@ -1,23 +1,88 @@
1
- import React, { type HTMLAttributes, type PropsWithChildren } from 'react'
2
- import { Popover as RACPopover, Select as RACSelect } from 'react-aria-components'
3
- import { type OverrideClassName } from '~components/types/OverrideClassName'
4
- import { List, ListItem, ListSection, Trigger } from './subcomponents'
5
- import styles from './SingleSelect.module.css'
6
-
7
- export type SingleSelectProps = {
8
- children?: React.ReactNode
9
- } & OverrideClassName<HTMLAttributes<Element>>
1
+ import React, { cloneElement, isValidElement, useId, useMemo, type PropsWithChildren } from 'react'
2
+ import { useSelectState } from '@react-stately/select'
3
+ import { type Key, type Selection } from '@react-types/shared'
4
+ import { Select as RACSelect, type ListBoxProps } from 'react-aria-components'
5
+ import { SingleSelectContext } from './context'
6
+ import { List, ListItem, ListSection, Popover, Trigger } from './subcomponents'
7
+ import { type SelectItem, type SelectSection, type SingleSelectProps } from './types'
10
8
 
11
9
  export const SingleSelect = ({
12
- classNameOverride,
10
+ items,
11
+ onSelectionChange,
13
12
  children,
14
13
  ...restProps
15
14
  }: PropsWithChildren<SingleSelectProps>): JSX.Element => {
15
+ const buttonRef = React.useRef<HTMLButtonElement>(null)
16
+ const popoverRef = React.useRef<HTMLDivElement>(null)
17
+ const racPopoverRef = React.useRef<HTMLElement>(null)
18
+ const uniqueId = useId()
19
+ const anchorName = `--trigger-${uniqueId}`
20
+
21
+ const state = useSelectState({
22
+ items,
23
+ })
24
+
25
+ const handleOnSelectionChange = React.useCallback(
26
+ (keys: Selection): void => {
27
+ let key: Key | null = null
28
+
29
+ if (keys instanceof Set && keys.size > 0) {
30
+ key = Array.from(keys)[0]
31
+ }
32
+
33
+ state.setSelectedKey(key)
34
+ if (onSelectionChange) {
35
+ onSelectionChange(key)
36
+ }
37
+ },
38
+ [state, onSelectionChange],
39
+ )
40
+
41
+ // Cloning children here to allow users to pass in a custom ListItem or ListSection
42
+ // and still have the SingleSelect handle selection state
43
+ const injectedChildren = useMemo(() => {
44
+ if (!isValidElement(children)) return null
45
+
46
+ const selectedKeys: Iterable<Key> = state.selectedKey
47
+ ? new Set<Key>([state.selectedKey])
48
+ : new Set()
49
+
50
+ return cloneElement(children as React.ReactElement<ListBoxProps<SelectItem | SelectSection>>, {
51
+ selectionMode: 'single',
52
+ selectedKeys,
53
+ onSelectionChange: handleOnSelectionChange,
54
+ autoFocus: 'first',
55
+ })
56
+ }, [children, handleOnSelectionChange, state.selectedKey])
57
+
16
58
  return (
17
- <RACSelect className={classNameOverride} placeholder="" {...restProps}>
18
- <Trigger />
19
- <RACPopover className={styles.popover}>{children}</RACPopover>
20
- </RACSelect>
59
+ <SingleSelectContext.Provider
60
+ value={{
61
+ isOpen: state.isOpen,
62
+ setOpen: state.setOpen,
63
+ selectedKey: state.selectedKey,
64
+ items: items,
65
+ anchorName,
66
+ }}
67
+ >
68
+ <RACSelect
69
+ // TODO: allow user to pass in label
70
+ aria-label={'single-select'}
71
+ onSelectionChange={(key) =>
72
+ handleOnSelectionChange(key != null ? new Set([key]) : new Set())
73
+ }
74
+ placeholder=""
75
+ {...restProps}
76
+ >
77
+ <Trigger buttonRef={buttonRef} />
78
+
79
+ {state.isOpen && (
80
+ <Popover buttonRef={buttonRef} popoverRef={popoverRef} racPopoverRef={racPopoverRef}>
81
+ {injectedChildren}
82
+ </Popover>
83
+ )}
84
+ </RACSelect>
85
+ </SingleSelectContext.Provider>
21
86
  )
22
87
  }
23
88
 
@@ -18,10 +18,13 @@ import * as SingleSelectStories from './SingleSelect.stories'
18
18
 
19
19
  ## Overview
20
20
 
21
- {/* @todo: Update summary. */}
22
- Brief summary of the component here.
21
+ SingleSelect component that handles selecting items from a dropdown, can be either filterable or not.
23
22
 
24
23
  <Canvas of={SingleSelectStories.Playground} />
25
24
  <Controls of={SingleSelectStories.Playground} />
26
25
 
27
26
  ## API
27
+
28
+ ## Positioning and z-index Management
29
+
30
+ The SingleSelect component leverages the native Popover API to manage its dropdown functionality. By using popover instead of custom portal logic, the component takes full advantage of CSS layers, ensuring dropdowns appear above other content without manual z-index management.
@@ -0,0 +1,100 @@
1
+ import React from 'react'
2
+ import { type Meta, type StoryObj } from '@storybook/react'
3
+ import { expect, screen, userEvent, waitFor } from '@storybook/test'
4
+ import { SingleSelect } from '../SingleSelect'
5
+ import { singleMockItems } from './mockData'
6
+
7
+ const meta = {
8
+ title: 'Components/SingleSelect/SingleSelect (alpha)',
9
+ component: SingleSelect,
10
+ parameters: {
11
+ layout: 'centered',
12
+ },
13
+ } satisfies Meta<typeof SingleSelect>
14
+
15
+ export default meta
16
+
17
+ type Story = StoryObj<typeof meta>
18
+
19
+ const args = {
20
+ items: singleMockItems,
21
+ children: (
22
+ <SingleSelect.List>
23
+ {singleMockItems.map((item) => (
24
+ <SingleSelect.ListItem key={item.value} id={item.value}>
25
+ {item.label}
26
+ </SingleSelect.ListItem>
27
+ ))}
28
+ </SingleSelect.List>
29
+ ),
30
+ }
31
+
32
+ export const RendersButton: Story = {
33
+ args,
34
+ play: async () => {
35
+ expect(screen.getByRole('button')).toBeInTheDocument()
36
+ },
37
+ }
38
+
39
+ export const OpensPopoverOnClick: Story = {
40
+ args,
41
+ play: async () => {
42
+ const trigger = screen.getByRole('button')
43
+ await userEvent.click(trigger)
44
+ await waitFor(() => expect(trigger).toHaveAttribute('aria-expanded', 'true'))
45
+ const options = await screen.findAllByRole('option')
46
+ expect(options[0]).toBeVisible()
47
+ expect(options[0]).toHaveTextContent(singleMockItems[0].label)
48
+ },
49
+ }
50
+
51
+ export const ClosesPopoverOnSelect: Story = {
52
+ args,
53
+ play: async () => {
54
+ const trigger = screen.getByRole('button')
55
+ await userEvent.click(trigger)
56
+ await waitFor(() => expect(trigger).toHaveAttribute('aria-expanded', 'true'))
57
+ const options = await screen.findAllByRole('option')
58
+ await userEvent.click(options[0])
59
+ await waitFor(() => expect(screen.queryAllByRole('option')).toHaveLength(0))
60
+ },
61
+ }
62
+
63
+ export const KeyboardNavigation: Story = {
64
+ args,
65
+ play: async () => {
66
+ const trigger = screen.getByRole('button')
67
+ trigger.focus()
68
+ await userEvent.keyboard('{Enter}')
69
+ await waitFor(() => expect(trigger).toHaveAttribute('aria-expanded', 'true'))
70
+ const options = await screen.findAllByRole('option')
71
+ await userEvent.keyboard('{ArrowDown}')
72
+ expect(options[1]).toHaveAttribute('data-focused', 'true')
73
+ await userEvent.keyboard('{ArrowUp}')
74
+ expect(options[0]).toHaveAttribute('data-focused', 'true')
75
+ },
76
+ }
77
+
78
+ export const KeyboardSelectsItem: Story = {
79
+ args,
80
+ play: async () => {
81
+ const trigger = screen.getByRole('button')
82
+ trigger.focus()
83
+ await userEvent.keyboard('{Enter}')
84
+ await waitFor(() => expect(trigger).toHaveAttribute('aria-expanded', 'true'))
85
+ await userEvent.keyboard('{ArrowDown}')
86
+ await userEvent.keyboard('{Enter}')
87
+ await waitFor(() => expect(screen.queryAllByRole('option')).toHaveLength(0))
88
+ },
89
+ }
90
+
91
+ export const KeyboardEscapeClosesPopover: Story = {
92
+ args,
93
+ play: async () => {
94
+ const trigger = screen.getByRole('button')
95
+ await userEvent.click(trigger)
96
+ await waitFor(() => expect(trigger).toHaveAttribute('aria-expanded', 'true'))
97
+ await userEvent.keyboard('{Escape}')
98
+ await waitFor(() => expect(trigger).toHaveAttribute('aria-expanded', 'false'))
99
+ },
100
+ }
@@ -17,22 +17,22 @@ const StickerSheetTemplate: StickerSheetStory = {
17
17
  return (
18
18
  <StickerSheet isReversed={isReversed} title="SingleSelect" headers={['Items', 'Grouped']}>
19
19
  <StickerSheet.Row>
20
- <SingleSelect>
20
+ <SingleSelect items={singleMockItems}>
21
21
  <SingleSelect.List>
22
22
  {singleMockItems.map((item) => (
23
- <SingleSelect.ListItem key={item.value} value={{ value: item.value }}>
23
+ <SingleSelect.ListItem key={item.value} id={item.value}>
24
24
  {item.label}
25
25
  </SingleSelect.ListItem>
26
26
  ))}
27
27
  </SingleSelect.List>
28
28
  </SingleSelect>
29
29
 
30
- <SingleSelect>
30
+ <SingleSelect items={groupedMockItems}>
31
31
  <SingleSelect.List>
32
32
  {groupedMockItems.map((section) => (
33
33
  <SingleSelect.ListSection name={section.label} key={section.label}>
34
34
  {section.options.map((item) => (
35
- <SingleSelect.ListItem key={item.value} value={{ value: item.value }}>
35
+ <SingleSelect.ListItem key={item.value} id={item.value}>
36
36
  {item.label}
37
37
  </SingleSelect.ListItem>
38
38
  ))}
@@ -1,10 +1,30 @@
1
+ import React from 'react'
1
2
  import { type Meta, type StoryObj } from '@storybook/react'
2
3
  import { SingleSelect } from '../index'
4
+ import { singleMockItems } from './mockData'
3
5
 
4
6
  const meta = {
5
7
  title: 'Components/SingleSelect/SingleSelect (alpha)',
6
8
  component: SingleSelect,
7
- args: {},
9
+ args: {
10
+ items: singleMockItems,
11
+ children: (
12
+ <SingleSelect.List>
13
+ {singleMockItems.map((item) => (
14
+ <SingleSelect.ListItem key={item.value} id={item.value}>
15
+ {item.label}
16
+ </SingleSelect.ListItem>
17
+ ))}
18
+ </SingleSelect.List>
19
+ ),
20
+ },
21
+ decorators: [
22
+ (Story) => (
23
+ <div className="h-200 justify-center items-center position-relative flex">
24
+ <Story />
25
+ </div>
26
+ ),
27
+ ],
8
28
  } satisfies Meta<typeof SingleSelect>
9
29
 
10
30
  export default meta
@@ -12,7 +32,6 @@ export default meta
12
32
  type Story = StoryObj<typeof meta>
13
33
 
14
34
  export const Playground: Story = {
15
- args: {},
16
35
  parameters: {
17
36
  docs: {
18
37
  canvas: {
@@ -0,0 +1,21 @@
1
+ import { createContext, useContext } from 'react'
2
+ import { type Key } from '@react-types/shared'
3
+ import { type SelectItem, type SelectSection } from '../types'
4
+
5
+ type SingleSelectContextType = {
6
+ isOpen: boolean
7
+ setOpen: (open: boolean) => void
8
+ selectedKey: Key | null
9
+ items: (SelectItem | SelectSection)[]
10
+ anchorName: string
11
+ }
12
+
13
+ export const SingleSelectContext = createContext<SingleSelectContextType | undefined>(undefined)
14
+
15
+ export const useSingleSelectContext = (): SingleSelectContextType => {
16
+ const context = useContext(SingleSelectContext)
17
+ if (!context) {
18
+ throw new Error('useSingleSelectContext must be used within a SingleSelectContext.Provider')
19
+ }
20
+ return context
21
+ }
@@ -0,0 +1 @@
1
+ export * from './SingleSelectContext'
@@ -2,6 +2,5 @@
2
2
  .list {
3
3
  display: flex;
4
4
  flex-direction: column;
5
- gap: var(--spacing-16);
6
5
  }
7
6
  }
@@ -1,13 +1,14 @@
1
1
  import React, { type PropsWithChildren } from 'react'
2
2
  import classNames from 'classnames'
3
3
  import { ListBox as RACListBox, type ListBoxProps } from 'react-aria-components'
4
+ import { type SelectItem, type SelectSection } from '../../types'
4
5
  import styles from './List.module.css'
5
6
 
6
7
  export const List = ({
7
8
  children,
8
9
  className,
9
10
  ...props
10
- }: ListBoxProps<object> & PropsWithChildren): React.ReactElement => {
11
+ }: ListBoxProps<SelectItem | SelectSection> & PropsWithChildren): React.ReactElement => {
11
12
  return (
12
13
  <RACListBox className={classNames(styles.list, className)} {...props}>
13
14
  {children}
@@ -5,5 +5,12 @@
5
5
  font-size: var(--typography-paragraph-body-font-size);
6
6
  line-height: var(--typography-paragraph-body-line-height);
7
7
  letter-spacing: var(--typography-paragraph-body-letter-spacing);
8
+ padding: var(--spacing-8) var(--spacing-16);
9
+ }
10
+
11
+ .listItem:focus-visible {
12
+ background-color: var(--color-blue-200);
13
+ outline: none;
14
+ border-color: white;
8
15
  }
9
16
  }
@@ -1,13 +1,14 @@
1
1
  import React, { type PropsWithChildren } from 'react'
2
2
  import classNames from 'classnames'
3
3
  import { ListBoxItem as RACListBoxItem, type ListBoxItemProps } from 'react-aria-components'
4
+ import { type SelectItem } from '../../types'
4
5
  import styles from './ListItem.module.css'
5
6
 
6
7
  export const ListItem = ({
7
8
  children,
8
9
  className,
9
10
  ...props
10
- }: ListBoxItemProps<object> & PropsWithChildren): React.ReactElement => {
11
+ }: ListBoxItemProps<SelectItem> & PropsWithChildren): React.ReactElement => {
11
12
  return (
12
13
  <RACListBoxItem className={classNames(styles.listItem, className)} {...props}>
13
14
  {children}
@@ -5,6 +5,7 @@ import {
5
5
  ListBoxSection as RACListBoxSection,
6
6
  type ListBoxSectionProps,
7
7
  } from 'react-aria-components'
8
+ import { type SelectSection } from '../../types'
8
9
  import styles from './ListSection.module.css'
9
10
 
10
11
  export const ListSection = ({
@@ -12,7 +13,8 @@ export const ListSection = ({
12
13
  className,
13
14
  children,
14
15
  ...props
15
- }: ListBoxSectionProps<object> & PropsWithChildren & { name: string }): React.ReactElement => {
16
+ }: ListBoxSectionProps<SelectSection> &
17
+ PropsWithChildren & { name: string }): React.ReactElement => {
16
18
  return (
17
19
  <RACListBoxSection {...props}>
18
20
  <RACHeader className={classNames(styles.listSectionHeader, className)}>{name}</RACHeader>
@@ -0,0 +1,24 @@
1
+ @layer kz-components {
2
+ .popover {
3
+ position: absolute;
4
+ height: auto;
5
+ background-color: var(--color-white);
6
+ border-radius: var(--spacing-8);
7
+ padding: 0;
8
+ box-shadow: var(--shadow-small-box-shadow);
9
+ overflow: hidden auto;
10
+ margin: 0;
11
+ box-sizing: border-box;
12
+
13
+ /* TODO: update width based on design */
14
+ width: 200px;
15
+
16
+ @supports (anchor-name: --anchor) {
17
+ position-anchor: var(--position-anchor);
18
+ margin-block: var(--spacing-4);
19
+ position-area: var(--position-area) center;
20
+ /* stylelint-disable-next-line declaration-property-value-no-unknown */
21
+ width: anchor-size(width);
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,54 @@
1
+ import React, { useLayoutEffect, useMemo, type PropsWithChildren } from 'react'
2
+
3
+ import { Popover as RACPopover } from 'react-aria-components'
4
+ import { useSingleSelectContext } from '../../context'
5
+ import { type PopoverProps } from '../../types'
6
+ import { usePositioningStyles } from './utils/usePositioningStyles'
7
+ import styles from './Popover.module.css'
8
+
9
+ export const Popover = ({
10
+ buttonRef,
11
+ popoverRef,
12
+ racPopoverRef,
13
+ children,
14
+ }: PopoverProps & PropsWithChildren): React.ReactElement => {
15
+ const { isOpen, setOpen, anchorName } = useSingleSelectContext()
16
+
17
+ const { popoverStyle, isPositioned } = usePositioningStyles(buttonRef, popoverRef, anchorName)
18
+
19
+ const shouldShowPopover = useMemo(() => isOpen && isPositioned, [isOpen, isPositioned])
20
+
21
+ useLayoutEffect(() => {
22
+ const popover = popoverRef.current
23
+ if (!popover?.showPopover || !popover?.hidePopover) return
24
+
25
+ if (shouldShowPopover) {
26
+ popover.showPopover()
27
+ } else {
28
+ popover.hidePopover()
29
+ }
30
+ // eslint-disable-next-line react-hooks/exhaustive-deps
31
+ }, [shouldShowPopover])
32
+
33
+ return (
34
+ <RACPopover
35
+ shouldUpdatePosition={false}
36
+ trigger="manual"
37
+ isOpen={isOpen}
38
+ onOpenChange={setOpen}
39
+ ref={racPopoverRef}
40
+ >
41
+ <div
42
+ // @ts-expect-error - popover attribute is not included in current ts version, ignore type error
43
+ popover="manual"
44
+ ref={popoverRef}
45
+ className={styles.popover}
46
+ style={popoverStyle}
47
+ >
48
+ {children}
49
+ </div>
50
+ </RACPopover>
51
+ )
52
+ }
53
+
54
+ Popover.displayName = 'SingleSelect.Popover'
@@ -0,0 +1 @@
1
+ export * from './Popover'
@@ -0,0 +1,2 @@
1
+ export * from './usePopoverPositioning'
2
+ export * from './useSupportsAnchorPositioning'