paris 0.2.1 → 0.3.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 (108) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +61 -5
  3. package/package.json +28 -13
  4. package/src/helpers/renderEnhancer.tsx +21 -0
  5. package/src/stories/Tokens.mdx +0 -8
  6. package/src/stories/button/Button.module.scss +12 -6
  7. package/src/stories/button/Button.stories.ts +17 -0
  8. package/src/stories/button/Button.tsx +48 -11
  9. package/src/stories/dropdown/Dropdown.module.scss +23 -0
  10. package/src/stories/input/Input.module.scss +134 -0
  11. package/src/stories/input/Input.stories.ts +87 -0
  12. package/src/stories/input/Input.tsx +172 -0
  13. package/src/stories/input/index.ts +1 -0
  14. package/src/stories/select/Select.module.scss +70 -0
  15. package/src/stories/select/Select.stories.ts +71 -0
  16. package/src/stories/select/Select.tsx +103 -0
  17. package/src/stories/select/index.ts +1 -0
  18. package/src/stories/text/Text.module.scss +1 -1
  19. package/src/stories/text/Text.tsx +36 -14
  20. package/src/stories/textarea/TextArea.stories.ts +19 -0
  21. package/src/stories/textarea/TextArea.tsx +120 -0
  22. package/src/stories/textarea/index.ts +1 -0
  23. package/src/stories/theme/global.scss +2 -0
  24. package/src/stories/theme/index.ts +1 -0
  25. package/src/stories/theme/themes.ts +52 -6
  26. package/src/stories/theme/util.scss +8 -0
  27. package/src/types/Enhancer.ts +3 -0
  28. package/.changeset/README.md +0 -8
  29. package/.changeset/config.json +0 -11
  30. package/.eslintrc.json +0 -22
  31. package/.github/workflows/publish.yml +0 -54
  32. package/.husky/pre-commit +0 -2
  33. package/.idea/inspectionProfiles/Project_Default.xml +0 -7
  34. package/.idea/jsLibraryMappings.xml +0 -6
  35. package/.idea/jsLinters/eslint.xml +0 -6
  36. package/.idea/modules.xml +0 -8
  37. package/.idea/paris.iml +0 -13
  38. package/.idea/vcs.xml +0 -6
  39. package/.idea/watcherTasks.xml +0 -4
  40. package/.storybook/main.ts +0 -43
  41. package/.storybook/manager-head.html +0 -16
  42. package/.storybook/manager.ts +0 -6
  43. package/.storybook/preview.ts +0 -74
  44. package/.storybook/themes.ts +0 -30
  45. package/cat +0 -2
  46. package/global.d.ts +0 -2
  47. package/next.config.js +0 -6
  48. package/public/favicon.ico +0 -0
  49. package/public/fira/fira_code.css +0 -48
  50. package/public/fira/woff/FiraCode-Bold.woff +0 -0
  51. package/public/fira/woff/FiraCode-Light.woff +0 -0
  52. package/public/fira/woff/FiraCode-Medium.woff +0 -0
  53. package/public/fira/woff/FiraCode-Regular.woff +0 -0
  54. package/public/fira/woff/FiraCode-SemiBold.woff +0 -0
  55. package/public/fira/woff/FiraCode-VF.woff +0 -0
  56. package/public/fira/woff2/FiraCode-Bold.woff2 +0 -0
  57. package/public/fira/woff2/FiraCode-Light.woff2 +0 -0
  58. package/public/fira/woff2/FiraCode-Medium.woff2 +0 -0
  59. package/public/fira/woff2/FiraCode-Regular.woff2 +0 -0
  60. package/public/fira/woff2/FiraCode-SemiBold.woff2 +0 -0
  61. package/public/fira/woff2/FiraCode-VF.woff2 +0 -0
  62. package/public/graphik/GraphikSS-Black.woff +0 -0
  63. package/public/graphik/GraphikSS-Black.woff2 +0 -0
  64. package/public/graphik/GraphikSS-BlackItalic.woff +0 -0
  65. package/public/graphik/GraphikSS-BlackItalic.woff2 +0 -0
  66. package/public/graphik/GraphikSS-Bold.woff +0 -0
  67. package/public/graphik/GraphikSS-Bold.woff2 +0 -0
  68. package/public/graphik/GraphikSS-BoldItalic.woff +0 -0
  69. package/public/graphik/GraphikSS-BoldItalic.woff2 +0 -0
  70. package/public/graphik/GraphikSS-Extralight.woff +0 -0
  71. package/public/graphik/GraphikSS-Extralight.woff2 +0 -0
  72. package/public/graphik/GraphikSS-ExtralightItalic.woff +0 -0
  73. package/public/graphik/GraphikSS-ExtralightItalic.woff2 +0 -0
  74. package/public/graphik/GraphikSS-Light.woff +0 -0
  75. package/public/graphik/GraphikSS-Light.woff2 +0 -0
  76. package/public/graphik/GraphikSS-LightItalic.woff +0 -0
  77. package/public/graphik/GraphikSS-LightItalic.woff2 +0 -0
  78. package/public/graphik/GraphikSS-Medium.woff +0 -0
  79. package/public/graphik/GraphikSS-Medium.woff2 +0 -0
  80. package/public/graphik/GraphikSS-MediumItalic.woff +0 -0
  81. package/public/graphik/GraphikSS-MediumItalic.woff2 +0 -0
  82. package/public/graphik/GraphikSS-Regular.woff +0 -0
  83. package/public/graphik/GraphikSS-Regular.woff2 +0 -0
  84. package/public/graphik/GraphikSS-RegularItalic.woff +0 -0
  85. package/public/graphik/GraphikSS-RegularItalic.woff2 +0 -0
  86. package/public/graphik/GraphikSS-Semibold.woff +0 -0
  87. package/public/graphik/GraphikSS-Semibold.woff2 +0 -0
  88. package/public/graphik/GraphikSS-SemiboldItalic.woff +0 -0
  89. package/public/graphik/GraphikSS-SemiboldItalic.woff2 +0 -0
  90. package/public/graphik/GraphikSS-Super.woff +0 -0
  91. package/public/graphik/GraphikSS-Super.woff2 +0 -0
  92. package/public/graphik/GraphikSS-SuperItalic.woff +0 -0
  93. package/public/graphik/GraphikSS-SuperItalic.woff2 +0 -0
  94. package/public/graphik/GraphikSS-Thin.woff +0 -0
  95. package/public/graphik/GraphikSS-Thin.woff2 +0 -0
  96. package/public/graphik/GraphikSS-ThinItalic.woff +0 -0
  97. package/public/graphik/GraphikSS-ThinItalic.woff2 +0 -0
  98. package/public/graphik/graphik.css +0 -174
  99. package/public/next.svg +0 -1
  100. package/public/pte.css +0 -219
  101. package/public/thirteen.svg +0 -1
  102. package/public/vercel.svg +0 -1
  103. package/scripts/createComponent.js +0 -100
  104. package/scripts/generateEntry.js +0 -35
  105. package/scripts/text.ts +0 -118
  106. package/src/styles/util.scss +0 -4
  107. package/tsconfig.json +0 -27
  108. /package/src/{styles → stories/theme}/tw-preflight.css +0 -0
@@ -0,0 +1,172 @@
1
+ 'use client';
2
+
3
+ import { useId } from 'react';
4
+ import type { FC, ComponentPropsWithoutRef } from 'react';
5
+ import clsx from 'clsx';
6
+ import styles from './Input.module.scss';
7
+ import type { TextProps } from '../text';
8
+ import { Text } from '../text';
9
+ import type { Enhancer } from '../../types/Enhancer';
10
+ import { pget, theme } from '../theme';
11
+ import { MemoizedEnhancer } from '../../helpers/renderEnhancer';
12
+
13
+ export type InputProps = {
14
+ /**
15
+ * A label for the input field. This is required for accessibility, but can be visually hidden using the `hideLabel` prop.
16
+ */
17
+ label: string;
18
+ /**
19
+ * The status of the input field.
20
+ * @default default
21
+ */
22
+ status?: 'default' | 'error' | 'success';
23
+ /**
24
+ * The input type. All HTML5 input types are supported, but some may require additional props to be set for full accessibility or aesthetic.
25
+ * @default text
26
+ */
27
+ type?: 'button' | 'checkbox' | 'color' | 'date' | 'datetime-local' | 'email' | 'file' | 'hidden' | 'image' | 'month' | 'number' | 'password' | 'radio' | 'range' | 'reset' | 'search' | 'submit' | 'tel' | 'text' | 'time' | 'url' | 'week';
28
+ /**
29
+ * Visually hide the label (while keeping it accessible to screen readers).
30
+ * @default false
31
+ */
32
+ hideLabel?: boolean;
33
+ /**
34
+ * A description of the input field. Can be visually hidden using the `hideDescription` prop.
35
+ */
36
+ description?: string;
37
+ /**
38
+ * Visually hide the description while keeping it accessible to screen readers.
39
+ * @default false
40
+ */
41
+ hideDescription?: boolean;
42
+ /**
43
+ * The placeholder for the input.
44
+ */
45
+ placeholder?: string;
46
+ /**
47
+ * An icon or other element to render before the Input element. A `{ size }` argument is passed that should be used to determine the width & height of the content displayed.
48
+ */
49
+ startEnhancer?: Enhancer;
50
+ /**
51
+ * An icon or other element to render after the Input element. A `{ size }` argument is passed that should be used to determine the width & height of the content displayed.
52
+ */
53
+ endEnhancer?: Enhancer;
54
+ /**
55
+ * Disables the input, disallowing user interaction.
56
+ */
57
+ disabled?: boolean;
58
+ /**
59
+ * Prop overrides for other rendered elements. Overrides for the input itself should be passed directly to the component.
60
+ */
61
+ overrides?: {
62
+ container?: ComponentPropsWithoutRef<'div'>;
63
+ label?: TextProps<'label'>;
64
+ description?: TextProps<'p'>;
65
+ startEnhancerContainer?: ComponentPropsWithoutRef<'div'>;
66
+ endEnhancerContainer?: ComponentPropsWithoutRef<'div'>;
67
+ }
68
+ };
69
+
70
+ /**
71
+ * An `Input` is used to collect user input, such as text, numbers, or dates.
72
+ *
73
+ * > `overrides` available: `container`, `label`, `description`, `startEnhancerContainer`, `endEnhancerContainer`
74
+ *
75
+ * <hr />
76
+ *
77
+ * To use the `Input` component, import it as follows:
78
+ *
79
+ * ```js
80
+ * import { Input } from 'paris/input';
81
+ * ```
82
+ * @constructor
83
+ */
84
+ export const Input: FC<InputProps & ComponentPropsWithoutRef<'input'>> = ({ status, disabled, ...props }) => {
85
+ const inputID = useId();
86
+ return (
87
+ // Disable a11y rules because the container doesn't need to be focusable for screen readers; the input itself should receive focus instead.
88
+ // The container is only made clickable for usability purposes.
89
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
90
+ <div
91
+ {...props.overrides?.container}
92
+ className={clsx(
93
+ props.overrides?.container?.className,
94
+ styles.container,
95
+ )}
96
+ onClick={(e) => {
97
+ if (disabled) e.preventDefault();
98
+ if (typeof window !== 'undefined') {
99
+ const input = document.getElementById(inputID);
100
+ if (input && !disabled) {
101
+ input.focus();
102
+ }
103
+ }
104
+ }}
105
+ >
106
+ <Text
107
+ {...props.overrides?.label}
108
+ as="label"
109
+ kind="paragraphSmall"
110
+ htmlFor={inputID}
111
+ className={clsx(
112
+ styles.label,
113
+ { [styles.hidden]: props.hideLabel },
114
+ )}
115
+ >
116
+ {props.label}
117
+ </Text>
118
+ <div
119
+ className={styles.inputContainer}
120
+ data-status={status}
121
+ data-disabled={disabled}
122
+ >
123
+ {!!props.startEnhancer && (
124
+ <div {...props.overrides?.startEnhancerContainer} className={clsx(styles.enhancer, props.overrides?.startEnhancerContainer?.className)}>
125
+ {!!props.startEnhancer && (
126
+ <MemoizedEnhancer
127
+ enhancer={props.startEnhancer}
128
+ size={parseInt(pget('typography.styles.paragraphSmall.fontSize') || theme.typography.styles.paragraphSmall.fontSize, 10)}
129
+ />
130
+ )}
131
+ </div>
132
+ )}
133
+ <input
134
+ {...props}
135
+ id={inputID}
136
+ type={props.type || 'text'}
137
+ aria-label={props.label}
138
+ aria-describedby={`${inputID}-description`}
139
+ aria-disabled={disabled}
140
+ readOnly={disabled}
141
+ className={clsx(
142
+ props.className,
143
+ styles.input,
144
+ )}
145
+ />
146
+ {!!props.endEnhancer && (
147
+ <div {...props.overrides?.endEnhancerContainer} className={clsx(styles.enhancer, props.overrides?.endEnhancerContainer?.className)}>
148
+ {!!props.endEnhancer && (
149
+ <MemoizedEnhancer
150
+ enhancer={props.endEnhancer}
151
+ size={parseInt(pget('typography.styles.paragraphSmall.fontSize') || theme.typography.styles.paragraphSmall.fontSize, 10)}
152
+ />
153
+ )}
154
+ </div>
155
+ )}
156
+ </div>
157
+ <Text
158
+ id={`${inputID}-description`}
159
+ {...props.overrides?.description}
160
+ as="p"
161
+ kind="paragraphXSmall"
162
+ className={clsx(
163
+ styles.description,
164
+ { [styles.hidden]: !props.description || props.hideDescription },
165
+ props.overrides?.description?.className,
166
+ )}
167
+ >
168
+ {props.description}
169
+ </Text>
170
+ </div>
171
+ );
172
+ };
@@ -0,0 +1 @@
1
+ export * from './Input';
@@ -0,0 +1,70 @@
1
+ .container {
2
+ position: relative;
3
+ user-select: none;
4
+
5
+ & > * {
6
+ cursor: default;
7
+ }
8
+ }
9
+
10
+ .field {
11
+ display: flex;
12
+ flex-direction: row;
13
+ justify-content: space-between;
14
+ align-items: center;
15
+ }
16
+
17
+ .options {
18
+ position: absolute;
19
+ top: 100%;
20
+ left: 0;
21
+ right: 0;
22
+ z-index: 10;
23
+
24
+ display: flex;
25
+ flex-direction: column;
26
+
27
+ background-color: var(--pte-colors-backgroundPrimary);
28
+
29
+ border-width: 1px;
30
+ border-style: solid;
31
+ border-radius: var(--pte-borders-radius-rectangle);
32
+ border-color: var(--pte-borders-dropdown-color);
33
+ box-shadow: var(--pte-borders-dropdown-shadow);
34
+
35
+ transition: var(--pte-animations-interaction);
36
+
37
+ &:focus-within {
38
+ outline: none;
39
+ }
40
+ }
41
+
42
+ .option {
43
+ display: flex;
44
+ flex-direction: row;
45
+ justify-content: flex-start;
46
+ align-items: center;
47
+ gap: 8px;
48
+
49
+ padding: 8px 16px;
50
+ margin: 0 -1px;
51
+
52
+ border-left: 1px solid var(--pte-borders-dropdown-color);
53
+ border-right: 1px solid var(--pte-borders-dropdown-color);
54
+
55
+ transition: var(--pte-animations-interaction);
56
+
57
+ &:hover, &[data-headlessui-state="active"] {
58
+ background-color: var(--pte-colors-backgroundSecondary);
59
+ }
60
+
61
+ &[data-selected=true] {
62
+ background-color: var(--pte-colors-backgroundTertiary);
63
+ }
64
+
65
+ &[data-disabled=true] {
66
+ color: var(--pte-colors-contentDisabled);
67
+ pointer-events: none;
68
+ cursor: default;
69
+ }
70
+ }
@@ -0,0 +1,71 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { createElement, useState } from 'react';
3
+ import { Select } from './Select';
4
+ import { Text } from '../text';
5
+
6
+ const meta: Meta<typeof Select> = {
7
+ title: 'Inputs/Select',
8
+ component: Select,
9
+ tags: ['autodocs'],
10
+ };
11
+
12
+ export default meta;
13
+ type Story = StoryObj<typeof Select>;
14
+
15
+ const render: Story['render'] = (args) => {
16
+ // eslint-disable-next-line react-hooks/rules-of-hooks
17
+ const [selected, setSelected] = useState<string | null>(null);
18
+ return createElement('div', {
19
+ style: { minHeight: '200px' },
20
+ }, createElement(Select, {
21
+ ...args,
22
+ value: selected,
23
+ onChange: (e) => setSelected(e),
24
+ }));
25
+ };
26
+
27
+ export const Default: Story = {
28
+ args: {
29
+ options: [
30
+ { id: '1', node: 'Option 1' },
31
+ { id: '2', node: 'Option 2' },
32
+ { id: '3', node: 'Option 3' },
33
+ ],
34
+ },
35
+ render,
36
+ };
37
+
38
+ export const WithCustomNodes: Story = {
39
+ args: {
40
+ options: [
41
+ {
42
+ id: '1',
43
+ // eslint-disable-next-line react/no-children-prop
44
+ node: createElement(Text, {
45
+ as: 'span',
46
+ kind: 'displaySmall',
47
+ children: 'Option 1',
48
+ }),
49
+ },
50
+ {
51
+ id: '2',
52
+ // eslint-disable-next-line react/no-children-prop
53
+ node: createElement(Text, {
54
+ as: 'span',
55
+ kind: 'paragraphXXSmall',
56
+ children: 'Option 2',
57
+ }),
58
+ },
59
+ {
60
+ id: '3',
61
+ // eslint-disable-next-line react/no-children-prop
62
+ node: createElement(Text, {
63
+ as: 'span',
64
+ kind: 'labelXLarge',
65
+ children: 'Option 3',
66
+ }),
67
+ },
68
+ ],
69
+ },
70
+ render,
71
+ };
@@ -0,0 +1,103 @@
1
+ 'use client';
2
+
3
+ import type { FC, ReactNode } from 'react';
4
+ import { Listbox, Transition } from '@headlessui/react';
5
+ import clsx from 'clsx';
6
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
7
+ import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
8
+ import inputStyles from '../input/Input.module.scss';
9
+ import dropdownStyles from '../dropdown/Dropdown.module.scss';
10
+ import styles from './Select.module.scss';
11
+ import { Text } from '../text';
12
+
13
+ export type Option<T = Record<string, any>> = {
14
+ id: string,
15
+ node: ReactNode,
16
+ metadata?: T,
17
+ };
18
+ export type SelectProps<T = Record<string, any>> = {
19
+ /**
20
+ * The {@link Option}s to render in the select box.
21
+ *
22
+ * Each option should have an id (`string`) and node ({@link ReactNode}) property at minimum. You can also pass in any other metadata through the `metadata` attribute.
23
+ *
24
+ * For type safety, you can pass in a type parameter to `SelectProps` component. This will be used as the type for the `metadata` property of each option.
25
+ */
26
+ options: Option<T>[];
27
+ /**
28
+ * The option ID to render as selected in the select box.
29
+ *
30
+ * This should exactly match one of the option IDs passed in the `options` prop. If `null`, no option will be selected.
31
+ */
32
+ value?: string | null;
33
+ /**
34
+ * The interaction handler for the Select.
35
+ */
36
+ onChange?: (value: string | null) => void | Promise<void>;
37
+ };
38
+
39
+ /**
40
+ * A Select component is used to render a `select` box.
41
+ *
42
+ * <hr />
43
+ *
44
+ * To use this component, import it as follows:
45
+ *
46
+ * ```js
47
+ * import { Select } from 'paris/select';
48
+ * ```
49
+ * @constructor
50
+ */
51
+ export function Select<T = Record<string, any>>({
52
+ options,
53
+ value,
54
+ onChange,
55
+ }: SelectProps<T>) {
56
+ return (
57
+ <div className={styles.container}>
58
+ <Listbox
59
+ value={value}
60
+ onChange={onChange}
61
+ >
62
+ <Listbox.Button
63
+ className={clsx(
64
+ inputStyles.inputContainer,
65
+ styles.field,
66
+ )}
67
+ >
68
+ {options?.find((o) => o.id === value)?.node || 'Select an option'}
69
+ <FontAwesomeIcon className={inputStyles.enhancer} width="10px" icon={faChevronDown} />
70
+ </Listbox.Button>
71
+ <Transition
72
+ enter={dropdownStyles.transition}
73
+ enterFrom={dropdownStyles.enterFrom}
74
+ enterTo={dropdownStyles.enterTo}
75
+ leave={dropdownStyles.transition}
76
+ leaveFrom={dropdownStyles.leaveFrom}
77
+ leaveTo={dropdownStyles.leaveTo}
78
+ >
79
+ <Listbox.Options
80
+ className={clsx(
81
+ styles.options,
82
+ )}
83
+ >
84
+ {(options || []).map((option) => (
85
+ <Listbox.Option
86
+ key={option.id}
87
+ value={option.id}
88
+ data-selected={option.id === value}
89
+ className={clsx(
90
+ styles.option,
91
+ )}
92
+ >
93
+ {typeof option.node === 'string' ? (
94
+ <Text as="span" kind="paragraphSmall">{option.node}</Text>
95
+ ) : option.node}
96
+ </Listbox.Option>
97
+ ))}
98
+ </Listbox.Options>
99
+ </Transition>
100
+ </Listbox>
101
+ </div>
102
+ );
103
+ }
@@ -0,0 +1 @@
1
+ export * from './Select';
@@ -1,3 +1,3 @@
1
1
  .text {
2
- //color: var(--pte-colors-contentPrimary);
2
+ font-family: var(--pte-typography-fontFamily);
3
3
  }
@@ -1,40 +1,62 @@
1
- import type { FC, HTMLProps, ReactNode } from 'react';
1
+ import type { ComponentProps, FC, ReactNode } from 'react';
2
2
  import { createElement } from 'react';
3
+ import clsx from 'clsx';
4
+ import styles from './Text.module.scss';
3
5
  import typography from './Typography.module.css';
4
6
  import type { LightTheme } from '../theme';
5
7
 
6
- export type TextProps = {
8
+ export type TextElement = 'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' | 'legend' | 'caption' | 'small';
9
+ export type TextProps<T extends TextElement = 'span'> = {
7
10
  /**
8
11
  * The font class to use.
9
12
  * @default paragraphMedium
10
13
  */
11
- kind: keyof typeof LightTheme.typography.styles;
14
+ kind?: keyof typeof LightTheme.typography.styles;
12
15
  /**
13
16
  * The HTML text tag to use.
14
17
  * @default span
15
18
  */
16
- as?: 'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' | 'legend' | 'caption' | 'small';
19
+ as?: T;
17
20
  /** The contents of the Text element. */
18
21
  children: ReactNode;
19
- };
22
+ } & ComponentProps<T>;
20
23
 
21
24
  /**
22
25
  * A `Text` component is used to render text with one of our theme formats.
26
+ *
27
+ * <hr />
28
+ *
29
+ * To use the `Text` component, import it as follows:
30
+ *
31
+ * ```tsx
32
+ * import { Text } from 'paris/text';
33
+ *
34
+ * export const ExampleHeading: FC = () => (
35
+ * <Text as="h1" format="headingLarge">Hello World!</Text>
36
+ * );
37
+ * ```
38
+ *
23
39
  * @example ```tsx
24
40
  * <Text as="h1" format="headingLarge">Hello World!</Text>
25
41
  * ```
26
42
  * @constructor
27
43
  */
28
- export const Text: FC<TextProps & HTMLProps<HTMLParagraphElement>> = ({
44
+ export function Text<T extends TextElement>({
29
45
  kind,
30
46
  as,
31
47
  children,
32
48
  ...props
33
- }) => createElement(
34
- as || 'span',
35
- {
36
- ...props,
37
- className: `${props.className || ''} ${typography[kind]}`,
38
- },
39
- children,
40
- );
49
+ }: TextProps<T>): JSX.Element {
50
+ return createElement(
51
+ as || 'span',
52
+ {
53
+ ...props,
54
+ className: clsx(
55
+ styles.text,
56
+ typography[kind || 'paragraphMedium'],
57
+ props?.className,
58
+ ),
59
+ },
60
+ children,
61
+ );
62
+ }
@@ -0,0 +1,19 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { TextArea } from './TextArea';
3
+
4
+ const meta: Meta<typeof TextArea> = {
5
+ title: 'Inputs/TextArea',
6
+ component: TextArea,
7
+ tags: ['autodocs'],
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof TextArea>;
12
+
13
+ export const Default: Story = {
14
+ args: {
15
+ placeholder: 'Billie Eilish',
16
+ label: 'Name',
17
+ description: 'Type your full name here.',
18
+ },
19
+ };
@@ -0,0 +1,120 @@
1
+ import type { ComponentPropsWithoutRef, FC } from 'react';
2
+ import { useId } from 'react';
3
+ import clsx from 'clsx';
4
+ import type { InputProps } from '../input';
5
+ import styles from '../input/Input.module.scss';
6
+ import { Text } from '../text';
7
+ import { MemoizedEnhancer } from '../../helpers/renderEnhancer';
8
+ import { pget, theme } from '../theme';
9
+
10
+ /**
11
+ * A `textarea` input field.
12
+ *
13
+ * <hr />
14
+ *
15
+ * To use this component in your app, import it as follows:
16
+ *
17
+ * ```js
18
+ * import { TextArea } from 'paris/textarea';
19
+ * ```
20
+ * @constructor
21
+ */
22
+ export const TextArea: FC<InputProps & ComponentPropsWithoutRef<'textarea'>> = ({ status, disabled, ...props }) => {
23
+ const inputID = useId();
24
+ return (
25
+ // Disable a11y rules because the container doesn't need to be focusable for screen readers; the input itself should receive focus instead.
26
+ // The container is only made clickable for usability purposes.
27
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
28
+ <div
29
+ {...props.overrides?.container}
30
+ className={clsx(
31
+ props.overrides?.container?.className,
32
+ styles.container,
33
+ )}
34
+ onClick={(e) => {
35
+ if (disabled) e.preventDefault();
36
+ if (typeof window !== 'undefined') {
37
+ const input = document.getElementById(inputID);
38
+ if (input && !disabled) {
39
+ input.focus();
40
+ }
41
+ }
42
+ }}
43
+ >
44
+ <Text
45
+ {...props.overrides?.label}
46
+ as="label"
47
+ kind="paragraphSmall"
48
+ htmlFor={inputID}
49
+ className={clsx(
50
+ styles.label,
51
+ { [styles.hidden]: props.hideLabel },
52
+ )}
53
+ >
54
+ {props.label}
55
+ </Text>
56
+ <div
57
+ className={styles.inputContainer}
58
+ data-status={status}
59
+ data-disabled={disabled}
60
+ >
61
+ {!!props.startEnhancer && (
62
+ <div {...props.overrides?.startEnhancerContainer} className={clsx(styles.enhancer, props.overrides?.startEnhancerContainer?.className)}>
63
+ {!!props.startEnhancer && (
64
+ <MemoizedEnhancer
65
+ enhancer={props.startEnhancer}
66
+ size={parseInt(pget('typography.styles.paragraphSmall.fontSize') || theme.typography.styles.paragraphSmall.fontSize, 10)}
67
+ />
68
+ )}
69
+ </div>
70
+ )}
71
+ <textarea
72
+ {...props}
73
+ id={inputID}
74
+ aria-label={props.label}
75
+ aria-describedby={`${inputID}-description`}
76
+ aria-disabled={disabled}
77
+ readOnly={disabled}
78
+ className={clsx(
79
+ props.className,
80
+ styles.input,
81
+ )}
82
+ />
83
+ {!!props.endEnhancer && (
84
+ <div {...props.overrides?.endEnhancerContainer} className={clsx(styles.enhancer, props.overrides?.endEnhancerContainer?.className)}>
85
+ {!!props.endEnhancer && (
86
+ <MemoizedEnhancer
87
+ enhancer={props.endEnhancer}
88
+ size={parseInt(pget('typography.styles.paragraphSmall.fontSize') || theme.typography.styles.paragraphSmall.fontSize, 10)}
89
+ />
90
+ )}
91
+ </div>
92
+ )}
93
+ </div>
94
+ <Text
95
+ id={`${inputID}-description`}
96
+ {...props.overrides?.description}
97
+ as="p"
98
+ kind="paragraphXSmall"
99
+ className={clsx(
100
+ styles.description,
101
+ { [styles.hidden]: !props.description || props.hideDescription },
102
+ props.overrides?.description?.className,
103
+ )}
104
+ >
105
+ {props.description}
106
+ </Text>
107
+ </div>
108
+ );
109
+ // return (
110
+ // <div
111
+ // className={clsx(
112
+ // styles.inputContainer,
113
+ // )}
114
+ // >
115
+ // <textarea
116
+ // {...props}
117
+ // />
118
+ // </div>
119
+ // );
120
+ };
@@ -0,0 +1 @@
1
+ export * from './TextArea';
@@ -0,0 +1,2 @@
1
+ @use 'tw-preflight.css';
2
+ @use 'util.scss';
@@ -1,2 +1,3 @@
1
1
  export * from './themes';
2
2
  export * from './tokens';
3
+ export { generateCSS } from 'pte';