@indico-data/design-system 2.11.0 → 2.12.0

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.
@@ -0,0 +1 @@
1
+ export { Form } from './Form';
@@ -0,0 +1,6 @@
1
+ import { Props as ReactSelectProps } from 'react-select';
2
+ import { SelectOption } from './types';
3
+ export interface SelectProps<OptionType extends SelectOption> extends ReactSelectProps<OptionType> {
4
+ options: OptionType[];
5
+ }
6
+ export declare const Select: <OptionType extends SelectOption>({ classNamePrefix, className, components: customComponents, ...props }: SelectProps<OptionType>) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { SelectProps } from './Select';
3
+ import { SelectOption } from './types';
4
+ declare const meta: Meta<SelectProps<SelectOption>>;
5
+ export default meta;
6
+ type Story = StoryObj<SelectProps<SelectOption>>;
7
+ export declare const Default: Story;
@@ -0,0 +1 @@
1
+ export { Select } from './Select';
@@ -0,0 +1,6 @@
1
+ export type SelectOption = {
2
+ label: string;
3
+ value: string;
4
+ detail?: string;
5
+ [key: string]: any;
6
+ };
@@ -8,3 +8,5 @@ export { Checkbox } from './forms/checkbox';
8
8
  export { Toggle } from './forms/toggle';
9
9
  export { Textarea } from './forms/textarea';
10
10
  export { PasswordInput } from './forms/passwordInput';
11
+ export { Select } from './forms/select';
12
+ export { Form } from './forms/form';
@@ -13,3 +13,5 @@ export { Checkbox } from './components/forms/checkbox';
13
13
  export { Toggle as ToggleInput } from './components/forms/toggle';
14
14
  export { Textarea } from './components/forms/textarea';
15
15
  export { PasswordInput } from './components/forms/passwordInput';
16
+ export { Select as SelectInput } from './components/forms/select';
17
+ export { Form } from './components/forms/form';
@@ -10,3 +10,5 @@ import { IconName } from '../build/generated/iconTypes';
10
10
  export type { IconName };
11
11
  import { IconSizes } from './components/icons/types';
12
12
  export type { IconSizes };
13
+ import { SelectOption } from './components/forms/select/types';
14
+ export type { SelectOption };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indico-data/design-system",
3
- "version": "2.11.0",
3
+ "version": "2.12.0",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "main": "lib/index.js",
@@ -103,7 +103,7 @@ const {
103
103
  <div className="radio-group mb-5">
104
104
  <h2 className="mb-4">Select a class</h2>
105
105
  <Radio label="Rogue" value="Rogue" id="rogue" {...register('rpgClass')} />
106
- <Radio label="Warrior" value="Warruir" id="warrior" {...register('rpgClass')} />
106
+ <Radio label="Warrior" value="Warrior" id="warrior" {...register('rpgClass')} />
107
107
  <Radio label="Mage" value="Mage" id="mage" {...register('rpgClass')} />
108
108
  <Radio label="Monk" value="Monk" id="monk" {...register('rpgClass')} />
109
109
  </div>
@@ -257,7 +257,7 @@ export const Uncontrolled: Story = {
257
257
  <div className="radio-group mb-5">
258
258
  <h2 className="mb-4">Select a class</h2>
259
259
  <Radio label="Rogue" value="Rogue" id="rogue" {...register('rpgClass')} />
260
- <Radio label="Warrior" value="Warruir" id="warrior" {...register('rpgClass')} />
260
+ <Radio label="Warrior" value="Warrior" id="warrior" {...register('rpgClass')} />
261
261
  <Radio label="Mage" value="Mage" id="mage" {...register('rpgClass')} />
262
262
  <Radio label="Monk" value="Monk" id="monk" {...register('rpgClass')} />
263
263
  </div>
@@ -383,7 +383,7 @@ export const UncontrolledPreFilled: Story = {
383
383
  <Radio label="Rogue" value="Rogue" id="rogue" {...register('rpgClass')} />
384
384
  <Radio
385
385
  label="Warrior"
386
- value="Warruir"
386
+ value="Warrior"
387
387
  id="warrior"
388
388
  {...register('rpgClass')}
389
389
  defaultChecked
@@ -0,0 +1 @@
1
+ export { Form } from './Form';
@@ -0,0 +1,118 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { Select, SelectProps } from './Select';
3
+ import { SelectOption } from './types';
4
+
5
+ const meta: Meta<SelectProps<SelectOption>> = {
6
+ title: 'Forms/Select',
7
+ component: Select,
8
+ argTypes: {
9
+ options: {
10
+ control: 'object',
11
+ description: 'Options for the select component',
12
+ table: {
13
+ category: 'Props',
14
+ type: {
15
+ summary: '{ value: string, label: string, detail?: string }[]',
16
+ },
17
+ },
18
+ defaultValue: { summary: '[]' },
19
+ },
20
+ isDisabled: {
21
+ control: 'boolean',
22
+ description: 'Toggles the disabled state of the select component',
23
+ table: {
24
+ category: 'Props',
25
+ type: {
26
+ summary: 'boolean',
27
+ },
28
+ },
29
+ defaultValue: { summary: 'false' },
30
+ },
31
+ isLoading: {
32
+ control: 'boolean',
33
+ description: 'Toggles the loading state of the select component',
34
+ table: {
35
+ category: 'Props',
36
+ type: {
37
+ summary: 'boolean',
38
+ },
39
+ },
40
+ defaultValue: { summary: 'false' },
41
+ },
42
+ isClearable: {
43
+ control: 'boolean',
44
+ description: 'Enables the clearable feature of the select component',
45
+ table: {
46
+ category: 'Props',
47
+ type: {
48
+ summary: 'boolean',
49
+ },
50
+ },
51
+ defaultValue: { summary: 'false' },
52
+ },
53
+ isSearchable: {
54
+ control: 'boolean',
55
+ description: 'Enables the searchable feature of the select component',
56
+ table: {
57
+ category: 'Props',
58
+ type: {
59
+ summary: 'boolean',
60
+ },
61
+ },
62
+ defaultValue: { summary: 'true' },
63
+ },
64
+ placeholder: {
65
+ control: 'text',
66
+ description: 'The placeholder text for the select component',
67
+ table: {
68
+ category: 'Props',
69
+ type: {
70
+ summary: 'string',
71
+ },
72
+ },
73
+ defaultValue: { summary: 'Select...' },
74
+ },
75
+ className: {
76
+ control: 'text',
77
+ description: 'Additional CSS class for the select component',
78
+ table: {
79
+ category: 'Props',
80
+ type: {
81
+ summary: 'string',
82
+ },
83
+ },
84
+ defaultValue: { summary: '' },
85
+ },
86
+ onChange: {
87
+ control: false,
88
+ description: 'Event handler for when the selected value changes',
89
+ table: {
90
+ category: 'Callbacks',
91
+ type: {
92
+ summary:
93
+ '(newValue: SingleValue<SelectOption> | MultiValue<SelectOption>, actionMeta: ActionMeta<SelectOption>) => void',
94
+ },
95
+ },
96
+ action: 'onChange',
97
+ },
98
+ },
99
+ };
100
+
101
+ export default meta;
102
+
103
+ type Story = StoryObj<SelectProps<SelectOption>>;
104
+
105
+ export const Default: Story = {
106
+ args: {
107
+ options: [
108
+ { value: 'option1', label: 'Option 1', detail: '123 Count' },
109
+ { value: 'option2', label: 'Option 2', detail: '456 Count' },
110
+ { value: 'option3', label: 'Option 3', detail: '789 Count' },
111
+ ],
112
+ placeholder: 'Select an option...',
113
+ isClearable: false,
114
+ isSearchable: true,
115
+ isDisabled: false,
116
+ isLoading: false,
117
+ },
118
+ };
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+ import ReactSelect, { Props as ReactSelectProps, components, OptionProps } from 'react-select';
4
+ import { SelectOption } from './types';
5
+
6
+ export interface SelectProps<OptionType extends SelectOption> extends ReactSelectProps<OptionType> {
7
+ options: OptionType[];
8
+ }
9
+
10
+ const OptionComponent = <OptionType extends SelectOption>({
11
+ ...props
12
+ }: OptionProps<OptionType>) => {
13
+ return (
14
+ <components.Option {...props}>
15
+ <div className="select__items">
16
+ <div className="select__item-value">{props?.data?.label}</div>
17
+ {props?.data?.detail && <div className="select__item-detail">{props?.data?.detail}</div>}
18
+ </div>
19
+ </components.Option>
20
+ );
21
+ };
22
+
23
+ export const Select = <OptionType extends SelectOption>({
24
+ classNamePrefix = 'select',
25
+ className,
26
+ components: customComponents,
27
+ ...props
28
+ }: SelectProps<OptionType>) => {
29
+ const defaultComponents = {
30
+ Option: OptionComponent as React.ComponentType<OptionProps<OptionType>>,
31
+ };
32
+
33
+ const mergedComponents = { ...defaultComponents, ...customComponents };
34
+
35
+ return (
36
+ <ReactSelect
37
+ classNamePrefix={classNamePrefix}
38
+ className={classNames('select-wrapper', className)}
39
+ components={mergedComponents}
40
+ {...props}
41
+ />
42
+ );
43
+ };
@@ -0,0 +1,67 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react';
2
+
3
+ import { Select } from '../Select';
4
+ import { SelectOption } from '../types';
5
+ import { OptionProps, components } from 'react-select';
6
+
7
+ const options: SelectOption[] = [
8
+ { value: 'option1', label: 'Option 1' },
9
+ { value: 'option2', label: 'Option 2' },
10
+ { value: 'option3', label: 'Option 3' },
11
+ ];
12
+
13
+ describe('Select Component', () => {
14
+ it('displays options correctly', () => {
15
+ render(<Select options={options} menuIsOpen />);
16
+ options.forEach((option) => {
17
+ expect(screen.getByText(option.label)).toBeInTheDocument();
18
+ });
19
+ });
20
+
21
+ it('handles onChange event', () => {
22
+ const handleChange = jest.fn();
23
+ render(<Select options={options} menuIsOpen onChange={handleChange} />);
24
+ const option = screen.getByText('Option 1');
25
+ fireEvent.click(option);
26
+ expect(handleChange).toHaveBeenCalledWith(options[0], expect.any(Object));
27
+ });
28
+
29
+ it('applies custom class names', () => {
30
+ const customClassName = 'custom-select-class';
31
+ render(<Select options={options} className={customClassName} />);
32
+ const selectWrapper = screen.getByRole('combobox').closest('.select-wrapper');
33
+ expect(selectWrapper).toHaveClass(customClassName);
34
+ });
35
+
36
+ it('applies custom class name prefix', () => {
37
+ const customClassNamePrefix = 'custom-prefix';
38
+ render(<Select options={options} classNamePrefix={customClassNamePrefix} />);
39
+ const selectWrapper = screen.getByRole('combobox');
40
+ expect(selectWrapper).toHaveClass(`${customClassNamePrefix}__input`);
41
+ });
42
+
43
+ it('displays detail when present', () => {
44
+ const optionsWithDetail = options.map((option, index) => ({
45
+ ...option,
46
+ detail: `${index + 1}23 Count`,
47
+ }));
48
+
49
+ render(<Select options={optionsWithDetail} menuIsOpen />);
50
+ optionsWithDetail.forEach((option) => {
51
+ expect(screen.getByText(option.detail)).toBeInTheDocument();
52
+ });
53
+ });
54
+
55
+ it('uses custom components', () => {
56
+ const oneOption = options.slice(0, 1);
57
+ const CustomOptionComponent = (props: OptionProps<SelectOption>) => (
58
+ <div data-testid="custom-option">{props.data.label}</div>
59
+ );
60
+
61
+ render(
62
+ <Select options={oneOption} components={{ Option: CustomOptionComponent }} menuIsOpen />,
63
+ );
64
+
65
+ expect(screen.getByTestId('custom-option')).toHaveTextContent(oneOption[0].label);
66
+ });
67
+ });
@@ -0,0 +1 @@
1
+ export { Select } from './Select';
@@ -0,0 +1,120 @@
1
+ // Common Variables
2
+ :root,
3
+ :root [data-theme='light'] {
4
+ --pf-select-background-color: var(--pf-white-color);
5
+ --pf-select-border-color: var(--pf-gray-color);
6
+ --pf-select-text-color: var(--pf-gray-color);
7
+ --pf-select-indicator-color: var(--pf-gray-color);
8
+ --pf-select-placeholder-text-color: var(--pf-gray-color);
9
+ --pf-select-hover-color: var(--pf-gray-color);
10
+ --pf-select-option-selected-color: var(--pf-primary-color-200);
11
+ --pf-select-option-text-color: var(--pf-gray-color);
12
+ --pf-select-option-hover-color: var(--pf-primary-color-100);
13
+
14
+ // Disabled
15
+ --pf-select-disabled-background-color: var(--pf-gray-color-100);
16
+ --pf-select-disabled-color: var(--pf-gray-color-400);
17
+ }
18
+
19
+ // Dark Theme Specific Variables
20
+ :root [data-theme='dark'] {
21
+ --pf-select-background-color: var(--pf-primary-color);
22
+ --pf-select-border-color: var(--pf-gray-color-100);
23
+ --pf-select-text-color: var(--pf-gray-color-100);
24
+ --pf-select-indicator-color: var(--pf-gray-color-100);
25
+ --pf-select-placeholder-text-color: var(--pf-gray-color-100);
26
+ --pf-select-hover-color: var(--pf-gray-color-100);
27
+ --pf-select-option-selected-color: var(--pf-primary-color-200);
28
+ --pf-select-option-text-color: var(--pf-gray-color-100);
29
+ --pf-select-option-hover-color: var(--pf-primary-color-300);
30
+
31
+ // Disabled
32
+ --pf-select-disabled-background-color: var(--pf-primary-color-200);
33
+ --pf-select-disabled-color: var(--pf-gray-color-400);
34
+ }
35
+
36
+ .select-wrapper {
37
+ .select__ {
38
+ &control {
39
+ background-color: var(--pf-select-background-color);
40
+ border: 1px solid var(--pf-select-border-color);
41
+ color: var(--pf-select-text-color);
42
+ box-shadow: none;
43
+
44
+ &:hover,
45
+ &:focus,
46
+ &--is-focused {
47
+ border: 1px solid var(--pf-select-hover-color);
48
+ }
49
+
50
+ &--is-disabled {
51
+ background-color: var(--pf-select-disabled-background-color);
52
+ color: var(--pf-select-disabled-color);
53
+ }
54
+ }
55
+
56
+ &placeholder {
57
+ color: var(--pf-select-placeholder-text-color);
58
+ }
59
+
60
+ &value-container {
61
+ cursor: text;
62
+ }
63
+
64
+ &input-container {
65
+ color: var(--pf-select-text-color);
66
+ }
67
+
68
+ &menu {
69
+ border: 1px solid var(--pf-select-border-color);
70
+ box-shadow: 0px 1px 8px 0px rgba(0, 0, 0, 0.4);
71
+ margin: 4px 0px;
72
+ background-color: var(--pf-select-background-color);
73
+ }
74
+
75
+ &menu-notice {
76
+ color: var(--pf-select-text-color);
77
+ }
78
+
79
+ &indicator-separator {
80
+ background-color: var(--pf-select-indicator-color);
81
+ }
82
+
83
+ &indicator {
84
+ cursor: pointer;
85
+ color: var(--pf-select-indicator-color);
86
+
87
+ &:hover,
88
+ &:focus {
89
+ color: var(--pf-select-hover-color);
90
+ }
91
+ }
92
+
93
+ &items {
94
+ display: flex;
95
+ align-items: center;
96
+ }
97
+
98
+ &item-detail {
99
+ margin-left: auto;
100
+ }
101
+
102
+ &option {
103
+ cursor: pointer;
104
+ overflow-wrap: break-word;
105
+ color: var(--pf-select-option-text-color);
106
+ &--is-focused,
107
+ &:active {
108
+ background: var(--pf-select-option-hover-color);
109
+ }
110
+
111
+ &--is-selected {
112
+ background: var(--pf-select-option-selected-color);
113
+ }
114
+ }
115
+
116
+ &single-value {
117
+ color: var(--pf-select-option-text-color);
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,6 @@
1
+ export type SelectOption = {
2
+ label: string;
3
+ value: string;
4
+ detail?: string;
5
+ [key: string]: any; // Allow for additional properties
6
+ };
@@ -8,3 +8,5 @@ export { Checkbox } from './forms/checkbox';
8
8
  export { Toggle } from './forms/toggle';
9
9
  export { Textarea } from './forms/textarea';
10
10
  export { PasswordInput } from './forms/passwordInput';
11
+ export { Select } from './forms/select';
12
+ export { Form } from './forms/form';
package/src/index.ts CHANGED
@@ -67,3 +67,5 @@ export { Checkbox } from './components/forms/checkbox';
67
67
  export { Toggle as ToggleInput } from './components/forms/toggle';
68
68
  export { Textarea } from './components/forms/textarea';
69
69
  export { PasswordInput } from './components/forms/passwordInput';
70
+ export { Select as SelectInput } from './components/forms/select';
71
+ export { Form } from './components/forms/form';
@@ -12,8 +12,9 @@
12
12
  @import '../components/forms/textarea/styles/Textarea.scss';
13
13
  @import '../components/forms/passwordInput/styles/PasswordInput.scss';
14
14
  @import '../components/forms/form/styles/Form.scss';
15
-
15
+ @import '../components/forms/select/styles/Select.scss';
16
16
  @import '../components/forms/toggle/styles/Toggle.scss';
17
+
17
18
  @import 'typography';
18
19
  @import 'colors';
19
20
  @import 'borders';
package/src/types.ts CHANGED
@@ -13,3 +13,6 @@ export type { IconName };
13
13
 
14
14
  import { IconSizes } from './components/icons/types';
15
15
  export type { IconSizes };
16
+
17
+ import { SelectOption } from './components/forms/select/types';
18
+ export type { SelectOption };