@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.
- package/codemods/README.md +12 -0
- package/codemods/renameV2ComponentImportsAndUsages/index.ts +19 -0
- package/codemods/renameV2ComponentImportsAndUsages/renameV2ComponentImportsAndUsages.spec.ts +390 -0
- package/codemods/renameV2ComponentImportsAndUsages/renameV2ComponentImportsAndUsages.ts +230 -0
- package/codemods/utils/index.ts +1 -0
- package/codemods/utils/updateJsxElementTagName.spec.ts +129 -0
- package/codemods/utils/updateJsxElementTagName.ts +56 -0
- package/codemods/utils/updateKaioImports.spec.ts +82 -0
- package/codemods/utils/updateKaioImports.ts +16 -7
- package/dist/cjs/src/__alpha__/SingleSelect/SingleSelect.cjs +69 -16
- package/dist/cjs/src/__alpha__/SingleSelect/context/SingleSelectContext.cjs +13 -0
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.cjs +54 -0
- package/dist/cjs/src/__alpha__/SingleSelect/{SingleSelect.module.css.cjs → subcomponents/Popover/Popover.module.css.cjs} +1 -1
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePopoverPositioning.cjs +94 -0
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePositioningStyles.cjs +69 -0
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Popover/utils/useSupportsAnchorPositioning.cjs +12 -0
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.cjs +41 -5
- package/dist/esm/src/__alpha__/SingleSelect/SingleSelect.mjs +60 -10
- package/dist/esm/src/__alpha__/SingleSelect/context/SingleSelectContext.mjs +10 -0
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.mjs +49 -0
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.module.css.mjs +4 -0
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePopoverPositioning.mjs +92 -0
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePositioningStyles.mjs +67 -0
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Popover/utils/useSupportsAnchorPositioning.mjs +10 -0
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.mjs +43 -7
- package/dist/styles.css +43 -21
- package/dist/types/__alpha__/SingleSelect/SingleSelect.d.ts +7 -9
- package/dist/types/__alpha__/SingleSelect/context/SingleSelectContext.d.ts +12 -0
- package/dist/types/__alpha__/SingleSelect/context/index.d.ts +1 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/List/List.d.ts +2 -1
- package/dist/types/__alpha__/SingleSelect/subcomponents/ListItem/ListItem.d.ts +2 -1
- package/dist/types/__alpha__/SingleSelect/subcomponents/ListSection/ListSection.d.ts +2 -1
- package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/Popover.d.ts +6 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/index.d.ts +1 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/utils/index.d.ts +2 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/utils/usePopoverPositioning.d.ts +4 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/utils/usePositioningStyles.d.ts +4 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/utils/useSupportsAnchorPositioning.d.ts +1 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.d.ts +2 -1
- package/dist/types/__alpha__/SingleSelect/subcomponents/index.d.ts +1 -0
- package/dist/types/__alpha__/SingleSelect/types.d.ts +45 -0
- package/package.json +4 -4
- package/src/__alpha__/SingleSelect/SingleSelect.tsx +79 -14
- package/src/__alpha__/SingleSelect/_docs/SingleSelect.mdx +5 -2
- package/src/__alpha__/SingleSelect/_docs/SingleSelect.spec.stories.tsx +100 -0
- package/src/__alpha__/SingleSelect/_docs/SingleSelect.stickersheet.stories.tsx +4 -4
- package/src/__alpha__/SingleSelect/_docs/SingleSelect.stories.tsx +21 -2
- package/src/__alpha__/SingleSelect/context/SingleSelectContext.tsx +21 -0
- package/src/__alpha__/SingleSelect/context/index.ts +1 -0
- package/src/__alpha__/SingleSelect/subcomponents/List/List.module.css +0 -1
- package/src/__alpha__/SingleSelect/subcomponents/List/List.tsx +2 -1
- package/src/__alpha__/SingleSelect/subcomponents/ListItem/ListItem.module.css +7 -0
- package/src/__alpha__/SingleSelect/subcomponents/ListItem/ListItem.tsx +2 -1
- package/src/__alpha__/SingleSelect/subcomponents/ListSection/ListSection.tsx +3 -1
- package/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.module.css +24 -0
- package/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.tsx +54 -0
- package/src/__alpha__/SingleSelect/subcomponents/Popover/index.ts +1 -0
- package/src/__alpha__/SingleSelect/subcomponents/Popover/utils/index.ts +2 -0
- package/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePopoverPositioning.ts +108 -0
- package/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePositioningStyles.ts +75 -0
- package/src/__alpha__/SingleSelect/subcomponents/Popover/utils/useSupportsAnchorPositioning.ts +13 -0
- package/src/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.module.css +1 -0
- package/src/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.tsx +29 -7
- package/src/__alpha__/SingleSelect/subcomponents/index.ts +1 -0
- package/src/__alpha__/SingleSelect/types.ts +58 -0
- package/dist/esm/src/__alpha__/SingleSelect/SingleSelect.module.css.mjs +0 -4
- package/src/__alpha__/SingleSelect/SingleSelect.module.css +0 -9
- package/src/__alpha__/SingleSelect/SingleSelect.spec.tsx +0 -26
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Popover';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useSupportsAnchorPositioning: () => boolean;
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { type TriggerProps } from '../../types';
|
|
2
|
+
export declare const Trigger: ({ buttonRef }: TriggerProps) => JSX.Element;
|
|
@@ -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.
|
|
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.
|
|
106
|
-
"@headlessui/react": "^2.2.
|
|
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.
|
|
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, {
|
|
2
|
-
import {
|
|
3
|
-
import { type
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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}
|
|
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}
|
|
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'
|
|
@@ -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<
|
|
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<
|
|
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<
|
|
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'
|