@indico-data/design-system 2.9.0 → 2.11.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.
Files changed (54) hide show
  1. package/lib/index.css +119 -8
  2. package/lib/index.d.ts +52 -8
  3. package/lib/index.esm.css +119 -8
  4. package/lib/index.esm.js +35 -19
  5. package/lib/index.esm.js.map +1 -1
  6. package/lib/index.js +36 -18
  7. package/lib/index.js.map +1 -1
  8. package/lib/src/components/forms/checkbox/Checkbox.d.ts +2 -1
  9. package/lib/src/components/forms/form/Form.d.ts +14 -0
  10. package/lib/src/components/forms/form/Form.stories.d.ts +8 -0
  11. package/lib/src/components/forms/input/Input.d.ts +5 -4
  12. package/lib/src/components/forms/passwordInput/PasswordInput.d.ts +17 -0
  13. package/lib/src/components/forms/passwordInput/PasswordInput.stories.d.ts +11 -0
  14. package/lib/src/components/forms/passwordInput/__tests__/PasswordInput.test.d.ts +1 -0
  15. package/lib/src/components/forms/passwordInput/index.d.ts +1 -0
  16. package/lib/src/components/forms/radio/Radio.d.ts +2 -1
  17. package/lib/src/components/forms/subcomponents/DisplayFormError.d.ts +5 -0
  18. package/lib/src/components/forms/textarea/Textarea.d.ts +4 -3
  19. package/lib/src/components/forms/toggle/Toggle.d.ts +2 -1
  20. package/lib/src/components/index.d.ts +2 -0
  21. package/lib/src/index.d.ts +2 -0
  22. package/package.json +5 -2
  23. package/src/components/forms/checkbox/Checkbox.stories.tsx +2 -2
  24. package/src/components/forms/checkbox/Checkbox.tsx +32 -41
  25. package/src/components/forms/form/Form.mdx +134 -0
  26. package/src/components/forms/form/Form.stories.tsx +413 -0
  27. package/src/components/forms/form/Form.tsx +64 -0
  28. package/src/components/forms/form/__tests__/Form.test.tsx +35 -0
  29. package/src/components/forms/form/index.ts +0 -0
  30. package/src/components/forms/form/styles/Form.scss +3 -0
  31. package/src/components/forms/input/Input.stories.tsx +0 -5
  32. package/src/components/forms/input/Input.tsx +67 -64
  33. package/src/components/forms/input/__tests__/Input.test.tsx +2 -13
  34. package/src/components/forms/input/styles/Input.scss +2 -8
  35. package/src/components/forms/passwordInput/PasswordInput.mdx +28 -0
  36. package/src/components/forms/passwordInput/PasswordInput.stories.tsx +268 -0
  37. package/src/components/forms/passwordInput/PasswordInput.tsx +86 -0
  38. package/src/components/forms/passwordInput/__tests__/PasswordInput.test.tsx +129 -0
  39. package/src/components/forms/passwordInput/index.ts +1 -0
  40. package/src/components/forms/passwordInput/styles/PasswordInput.scss +120 -0
  41. package/src/components/forms/radio/Radio.tsx +32 -35
  42. package/src/components/forms/subcomponents/DisplayFormError.tsx +7 -0
  43. package/src/components/forms/textarea/Textarea.stories.tsx +15 -21
  44. package/src/components/forms/textarea/Textarea.tsx +64 -62
  45. package/src/components/forms/textarea/__tests__/Textarea.test.tsx +1 -1
  46. package/src/components/forms/textarea/styles/Textarea.scss +1 -1
  47. package/src/components/forms/toggle/Toggle.tsx +30 -37
  48. package/src/components/index.ts +2 -0
  49. package/src/index.ts +2 -0
  50. package/src/styles/index.scss +2 -0
  51. package/lib/src/components/forms/subcomponents/ErrorList.d.ts +0 -6
  52. package/src/components/forms/subcomponents/ErrorList.tsx +0 -14
  53. package/src/components/forms/subcomponents/__tests__/ErrorList.test.tsx +0 -16
  54. /package/lib/src/components/forms/{subcomponents/__tests__/ErrorList.test.d.ts → form/__tests__/Form.test.d.ts} +0 -0
@@ -8,5 +8,6 @@ export interface CheckboxProps {
8
8
  isChecked?: boolean | undefined;
9
9
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
10
10
  isDisabled?: boolean;
11
+ defaultChecked?: boolean;
11
12
  }
12
- export declare const Checkbox: ({ ref, id, label, name, value, onChange, isDisabled, isChecked, ...rest }: CheckboxProps) => import("react/jsx-runtime").JSX.Element;
13
+ export declare const Checkbox: React.ForwardRefExoticComponent<Omit<CheckboxProps, "ref"> & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ export interface FormProps {
3
+ children: React.ReactNode;
4
+ className?: string;
5
+ action?: string;
6
+ method?: 'get' | 'post' | 'dialog' | 'delete' | 'put';
7
+ target?: '_blank' | '_self' | '_parent' | '_top';
8
+ autocomplete?: 'on' | 'off';
9
+ noValidate?: boolean;
10
+ enctype?: 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain';
11
+ rel?: string;
12
+ onSubmit?: (formObject: Record<string, string>) => void;
13
+ }
14
+ export declare const Form: ({ children, onSubmit, action, method, target, autocomplete, noValidate, enctype, rel, ...rest }: FormProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { Form } from './Form';
3
+ declare const meta: Meta;
4
+ export default meta;
5
+ type Story = StoryObj<typeof Form>;
6
+ export declare const Controlled: Story;
7
+ export declare const Uncontrolled: Story;
8
+ export declare const UncontrolledPreFilled: Story;
@@ -1,18 +1,19 @@
1
1
  import React from 'react';
2
2
  import { IconName } from '@/types';
3
3
  export interface InputProps {
4
- ref?: React.LegacyRef<HTMLInputElement>;
5
4
  label: string;
6
5
  name: string;
6
+ value?: string | undefined;
7
7
  placeholder: string;
8
- value: string;
9
8
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
10
9
  isRequired?: boolean;
11
10
  isDisabled?: boolean;
12
- errorList?: string[];
11
+ errorMessage?: string | undefined;
13
12
  helpText?: string;
14
13
  hasHiddenLabel?: boolean;
15
14
  iconName?: IconName;
16
15
  isClearable?: boolean;
16
+ className?: string;
17
+ defaultValue?: string;
17
18
  }
18
- export declare const Input: ({ ref, label, name, placeholder, value, onChange, isRequired, isDisabled, errorList, helpText, iconName, hasHiddenLabel, isClearable, ...rest }: InputProps) => import("react/jsx-runtime").JSX.Element;
19
+ export declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ export interface PasswordInputProps {
3
+ ref?: React.LegacyRef<HTMLInputElement>;
4
+ label: string;
5
+ value?: string | undefined;
6
+ name: string;
7
+ placeholder: string;
8
+ onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
9
+ isRequired?: boolean;
10
+ isDisabled?: boolean;
11
+ errorMessage?: string | undefined;
12
+ helpText?: string;
13
+ hasHiddenLabel?: boolean;
14
+ hasShowPassword?: boolean;
15
+ defaultValue?: string;
16
+ }
17
+ export declare const PasswordInput: React.ForwardRefExoticComponent<Omit<PasswordInputProps, "ref"> & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,11 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { PasswordInput } from './PasswordInput';
3
+ declare const meta: Meta;
4
+ export default meta;
5
+ type Story = StoryObj<typeof PasswordInput>;
6
+ export declare const Default: Story;
7
+ export declare const Errors: Story;
8
+ export declare const HiddenLabel: Story;
9
+ export declare const HelpText: Story;
10
+ export declare const Required: Story;
11
+ export declare const NoTogglePasswordVisibility: Story;
@@ -0,0 +1 @@
1
+ export { PasswordInput } from './PasswordInput';
@@ -7,5 +7,6 @@ export interface RadioProps {
7
7
  value?: string;
8
8
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
9
9
  isDisabled?: boolean;
10
+ defaultChecked?: boolean;
10
11
  }
11
- export declare const Radio: ({ ref, id, label, name, value, onChange, isDisabled, ...rest }: RadioProps) => import("react/jsx-runtime").JSX.Element;
12
+ export declare const Radio: React.ForwardRefExoticComponent<Omit<RadioProps, "ref"> & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,5 @@
1
+ interface DisplayFormErrorProps {
2
+ message: string | undefined;
3
+ }
4
+ export declare const DisplayFormError: ({ message }: DisplayFormErrorProps) => import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -4,11 +4,11 @@ export interface TextareaProps {
4
4
  label: string;
5
5
  name: string;
6
6
  placeholder: string;
7
- value: string;
7
+ value?: string | undefined;
8
8
  onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
9
9
  isRequired?: boolean;
10
10
  isDisabled?: boolean;
11
- errorList?: string[];
11
+ errorMessage?: string | undefined;
12
12
  helpText?: string;
13
13
  hasHiddenLabel?: boolean;
14
14
  rows?: number;
@@ -18,5 +18,6 @@ export interface TextareaProps {
18
18
  form?: string;
19
19
  maxLength?: number;
20
20
  autofocus?: boolean;
21
+ defaultValue?: string;
21
22
  }
22
- export declare const Textarea: ({ ref, label, name, placeholder, value, onChange, isRequired, isDisabled, errorList, helpText, hasHiddenLabel, rows, cols, readonly, wrap, form, maxLength, autofocus, ...rest }: TextareaProps) => import("react/jsx-runtime").JSX.Element;
23
+ export declare const Textarea: React.ForwardRefExoticComponent<Omit<TextareaProps, "ref"> & React.RefAttributes<HTMLTextAreaElement>>;
@@ -8,5 +8,6 @@ export interface ToggleProps {
8
8
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
9
9
  isDisabled?: boolean;
10
10
  isChecked?: boolean;
11
+ defaultChecked?: boolean;
11
12
  }
12
- export declare const Toggle: ({ ref, id, label, name, value, onChange, isDisabled, isChecked, ...rest }: ToggleProps) => import("react/jsx-runtime").JSX.Element;
13
+ export declare const Toggle: React.ForwardRefExoticComponent<Omit<ToggleProps, "ref"> & React.RefAttributes<HTMLInputElement>>;
@@ -6,3 +6,5 @@ export { Input } from './forms/input';
6
6
  export { Radio } from './forms/radio';
7
7
  export { Checkbox } from './forms/checkbox';
8
8
  export { Toggle } from './forms/toggle';
9
+ export { Textarea } from './forms/textarea';
10
+ export { PasswordInput } from './forms/passwordInput';
@@ -11,3 +11,5 @@ export { Input } from './components/forms/input';
11
11
  export { Radio as RadioInput } from './components/forms/radio';
12
12
  export { Checkbox } from './components/forms/checkbox';
13
13
  export { Toggle as ToggleInput } from './components/forms/toggle';
14
+ export { Textarea } from './components/forms/textarea';
15
+ export { PasswordInput } from './components/forms/passwordInput';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indico-data/design-system",
3
- "version": "2.9.0",
3
+ "version": "2.11.0",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "main": "lib/index.js",
@@ -32,6 +32,7 @@
32
32
  "@fortawesome/free-regular-svg-icons": "^6.5.2",
33
33
  "@fortawesome/free-solid-svg-icons": "^6.5.2",
34
34
  "@fortawesome/react-fontawesome": "^0.2.0",
35
+ "@hookform/resolvers": "^3.9.0",
35
36
  "@indico-data/utils": "^0.0.5",
36
37
  "@popperjs/core": "^2.11.8",
37
38
  "@react-aria/radio": "^3.10.3",
@@ -49,6 +50,7 @@
49
50
  "react-data-table-component": "^7.6.2",
50
51
  "react-day-picker": "^8.10.1",
51
52
  "react-grid-system": "^8.2.0",
53
+ "react-hook-form": "^7.52.1",
52
54
  "react-modal": "^3.16.1",
53
55
  "react-popper": "^2.3.0",
54
56
  "react-stately": "^3.31.0",
@@ -59,7 +61,8 @@
59
61
  "uuid": "^9.0.1",
60
62
  "webpack": "^5.91.0",
61
63
  "webpack-cli": "^5.1.4",
62
- "webpack-dev-server": "^5.0.4"
64
+ "webpack-dev-server": "^5.0.4",
65
+ "zod": "^3.23.8"
63
66
  },
64
67
  "devDependencies": {
65
68
  "@babel/core": "^7.23.9",
@@ -1,6 +1,6 @@
1
1
  import { Meta, StoryObj } from '@storybook/react';
2
- import { Checkbox, CheckboxProps } from './Checkbox';
3
- import { SetStateAction, useState } from 'react';
2
+ import { Checkbox } from './Checkbox';
3
+ import { useState } from 'react';
4
4
 
5
5
  const meta: Meta = {
6
6
  title: 'Forms/Checkbox',
@@ -1,4 +1,3 @@
1
- import { isChecked } from '@indico-data/utils/dist/validators';
2
1
  import React from 'react';
3
2
 
4
3
  export interface CheckboxProps {
@@ -10,46 +9,38 @@ export interface CheckboxProps {
10
9
  isChecked?: boolean | undefined;
11
10
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
12
11
  isDisabled?: boolean;
12
+ defaultChecked?: boolean;
13
13
  }
14
14
 
15
- export const Checkbox = ({
16
- ref,
17
- id,
18
- label,
19
- name,
20
- value,
21
- onChange,
22
- isDisabled,
23
- isChecked = false,
24
- ...rest
25
- }: CheckboxProps) => {
26
- return (
27
- <div className="form-control">
28
- <div className="checkbox-wrapper">
29
- <input
30
- data-testid={`form-checkbox-input-${name}`}
31
- className="checkbox-input"
32
- type="checkbox"
33
- id={id}
34
- checked={isChecked}
35
- name={name}
36
- value={value}
37
- disabled={isDisabled}
38
- ref={ref}
39
- onChange={onChange}
40
- tabIndex={0}
41
- aria-describedby={id}
42
- aria-label={label}
43
- {...rest}
44
- />
45
- <label
46
- htmlFor={id}
47
- className="checkbox-input-label"
48
- data-testid={`label-checkbox-input-${name}`}
49
- >
50
- {label}
51
- </label>
15
+ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
16
+ ({ id, label, name, value, onChange, isDisabled, isChecked = false, ...rest }, ref) => {
17
+ return (
18
+ <div className="form-control">
19
+ <div className="checkbox-wrapper">
20
+ <input
21
+ data-testid={`form-checkbox-input-${name}`}
22
+ className="checkbox-input"
23
+ type="checkbox"
24
+ id={id}
25
+ name={name}
26
+ value={value}
27
+ disabled={isDisabled}
28
+ ref={ref}
29
+ onChange={onChange}
30
+ tabIndex={0}
31
+ aria-describedby={id}
32
+ aria-label={label}
33
+ {...rest}
34
+ />
35
+ <label
36
+ htmlFor={id}
37
+ className="checkbox-input-label"
38
+ data-testid={`label-checkbox-input-${name}`}
39
+ >
40
+ {label}
41
+ </label>
42
+ </div>
52
43
  </div>
53
- </div>
54
- );
55
- };
44
+ );
45
+ },
46
+ );
@@ -0,0 +1,134 @@
1
+ import { Canvas, Meta, Controls } from '@storybook/blocks';
2
+ import * as Form from './Form.stories';
3
+
4
+ <Meta title="Forms/Form" name="Form" />
5
+
6
+ # Form
7
+
8
+ The Form component is a wrapper for the html form element. It has the added value that it will apply the `onSubmit` event to the form element and prevent the default behavior of the form submission. This will allow you to handle the form submission in a more controlled manner. We also leverage two third party libraries in this source code demo, however, they are not baked into the form component. They mainly serve as an example of how we would build a form in our applications.
9
+
10
+
11
+ <Canvas
12
+ of={Form.Uncontrolled}
13
+ source={{
14
+ code: `
15
+ // This sets the form validation
16
+ const schema = z
17
+ .object({
18
+ firstName: z.string().min(1, 'First name is required').max(10),
19
+ lastName: z.string().min(1, 'Last name is required').max(10),
20
+ email: z.string().min(1, 'Email is required').email(),
21
+ password: z
22
+ .string()
23
+ .min(1, 'Password is required')
24
+ .max(100)
25
+ .regex(
26
+ /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
27
+ 'Password must be at least 8 characters long and include at least one uppercase letter, one lowercase letter, one number, and one special character (@, $, !, %, *, ?, &).',
28
+ ),
29
+ confirmPassword: z.string().min(1, 'Password is required').max(100),
30
+ description: z.string().max(255).optional(),
31
+ terms: z.boolean().optional(),
32
+ rpgClass: z.enum(['Rogue', 'Warrior', 'Mage']).optional(),
33
+ toggle: z.boolean().optional(),
34
+ })
35
+ .refine((data) => data.password === data.confirmPassword, {
36
+ message: "Passwords don't match",
37
+ path: ['confirmPassword'],
38
+ });
39
+
40
+ interface FormValues {
41
+ firstName: string;
42
+ lastName: string;
43
+ email: string;
44
+ password: string;
45
+ confirmPassword: string;
46
+ description: string;
47
+ terms: boolean;
48
+ rpgClass: string;
49
+ toggle: boolean;
50
+ }
51
+
52
+ const {
53
+ register,
54
+ handleSubmit,
55
+ formState: { errors },
56
+ } = useForm<FormValues>({ mode: 'onChange', resolver: zodResolver(schema) });
57
+
58
+ <Form onSubmit={() => handleSubmit(onSubmit)}>
59
+ <Input
60
+ placeholder={'Please enter your first name.'}
61
+ label="First name"
62
+ isRequired
63
+ errorMessage={errors.firstName ? errors.firstName.message?.toString() : ''}
64
+ {...register('firstName')}
65
+ />
66
+ <Input
67
+ placeholder={'Please enter your last name.'}
68
+ label="Last name"
69
+ isRequired
70
+ errorMessage={errors.lastName ? errors.lastName.message?.toString() : ''}
71
+ {...register('lastName')}
72
+ />
73
+ <Input
74
+ placeholder={'Please enter your email.'}
75
+ label="Email"
76
+ isRequired
77
+ errorMessage={errors.email ? errors.email.message?.toString() : ''}
78
+ {...register('email')}
79
+ />
80
+ <PasswordInput
81
+ placeholder={'Please enter your password.'}
82
+ label="Password"
83
+ isRequired
84
+ errorMessage={errors.password ? errors.password.message?.toString() : ''}
85
+ {...register('password')}
86
+ />
87
+ <PasswordInput
88
+ placeholder={'Please enter your password again.'}
89
+ label="Confirm Password"
90
+ isRequired
91
+ errorMessage={
92
+ errors.confirmPassword ? errors.confirmPassword.message?.toString() : ''
93
+ }
94
+ {...register('confirmPassword')}
95
+ />
96
+
97
+ <Textarea
98
+ label="Description"
99
+ placeholder="Please enter a description"
100
+ {...register('description')}
101
+ />
102
+
103
+ <div className="radio-group mb-5">
104
+ <h2 className="mb-4">Select a class</h2>
105
+ <Radio label="Rogue" value="Rogue" id="rogue" {...register('rpgClass')} />
106
+ <Radio label="Warrior" value="Warruir" id="warrior" {...register('rpgClass')} />
107
+ <Radio label="Mage" value="Mage" id="mage" {...register('rpgClass')} />
108
+ <Radio label="Monk" value="Monk" id="monk" {...register('rpgClass')} />
109
+ </div>
110
+
111
+ <Checkbox label="Do you accept the terms?" id={'one'} {...register('terms')} />
112
+
113
+ <Toggle label="Party On?" id={'Toggle'} {...register('toggle')} />
114
+
115
+ <Button type="submit" ariaLabel={'Submit Form'}>
116
+ Submit Form
117
+ </Button>
118
+ </Form>
119
+ `,
120
+ }}
121
+ />
122
+
123
+ <Controls of={Form.Uncontrolled} />
124
+
125
+ ## Libraries
126
+ - [React Hook Form](https://react-hook-form.com/) - We will leverage this to simplify our form state
127
+ - [Zod](https://www.freecodecamp.org/news/react-form-validation-zod-react-hook-form/) - We will leverage this to validate our form data
128
+
129
+ ## Quick tips
130
+ - Use the `zodResolver` to validate your form data
131
+ - Use the `useForm` hook to manage your form state for uncontrolled forms and the `Controller` component for controlled forms
132
+ - Use the `onSubmit` event to handle your form submission
133
+ - Zod and React Hook Form manage your errors. Only one error may be displayed at once. In the case a value is required and it exceeds the max length, since the required has been met, it will warn you about max length. However if the value is empty, since required was the first error it will display that error.
134
+ - In order to set a form field to required, you will need to set the `isRequired` prop to true on the input component AND set the `min` value to 1 in the zod schema. This is because the zod schema will not validate the required field if the value is empty. This is a library decision and not a bug. This is the best workaround we have.