uikit-react-public 0.11.13 → 0.11.15

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 (40) hide show
  1. package/dist/components/Button/Button.d.ts +21 -3
  2. package/dist/components/Button/Button.stories.d.ts +1 -1
  3. package/dist/components/Button/buttonPrimaryStyle.d.ts +2 -2
  4. package/dist/components/Button/buttonSecondaryStyle.d.ts +2 -2
  5. package/dist/components/Button/buttonTertiaryStyle.d.ts +2 -2
  6. package/dist/components/Button/index.d.ts +1 -1
  7. package/dist/components/Select/Select.d.ts +3 -8
  8. package/dist/components/Select/Select.stories.d.ts +50 -2
  9. package/dist/components/Select/Select.types.d.ts +122 -0
  10. package/dist/components/Select/index.d.ts +1 -1
  11. package/dist/components/Select/subcomponents/CustomOption.d.ts +3 -0
  12. package/dist/components/Select/subcomponents/CustomSelect.d.ts +3 -0
  13. package/dist/components/Select/subcomponents/NativeSelect.d.ts +3 -0
  14. package/dist/components/Select/subcomponents/Panel.d.ts +3 -0
  15. package/dist/components/Select/subcomponents/VisibleField.d.ts +9 -0
  16. package/dist/components/Select/subcomponents/index.d.ts +5 -0
  17. package/dist/index.js +2875 -2549
  18. package/lib/Welcome.mdx +7 -7
  19. package/lib/components/Button/Button.tsx +36 -7
  20. package/lib/components/Button/buttonPrimaryStyle.ts +4 -4
  21. package/lib/components/Button/buttonSecondaryStyle.ts +4 -4
  22. package/lib/components/Button/buttonTertiaryStyle.ts +3 -3
  23. package/lib/components/Button/index.ts +1 -1
  24. package/lib/components/Icon/svgs/AvatarSvg.tsx +2 -2
  25. package/lib/components/Icon/svgs/ChevronDownSvg.tsx +2 -5
  26. package/lib/components/Select/Select.stories.tsx +192 -13
  27. package/lib/components/Select/Select.tsx +33 -76
  28. package/lib/components/Select/Select.types.ts +146 -0
  29. package/lib/components/Select/__tests__/Select.test.tsx +99 -20
  30. package/lib/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +33 -37
  31. package/lib/components/Select/index.ts +1 -1
  32. package/lib/components/Select/subcomponents/CustomOption.tsx +74 -0
  33. package/lib/components/Select/subcomponents/CustomSelect.tsx +211 -0
  34. package/lib/components/Select/subcomponents/NativeSelect.tsx +109 -0
  35. package/lib/components/Select/subcomponents/Panel.tsx +46 -0
  36. package/lib/components/Select/subcomponents/VisibleField.tsx +69 -0
  37. package/lib/components/Select/subcomponents/index.tsx +5 -0
  38. package/package.json +1 -1
  39. package/dist/components/Button/Button.types.d.ts +0 -26
  40. package/lib/components/Button/Button.types.ts +0 -46
package/lib/Welcome.mdx CHANGED
@@ -1,10 +1,10 @@
1
- import { Meta } from '@storybook/blocks';
2
- import UclLogo from './components/UclLogo';
1
+ import { Meta } from "@storybook/blocks";
2
+ import { UclLogo } from "./components/";
3
3
 
4
4
  <Meta title="Welcome" />
5
5
 
6
6
  <style>
7
- {`
7
+ {`
8
8
  .logo-container {
9
9
  display: flex;
10
10
  align-items: flex-end;
@@ -18,13 +18,13 @@ import UclLogo from './components/UclLogo';
18
18
  `}
19
19
  </style>
20
20
 
21
- <div className='logo-container'>
22
- <UclLogo className='ucl-logo' />
21
+ <div className="logo-container">
22
+ <UclLogo className="ucl-logo" />
23
23
  </div>
24
24
 
25
- # UIKit-React
25
+ # UIKit-React
26
26
 
27
- This component library of React components implements the UCL Design System.
27
+ This component library of React components implements the UCL Design System.
28
28
 
29
29
  - [GitHub repo](https://github.com/ucl-isd/uikit-react)
30
30
  - [Design System Figma](https://www.figma.com/design/8Sm5PxWOWJYpYXAzhRzUzt/UCL-Design-System-UI-Kit)
@@ -1,16 +1,43 @@
1
- import { useState, useRef, memo } from 'react';
1
+ import {
2
+ useState,
3
+ useRef,
4
+ ReactElement,
5
+ ElementType,
6
+ ComponentPropsWithRef,
7
+ memo,
8
+ } from 'react';
2
9
  import { css, cx } from '@emotion/css';
3
10
  import useTheme from '../../theme/useTheme';
4
11
  import buttonPrimaryStyle from './buttonPrimaryStyle';
5
12
  import buttonSecondaryStyle from './buttonSecondaryStyle';
6
13
  import buttonTertiaryStyle from './buttonTertiaryStyle';
7
- import { ButtonProps } from './Button.types';
8
14
  import { Spinner, Overlay, Tooltip } from '../';
9
15
  import marginsStyle from '../common/marginsStyle';
10
16
 
11
17
  export const NAME = 'ucl-uikit-button';
12
18
 
13
- const Button = ({
19
+ type PolymorphicRef<C extends ElementType> = ComponentPropsWithRef<C>['ref'];
20
+
21
+ export interface ButtonBaseProps {
22
+ variant?: 'primary' | 'secondary' | 'tertiary';
23
+ destructive?: boolean;
24
+ size?: 'large' | 'default' | 'small';
25
+ disabled?: boolean;
26
+ icon?: ReactElement;
27
+ iconPosition?: 'left' | 'right';
28
+ tooltip?: string;
29
+ loading?: boolean;
30
+ fullWidth?: boolean;
31
+ testId?: string;
32
+ }
33
+
34
+ export type ButtonProps<C extends ElementType = 'button'> = {
35
+ as?: C;
36
+ ref?: PolymorphicRef<C>;
37
+ } & ButtonBaseProps &
38
+ Omit<ComponentPropsWithRef<C>, keyof ButtonBaseProps | 'as'>;
39
+
40
+ const Button = <C extends ElementType = 'button'>({
14
41
  as,
15
42
  variant = 'primary',
16
43
  destructive = false,
@@ -26,7 +53,9 @@ const Button = ({
26
53
  children,
27
54
  className,
28
55
  ...props
29
- }: ButtonProps) => {
56
+ }: ButtonProps<C>) => {
57
+ const Component = as || 'button';
58
+
30
59
  if (variant === 'tertiary' && destructive) {
31
60
  console.warn("Button variant 'tertiary' cannot also be 'destructive'.");
32
61
  }
@@ -99,8 +128,6 @@ const Button = ({
99
128
  className
100
129
  );
101
130
 
102
- const Component = as || 'button';
103
-
104
131
  return (
105
132
  <>
106
133
  <Component
@@ -143,4 +170,6 @@ const Button = ({
143
170
  );
144
171
  };
145
172
 
146
- export default memo(Button);
173
+ const MemoizedButton = memo(Button) as typeof Button;
174
+
175
+ export default MemoizedButton;
@@ -1,12 +1,12 @@
1
1
  import { css, cx } from '@emotion/css';
2
2
  import { ThemeType } from '../../theme';
3
- import { ButtonProps } from './Button.types';
3
+ import { ButtonBaseProps } from './Button';
4
4
 
5
5
  const buttonPrimaryStyle = (
6
6
  theme: ThemeType,
7
- destructive: ButtonProps['destructive'],
8
- disabled: ButtonProps['disabled'],
9
- loading: ButtonProps['loading']
7
+ destructive: ButtonBaseProps['destructive'],
8
+ disabled: ButtonBaseProps['disabled'],
9
+ loading: ButtonBaseProps['loading']
10
10
  ) => {
11
11
  const {
12
12
  color: { system, interaction },
@@ -1,12 +1,12 @@
1
1
  import { css, cx } from '@emotion/css';
2
2
  import { ThemeType } from '../../theme';
3
- import { ButtonProps } from './Button.types';
3
+ import { ButtonBaseProps } from './Button';
4
4
 
5
5
  const buttonSecondaryStyle = (
6
6
  theme: ThemeType,
7
- destructive: ButtonProps['destructive'],
8
- disabled: ButtonProps['disabled'],
9
- loading: ButtonProps['loading']
7
+ destructive: ButtonBaseProps['destructive'],
8
+ disabled: ButtonBaseProps['disabled'],
9
+ loading: ButtonBaseProps['loading']
10
10
  ) => {
11
11
  const {
12
12
  color: { system, interaction },
@@ -1,11 +1,11 @@
1
1
  import { css, cx } from '@emotion/css';
2
2
  import { ThemeType } from '../../theme';
3
- import { ButtonProps } from './Button.types';
3
+ import { ButtonBaseProps } from './Button';
4
4
 
5
5
  const buttonTertiaryStyle = (
6
6
  theme: ThemeType,
7
- disabled: ButtonProps['disabled'],
8
- loading: ButtonProps['loading']
7
+ disabled: ButtonBaseProps['disabled'],
8
+ loading: ButtonBaseProps['loading']
9
9
  ) => {
10
10
  const {
11
11
  color: { interaction },
@@ -1,2 +1,2 @@
1
1
  export { default } from './Button';
2
- export type { ButtonProps } from './Button.types';
2
+ export type { ButtonProps, ButtonBaseProps } from './Button';
@@ -13,8 +13,8 @@ const AvatarSvg = ({ ...props }) => {
13
13
  >
14
14
  {title && <title>{title}</title>}
15
15
  <path
16
- fill-rule='evenodd'
17
- clip-rule='evenodd'
16
+ fillRule='evenodd'
17
+ clipRule='evenodd'
18
18
  d='M4 20.9995C4.49232 17.0534 7.8586 14 11.938 14C16.0175 14 19.5077 17.0539 20 21C18 23 15 24 12 24C9 24 6 23 4 20.9995Z'
19
19
  />
20
20
  <circle
@@ -22,11 +22,8 @@ const ChevronDownSvg = ({ ...props }) => {
22
22
 
23
23
  export default memo(ChevronDownSvg);
24
24
 
25
- // Used by Select
25
+ // Used by <Select>
26
26
  const DATA_URI =
27
27
  'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22{{COLOUR}}%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20width%3D%2224%22%20height%3D%2224%22%20class%3D%22ucl-icon%20css-4bxmzw%22%3E%3Cpolyline%20points%3D%226%209%2012%2015%2018%209%22%3E%3C%2Fpolyline%3E%3C%2Fsvg%3E';
28
28
  export const dataUri = (colour: string) =>
29
- DATA_URI.replace(
30
- '{{COLOUR}}',
31
- encodeURIComponent(colour)
32
- );
29
+ DATA_URI.replace('{{COLOUR}}', encodeURIComponent(colour));
@@ -1,27 +1,206 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react';
2
-
2
+ import { useArgs } from '@storybook/preview-api';
3
3
  import Select from './Select';
4
+ import { Icon } from '../';
5
+ import { SelectEvent } from './Select.types';
4
6
 
5
7
  const meta = {
6
- title: 'Components/Work in progress/Select',
8
+ title: 'Components/Ready to use/Select',
7
9
  component: Select,
10
+ argTypes: {
11
+ value: { control: { type: 'text' } },
12
+ native: {
13
+ control: { type: 'boolean' },
14
+ },
15
+ disabled: {
16
+ control: { type: 'boolean' },
17
+ },
18
+ placeholder: {
19
+ control: { type: 'text' },
20
+ },
21
+ testId: {
22
+ control: { type: 'text' },
23
+ },
24
+ width: {
25
+ control: { type: 'range', min: 80, max: 624, step: 1 },
26
+ },
27
+ },
28
+ args: {
29
+ value: undefined,
30
+ onChange: () => {},
31
+ options: [
32
+ { text: 'Option one', value: '1' },
33
+ { text: 'Option two', value: '2' },
34
+ { text: 'Option three', value: '3' },
35
+ ],
36
+ },
8
37
  } satisfies Meta<typeof Select>;
9
38
 
10
39
  export default meta;
11
-
12
40
  type Story = StoryObj<typeof meta>;
13
41
 
14
42
  export const Default: Story = {
15
- render: () => (
16
- <Select>
17
- <option value='1'>Option 1</option>
18
- <option value='2'>Option 2</option>
19
- <option value='3'>Option 3</option>
20
- </Select>
21
- ),
43
+ render: () => {
44
+ const [args, updateArgs] = useArgs();
45
+ const onChange = (_event: SelectEvent, value: string) => {
46
+ updateArgs({ value });
47
+ };
48
+ return (
49
+ <Select
50
+ {...args}
51
+ options={args.options}
52
+ value={args.value}
53
+ onChange={onChange}
54
+ />
55
+ );
56
+ },
57
+ };
58
+
59
+ export const Native: Story = {
60
+ args: {
61
+ native: true,
62
+ },
63
+ render: () => {
64
+ const [args, updateArgs] = useArgs();
65
+ const onChange = (_event: SelectEvent, value: string) => {
66
+ updateArgs({ value });
67
+ };
68
+ return (
69
+ <Select
70
+ {...args}
71
+ options={args.options}
72
+ value={args.value}
73
+ onChange={onChange}
74
+ />
75
+ );
76
+ },
77
+ };
78
+
79
+ export const OptionsWithIcons: Story = {
80
+ name: 'Options with icons',
81
+ args: {
82
+ options: [
83
+ { text: 'Option 1', value: '1', icon: <Icon.Printer /> },
84
+ { text: 'Option 2', value: '2', icon: <Icon.User /> },
85
+ { text: 'Option 3', value: '3', icon: <Icon.Info /> },
86
+ ],
87
+ },
88
+ render: () => {
89
+ const [args, updateArgs] = useArgs();
90
+ const onChange = (_event: SelectEvent, value: string) => {
91
+ updateArgs({ value });
92
+ };
93
+
94
+ return (
95
+ <Select
96
+ {...args}
97
+ options={args.options}
98
+ value={args.value}
99
+ onChange={onChange}
100
+ />
101
+ );
102
+ },
103
+ };
104
+
105
+ export const Disabled: Story = {
106
+ args: {
107
+ disabled: true,
108
+ },
109
+ render: () => {
110
+ const [args, updateArgs] = useArgs();
111
+ const onChange = (_event: SelectEvent, value: string) => {
112
+ updateArgs({ value });
113
+ };
114
+
115
+ return (
116
+ <Select
117
+ {...args}
118
+ options={args.options}
119
+ value={args.value}
120
+ onChange={onChange}
121
+ />
122
+ );
123
+ },
124
+ };
125
+
126
+ export const WithPlaceholder: Story = {
127
+ args: { placeholder: 'Please select an option' },
128
+ render: () => {
129
+ const [args, updateArgs] = useArgs();
130
+ const onChange = (_event: SelectEvent, value: string) => {
131
+ updateArgs({ value });
132
+ };
133
+
134
+ return (
135
+ <Select
136
+ {...args}
137
+ options={args.options}
138
+ value={args.value}
139
+ onChange={onChange}
140
+ />
141
+ );
142
+ },
143
+ };
144
+
145
+ export const SingleLongOption: Story = {
146
+ name: 'Long option',
147
+ args: {
148
+ options: [
149
+ { text: 'Option 1', value: '1' },
150
+ { text: 'Option 2', value: '2' },
151
+ { text: 'Option 3 long long long long ', value: '3' },
152
+ ],
153
+ },
154
+ render: () => {
155
+ const [args, updateArgs] = useArgs();
156
+ const onChange = (_event: SelectEvent, value: string) => {
157
+ updateArgs({ value });
158
+ };
159
+
160
+ return (
161
+ <Select
162
+ {...args}
163
+ options={args.options}
164
+ value={args.value}
165
+ onChange={onChange}
166
+ />
167
+ );
168
+ },
22
169
  };
23
170
 
24
- export const NoOptions: Story = {
25
- name: 'No options',
26
- render: () => <Select />,
171
+ export const ManyOptions: Story = {
172
+ name: 'Many options',
173
+ args: {
174
+ options: [
175
+ { text: 'Option one', value: '1' },
176
+ { text: 'Option two', value: '2' },
177
+ { text: 'Option three', value: '3' },
178
+ { text: 'Option four', value: '4' },
179
+ { text: 'Option five', value: '5' },
180
+ { text: 'Option six', value: '6' },
181
+ { text: 'Option seven', value: '7' },
182
+ { text: 'Option eight', value: '8' },
183
+ { text: 'Option nine', value: '9' },
184
+ { text: 'Option ten', value: '10' },
185
+ { text: 'Option eleven', value: '11' },
186
+ { text: 'Option twelve', value: '12' },
187
+ { text: 'Option thirteen', value: '13' },
188
+ { text: 'Option fourteen', value: '14' },
189
+ { text: 'Option fifteen', value: '15' },
190
+ ],
191
+ },
192
+ render: () => {
193
+ const [args, updateArgs] = useArgs();
194
+ const onChange = (_event: SelectEvent, value: string) => {
195
+ updateArgs({ value });
196
+ };
197
+ return (
198
+ <Select
199
+ {...args}
200
+ options={args.options}
201
+ value={args.value}
202
+ onChange={onChange}
203
+ />
204
+ );
205
+ },
27
206
  };
@@ -1,82 +1,39 @@
1
- import {
2
- memo,
3
- SelectHTMLAttributes,
4
- forwardRef,
5
- } from 'react';
6
- import { css, cx } from '@emotion/css';
7
- import { useTheme } from '../..';
8
- import { dataUri as chevronDownSvgDataUri } from '../Icon/svgs/ChevronDownSvg';
9
-
10
- export const NAME = 'ucl-uikit-select';
11
-
12
- export interface SelectProps
13
- extends SelectHTMLAttributes<HTMLSelectElement> {
14
- testId?: string;
15
- }
16
-
17
- export type Ref = HTMLSelectElement;
18
-
19
- const Select = forwardRef<Ref, SelectProps>(
20
- ({ testId = NAME, className, ...props }, ref) => {
21
- const [theme] = useTheme();
22
-
23
- const baseStyle = css`
24
- height: ${theme.padding.p48};
25
- line-height: ${theme.padding.p48};
26
- font-family: ${theme.font.family.primary};
27
- padding: 0 ${theme.padding.p40} 0 ${theme.padding.p16};
28
- background-color: ${theme.color.neutral.white};
29
- border: ${theme.border.b1} solid
30
- ${theme.color.neutral.grey40};
31
- appearance: none;
32
- -webkit-appearance: none;
33
- -moz-appearance: none;
34
- outline: none;
35
- cursor: pointer;
36
-
37
- background-image: url(${chevronDownSvgDataUri(
38
- theme.color.interaction.blue70
39
- )});
40
- background-size: 24px 24px;
41
- background-position: right 8px center;
42
- background-repeat: no-repeat;
43
- `;
44
-
45
- const hoverStyle = css`
46
- &:hover {
47
- border-color: ${theme.color.neutral.grey60};
48
- background-color: ${theme.color.neutral.grey5};
49
- }
50
- `;
51
-
52
- const focusStyle = css`
53
- &:focus {
54
- box-shadow: ${theme.boxShadow.focus};
55
- }
56
- `;
57
-
58
- const disabledStyle = css`
59
- cursor: not-allowed;
60
- `;
61
-
62
- const style = cx(
63
- NAME,
64
- baseStyle,
65
- !props.disabled && hoverStyle,
66
- !props.disabled && focusStyle,
67
- props.disabled && disabledStyle,
68
- className
1
+ import { NativeSelect, CustomSelect } from './subcomponents';
2
+ import { SelectProps } from './Select.types';
3
+
4
+ const Select = (props: SelectProps) => {
5
+ if (props.native) {
6
+ // We can throw away `native` prop before passing to internal components
7
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
8
+ const { onChange, value, native, ...rest } = props;
9
+ const handleNativeChange = (
10
+ event: React.ChangeEvent<HTMLSelectElement>
11
+ ) => {
12
+ if (onChange) onChange(event, event.target.value);
13
+ };
14
+ return (
15
+ <NativeSelect
16
+ onChange={handleNativeChange}
17
+ value={value || ''}
18
+ {...rest}
19
+ />
69
20
  );
70
-
21
+ } else {
22
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
23
+ const { onChange, native, ...rest } = props;
24
+ const handleCustomChange = (
25
+ event: React.MouseEvent | React.KeyboardEvent,
26
+ value: string
27
+ ) => {
28
+ if (onChange) onChange(event, value);
29
+ };
71
30
  return (
72
- <select
73
- ref={ref}
74
- className={style}
75
- data-testid={testId}
76
- {...props}
31
+ <CustomSelect
32
+ onChange={handleCustomChange}
33
+ {...rest}
77
34
  />
78
35
  );
79
36
  }
80
- );
37
+ };
81
38
 
82
- export default memo(Select);
39
+ export default Select;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Represents an 'option' in the <Select>
3
+ * Maps to a native <option> element in <NativeSelect>
4
+ * or a <CustomOption> in <CustomSelect>
5
+ */
6
+ export type OptionData = {
7
+ /**
8
+ * Display text shown to the user
9
+ */
10
+ text: string;
11
+ /**
12
+ * Data-friendly value that is returned when the option is selected
13
+ * We assume this will be submitted to a server or used in some other way
14
+ */
15
+ value: string;
16
+ /**
17
+ * Optional icon to be displayed to the left of the option text.
18
+ * Only used by <CustomOption>
19
+ * Takes in an actual <Icon> component
20
+ */
21
+ icon?: React.ReactNode;
22
+ };
23
+
24
+ /**
25
+ * Utility type we expose to developers
26
+ * We expect this to be used in typing event handlers
27
+ * This only applies to the top-level <Select> component
28
+ */
29
+ export type SelectEvent =
30
+ | React.ChangeEvent<HTMLSelectElement>
31
+ | React.MouseEvent
32
+ | React.KeyboardEvent;
33
+
34
+ /**
35
+ * Top level props that <Select> accepts when implemented
36
+ */
37
+ interface BaseSelectProps {
38
+ /**
39
+ * An array of option data, to be rendered either natively or custom
40
+ */
41
+ options: OptionData[];
42
+ /**
43
+ * The currently selected value
44
+ * This determines which option is shown when the select is closed
45
+ */
46
+ value: string | undefined | null;
47
+ /**
48
+ * Generic onChange that splits into native and custom versions
49
+ * The original event is exposed, and the value always returns the value of the selected option
50
+ */
51
+ onChange: (event: SelectEvent, value: string) => void;
52
+ /**
53
+ * Prevents use, including focus events
54
+ */
55
+ disabled?: boolean | undefined;
56
+ /**
57
+ * Placeholder text shown when no option is selected
58
+ * Displayed in visible field of custom implementation
59
+ * or as a disabled option in the native implementation
60
+ */
61
+ placeholder?: string;
62
+ /**
63
+ * Specify a specific width if the default (325px) is not suitable
64
+ */
65
+ width?: number;
66
+ /**
67
+ * Test ID for testing purposes
68
+ * This is passed to the root element of the component
69
+ * as `data-testid`
70
+ */
71
+ testId?: string;
72
+ /**
73
+ * Optional className for styling
74
+ * This is passed to the root element of the component
75
+ * for additional CSS styling via Emotion.
76
+ */
77
+ className?: string;
78
+ /**
79
+ * Native flag determines which implementation to use
80
+ */
81
+ native?: boolean;
82
+ // `ref` prop added in the discriminated union below
83
+ }
84
+
85
+ export type SelectProps = BaseSelectProps &
86
+ // Discriminated union to determine which implementation to use
87
+ (| ({ native: true; ref?: React.RefObject<HTMLSelectElement | null> } & Omit<
88
+ React.SelectHTMLAttributes<HTMLSelectElement>,
89
+ keyof BaseSelectProps
90
+ >)
91
+ | ({ native?: false; ref?: React.RefObject<HTMLDivElement | null> } & Omit<
92
+ React.HTMLAttributes<HTMLDivElement>,
93
+ keyof BaseSelectProps
94
+ >)
95
+ );
96
+
97
+ // Props interface for the two variants are separated.
98
+ // We expose SelectProps for developers to use, and handle discrepancies internally.
99
+
100
+ /**
101
+ * Internal props for the custom implementation, with <div> as root element
102
+ * onChange already exists on <div>. We override it.
103
+ */
104
+ export interface CustomSelectProps
105
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
106
+ options: OptionData[];
107
+ value: string | undefined | null;
108
+ onChange: (
109
+ // Stripping out the native <select> event signature
110
+ event: React.MouseEvent | React.KeyboardEvent,
111
+ // Value returned by <CustomOption>: we validate there
112
+ value: string
113
+ ) => void;
114
+ disabled?: boolean;
115
+ placeholder?: string;
116
+ width?: number;
117
+ testId?: string;
118
+ className?: string;
119
+ ref?: React.RefObject<HTMLDivElement | null>;
120
+ }
121
+
122
+ /**
123
+ * Internal props for native implementation, with <select> as root element
124
+ * Default props like value and onChange are passed to the <select> element automatically
125
+ */
126
+ export interface NativeSelectProps
127
+ extends React.SelectHTMLAttributes<HTMLSelectElement> {
128
+ options: OptionData[];
129
+ placeholder?: string;
130
+ width?: number;
131
+ testId?: string;
132
+ className?: string;
133
+ ref?: React.RefObject<HTMLSelectElement | null>;
134
+ }
135
+
136
+ /**
137
+ * Each option as displayed in the Panel of <CustomSelect>
138
+ * Roughly equivalent to a custom version of <option>
139
+ */
140
+ export interface CustomOptionProps
141
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {
142
+ value: string;
143
+ testId?: string;
144
+ isSelected?: boolean;
145
+ onSelect: (event: React.MouseEvent, value: string) => void;
146
+ }