@indico-data/design-system 2.8.0 → 2.9.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.
package/lib/index.css CHANGED
@@ -870,6 +870,10 @@
870
870
  --pf-input-text-color: var(--pf-gray-color);
871
871
  --pf-input-placeholder-text-color: var(--pf-gray-color-300);
872
872
  --pf-input-help-text-color: var(--pf-gray-color-400);
873
+ --pf-input-disabled-background-color: var(--pf-gray-color-100);
874
+ --pf-input-border-color: var(--pf-gray-color);
875
+ --pf-input-disabled-color: var(--pf-gray-color-400);
876
+ --pf-input-border-color: var(--pf-gray-color);
873
877
  --pf-input-rounded: var(--pf-rounded);
874
878
  }
875
879
 
@@ -879,6 +883,9 @@
879
883
  --pf-input-text-color: var(--pf-gray-color-100);
880
884
  --pf-input-placeholder-text-color: var(--pf-gray-color);
881
885
  --pf-input-help-text-color: var(--pf-gray-color-200);
886
+ --pf-input-disabled-background-color: var(--pf-primary-color-200);
887
+ --pf-input-disabled-border-color: var(--pf-gray-color-300);
888
+ --pf-input-disabled-color: var(--pf-gray-color-400);
882
889
  }
883
890
 
884
891
  .input {
@@ -913,9 +920,9 @@
913
920
  border-color: var(--pf-info-color);
914
921
  }
915
922
  .input:disabled {
916
- background-color: var(--pf-gray-color-100);
917
- border-color: var(--pf-gray-color-300);
918
- color: var(--pf-gray-color-400);
923
+ background-color: var(--pf-input-disabled-background-color);
924
+ border-color: var(--pf-input-disabled-border-color);
925
+ color: var(--pf-input-disabled-color);
919
926
  }
920
927
  .input--has-icon {
921
928
  padding-left: var(--pf-padding-7);
@@ -1064,7 +1071,7 @@
1064
1071
  }
1065
1072
 
1066
1073
  :root [data-theme=dark] {
1067
- --pf-checkbox-background-color: transparent;
1074
+ --pf-checkbox-background-color: var(--pf-primary-color-200);
1068
1075
  --pf-checkbox-check-color: var(--pf-white-color);
1069
1076
  --pf-checkbox-border-color: var(--pf-white-color);
1070
1077
  --pf-checkbox-disabled-color: var(--pf-gray-color-300);
@@ -1143,6 +1150,99 @@
1143
1150
  background: var(--pf-checkbox-disabled-color);
1144
1151
  }
1145
1152
 
1153
+ :root,
1154
+ :root [data-theme=light],
1155
+ :root [data-theme=dark] {
1156
+ --pf-textarea-background-color: var(--pf-white-color);
1157
+ --pf-textarea-border-color: var(--pf-gray-color);
1158
+ --pf-textarea-text-color: var(--pf-gray-color);
1159
+ --pf-textarea-placeholder-text-color: var(--pf-gray-color-300);
1160
+ --pf-textarea-help-text-color: var(--pf-gray-color-400);
1161
+ --pf-textarea-disabled-background-color: var(--pf-gray-color-100);
1162
+ --pf-textarea-border-color: var(--pf-gray-color);
1163
+ --pf-textarea-disabled-color: var(--pf-gray-color-400);
1164
+ --pf-textarea-rounded: var(--pf-rounded);
1165
+ }
1166
+
1167
+ :root [data-theme=dark] {
1168
+ --pf-textarea-background-color: var(--pf-primary-color);
1169
+ --pf-textarea-border-color: var(--pf-gray-color-100);
1170
+ --pf-textarea-text-color: var(--pf-gray-color-100);
1171
+ --pf-textarea-placeholder-text-color: var(--pf-gray-color);
1172
+ --pf-textarea-help-text-color: var(--pf-gray-color-200);
1173
+ --pf-textarea-disabled-background-color: var(--pf-primary-color-200);
1174
+ --pf-textarea-disabled-border-color: var(--pf-gray-color-300);
1175
+ --pf-textarea-disabled-color: var(--pf-gray-color-400);
1176
+ }
1177
+
1178
+ .textarea {
1179
+ background-color: var(--pf-textarea-background-color);
1180
+ border: 1px solid var(--pf-textarea-border-color);
1181
+ border-radius: var(--pf-textarea-rounded);
1182
+ color: var(--pf-textarea-text-color);
1183
+ padding: 10px;
1184
+ width: 100%;
1185
+ box-sizing: border-box;
1186
+ }
1187
+ .textarea::-moz-placeholder {
1188
+ color: var(--pf-textarea-placeholder-text-color);
1189
+ }
1190
+ .textarea::placeholder {
1191
+ color: var(--pf-textarea-placeholder-text-color);
1192
+ }
1193
+ .textarea:focus {
1194
+ border-color: var(--pf-primary-color);
1195
+ }
1196
+ .textarea.error {
1197
+ border-color: var(--pf-error-color);
1198
+ }
1199
+ .textarea.success {
1200
+ border-color: var(--pf-success-color);
1201
+ }
1202
+ .textarea.warning {
1203
+ border-color: var(--pf-warning-color);
1204
+ }
1205
+ .textarea.info {
1206
+ border-color: var(--pf-info-color);
1207
+ }
1208
+ .textarea:disabled {
1209
+ background-color: var(--pf-textarea-disabled-background-color);
1210
+ border-color: var(--pf-textarea-disabled-border-color);
1211
+ color: var(--pf-textarea-disabled-color);
1212
+ }
1213
+ .textarea--has-icon {
1214
+ padding-left: var(--pf-padding-7);
1215
+ }
1216
+
1217
+ .form-control .error-list {
1218
+ list-style: none;
1219
+ padding: 0;
1220
+ margin: 0;
1221
+ margin-top: var(--pf-margin-2);
1222
+ margin-bottom: var(--pf-margin-2);
1223
+ color: var(--pf-error-color);
1224
+ }
1225
+ .form-control .help-text {
1226
+ margin-top: var(--pf-margin-2);
1227
+ margin-bottom: var(--pf-margin-2);
1228
+ color: var(--pf-textarea-help-text-color);
1229
+ font-size: var(--pf-font-size-subtitle2);
1230
+ }
1231
+ .form-control .is-visually-hidden {
1232
+ position: absolute;
1233
+ width: 1px;
1234
+ height: 1px;
1235
+ padding: 0;
1236
+ margin: -1px;
1237
+ overflow: hidden;
1238
+ clip: rect(0, 0, 0, 0);
1239
+ white-space: nowrap;
1240
+ border: 0;
1241
+ }
1242
+ .form-control .form-label {
1243
+ margin-bottom: var(--pf-margin-2);
1244
+ }
1245
+
1146
1246
  :root,
1147
1247
  :root [data-theme=light],
1148
1248
  :root [data-theme=dark] {
package/lib/index.esm.css CHANGED
@@ -870,6 +870,10 @@
870
870
  --pf-input-text-color: var(--pf-gray-color);
871
871
  --pf-input-placeholder-text-color: var(--pf-gray-color-300);
872
872
  --pf-input-help-text-color: var(--pf-gray-color-400);
873
+ --pf-input-disabled-background-color: var(--pf-gray-color-100);
874
+ --pf-input-border-color: var(--pf-gray-color);
875
+ --pf-input-disabled-color: var(--pf-gray-color-400);
876
+ --pf-input-border-color: var(--pf-gray-color);
873
877
  --pf-input-rounded: var(--pf-rounded);
874
878
  }
875
879
 
@@ -879,6 +883,9 @@
879
883
  --pf-input-text-color: var(--pf-gray-color-100);
880
884
  --pf-input-placeholder-text-color: var(--pf-gray-color);
881
885
  --pf-input-help-text-color: var(--pf-gray-color-200);
886
+ --pf-input-disabled-background-color: var(--pf-primary-color-200);
887
+ --pf-input-disabled-border-color: var(--pf-gray-color-300);
888
+ --pf-input-disabled-color: var(--pf-gray-color-400);
882
889
  }
883
890
 
884
891
  .input {
@@ -913,9 +920,9 @@
913
920
  border-color: var(--pf-info-color);
914
921
  }
915
922
  .input:disabled {
916
- background-color: var(--pf-gray-color-100);
917
- border-color: var(--pf-gray-color-300);
918
- color: var(--pf-gray-color-400);
923
+ background-color: var(--pf-input-disabled-background-color);
924
+ border-color: var(--pf-input-disabled-border-color);
925
+ color: var(--pf-input-disabled-color);
919
926
  }
920
927
  .input--has-icon {
921
928
  padding-left: var(--pf-padding-7);
@@ -1064,7 +1071,7 @@
1064
1071
  }
1065
1072
 
1066
1073
  :root [data-theme=dark] {
1067
- --pf-checkbox-background-color: transparent;
1074
+ --pf-checkbox-background-color: var(--pf-primary-color-200);
1068
1075
  --pf-checkbox-check-color: var(--pf-white-color);
1069
1076
  --pf-checkbox-border-color: var(--pf-white-color);
1070
1077
  --pf-checkbox-disabled-color: var(--pf-gray-color-300);
@@ -1143,6 +1150,99 @@
1143
1150
  background: var(--pf-checkbox-disabled-color);
1144
1151
  }
1145
1152
 
1153
+ :root,
1154
+ :root [data-theme=light],
1155
+ :root [data-theme=dark] {
1156
+ --pf-textarea-background-color: var(--pf-white-color);
1157
+ --pf-textarea-border-color: var(--pf-gray-color);
1158
+ --pf-textarea-text-color: var(--pf-gray-color);
1159
+ --pf-textarea-placeholder-text-color: var(--pf-gray-color-300);
1160
+ --pf-textarea-help-text-color: var(--pf-gray-color-400);
1161
+ --pf-textarea-disabled-background-color: var(--pf-gray-color-100);
1162
+ --pf-textarea-border-color: var(--pf-gray-color);
1163
+ --pf-textarea-disabled-color: var(--pf-gray-color-400);
1164
+ --pf-textarea-rounded: var(--pf-rounded);
1165
+ }
1166
+
1167
+ :root [data-theme=dark] {
1168
+ --pf-textarea-background-color: var(--pf-primary-color);
1169
+ --pf-textarea-border-color: var(--pf-gray-color-100);
1170
+ --pf-textarea-text-color: var(--pf-gray-color-100);
1171
+ --pf-textarea-placeholder-text-color: var(--pf-gray-color);
1172
+ --pf-textarea-help-text-color: var(--pf-gray-color-200);
1173
+ --pf-textarea-disabled-background-color: var(--pf-primary-color-200);
1174
+ --pf-textarea-disabled-border-color: var(--pf-gray-color-300);
1175
+ --pf-textarea-disabled-color: var(--pf-gray-color-400);
1176
+ }
1177
+
1178
+ .textarea {
1179
+ background-color: var(--pf-textarea-background-color);
1180
+ border: 1px solid var(--pf-textarea-border-color);
1181
+ border-radius: var(--pf-textarea-rounded);
1182
+ color: var(--pf-textarea-text-color);
1183
+ padding: 10px;
1184
+ width: 100%;
1185
+ box-sizing: border-box;
1186
+ }
1187
+ .textarea::-moz-placeholder {
1188
+ color: var(--pf-textarea-placeholder-text-color);
1189
+ }
1190
+ .textarea::placeholder {
1191
+ color: var(--pf-textarea-placeholder-text-color);
1192
+ }
1193
+ .textarea:focus {
1194
+ border-color: var(--pf-primary-color);
1195
+ }
1196
+ .textarea.error {
1197
+ border-color: var(--pf-error-color);
1198
+ }
1199
+ .textarea.success {
1200
+ border-color: var(--pf-success-color);
1201
+ }
1202
+ .textarea.warning {
1203
+ border-color: var(--pf-warning-color);
1204
+ }
1205
+ .textarea.info {
1206
+ border-color: var(--pf-info-color);
1207
+ }
1208
+ .textarea:disabled {
1209
+ background-color: var(--pf-textarea-disabled-background-color);
1210
+ border-color: var(--pf-textarea-disabled-border-color);
1211
+ color: var(--pf-textarea-disabled-color);
1212
+ }
1213
+ .textarea--has-icon {
1214
+ padding-left: var(--pf-padding-7);
1215
+ }
1216
+
1217
+ .form-control .error-list {
1218
+ list-style: none;
1219
+ padding: 0;
1220
+ margin: 0;
1221
+ margin-top: var(--pf-margin-2);
1222
+ margin-bottom: var(--pf-margin-2);
1223
+ color: var(--pf-error-color);
1224
+ }
1225
+ .form-control .help-text {
1226
+ margin-top: var(--pf-margin-2);
1227
+ margin-bottom: var(--pf-margin-2);
1228
+ color: var(--pf-textarea-help-text-color);
1229
+ font-size: var(--pf-font-size-subtitle2);
1230
+ }
1231
+ .form-control .is-visually-hidden {
1232
+ position: absolute;
1233
+ width: 1px;
1234
+ height: 1px;
1235
+ padding: 0;
1236
+ margin: -1px;
1237
+ overflow: hidden;
1238
+ clip: rect(0, 0, 0, 0);
1239
+ white-space: nowrap;
1240
+ border: 0;
1241
+ }
1242
+ .form-control .form-label {
1243
+ margin-bottom: var(--pf-margin-2);
1244
+ }
1245
+
1146
1246
  :root,
1147
1247
  :root [data-theme=light],
1148
1248
  :root [data-theme=dark] {
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ export interface TextareaProps {
3
+ ref?: React.LegacyRef<HTMLTextAreaElement>;
4
+ label: string;
5
+ name: string;
6
+ placeholder: string;
7
+ value: string;
8
+ onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
9
+ isRequired?: boolean;
10
+ isDisabled?: boolean;
11
+ errorList?: string[];
12
+ helpText?: string;
13
+ hasHiddenLabel?: boolean;
14
+ rows?: number;
15
+ cols?: number;
16
+ readonly?: boolean;
17
+ wrap?: 'hard' | 'soft';
18
+ form?: string;
19
+ maxLength?: number;
20
+ autofocus?: boolean;
21
+ }
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;
@@ -0,0 +1,11 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { Textarea } from './Textarea';
3
+ declare const meta: Meta;
4
+ export default meta;
5
+ type Story = StoryObj<typeof Textarea>;
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 Disabled: Story;
11
+ export declare const Readonly: Story;
@@ -0,0 +1 @@
1
+ export { Textarea } from './Textarea';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indico-data/design-system",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "main": "lib/index.js",
@@ -10,7 +10,7 @@
10
10
 
11
11
  // Dark Theme Specific Variables
12
12
  :root [data-theme='dark'] {
13
- --pf-checkbox-background-color: transparent;
13
+ --pf-checkbox-background-color: var(--pf-primary-color-200);
14
14
  --pf-checkbox-check-color: var(--pf-white-color);
15
15
  --pf-checkbox-border-color: var(--pf-white-color);
16
16
  --pf-checkbox-disabled-color: var(--pf-gray-color-300);
@@ -1,6 +1,6 @@
1
1
  import { Meta, StoryObj } from '@storybook/react';
2
2
  import { Input, InputProps } from './Input';
3
- import { useState } from 'react';
3
+ import { SetStateAction, useEffect, useState } from 'react';
4
4
  import { iconNames } from 'build/generated/iconTypes';
5
5
 
6
6
  const meta: Meta = {
@@ -174,15 +174,15 @@ export const Default: Story = {
174
174
  },
175
175
  render: (args) => {
176
176
  const [value, setValue] = useState('');
177
- return (
178
- <Input
179
- {...args}
180
- value={value}
181
- onChange={(e) => {
182
- setValue(e.target.value);
183
- }}
184
- />
185
- );
177
+
178
+ useEffect(() => {
179
+ setValue(args.value);
180
+ }, [args.value]);
181
+
182
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
183
+ setValue(e.target.value);
184
+ };
185
+ return <Input {...args} value={value} onChange={handleChange} />;
186
186
  },
187
187
  };
188
188
 
@@ -193,15 +193,16 @@ export const Errors: Story = {
193
193
  },
194
194
  render: (args) => {
195
195
  const [value, setValue] = useState('');
196
- return (
197
- <Input
198
- {...args}
199
- value={value}
200
- onChange={(e) => {
201
- setValue(e.target.value);
202
- }}
203
- />
204
- );
196
+
197
+ useEffect(() => {
198
+ setValue(args.value);
199
+ }, [args.value]);
200
+
201
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
202
+ setValue(e.target.value);
203
+ };
204
+
205
+ return <Input {...args} value={value} onChange={handleChange} />;
205
206
  },
206
207
  };
207
208
 
@@ -212,15 +213,16 @@ export const HiddenLabel: Story = {
212
213
  },
213
214
  render: (args) => {
214
215
  const [value, setValue] = useState('');
215
- return (
216
- <Input
217
- {...args}
218
- value={value}
219
- onChange={(e) => {
220
- setValue(e.target.value);
221
- }}
222
- />
223
- );
216
+
217
+ useEffect(() => {
218
+ setValue(args.value);
219
+ }, [args.value]);
220
+
221
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
222
+ setValue(e.target.value);
223
+ };
224
+
225
+ return <Input {...args} value={value} onChange={handleChange} />;
224
226
  },
225
227
  };
226
228
 
@@ -231,15 +233,16 @@ export const HelpText: Story = {
231
233
  },
232
234
  render: (args) => {
233
235
  const [value, setValue] = useState('');
234
- return (
235
- <Input
236
- {...args}
237
- value={value}
238
- onChange={(e) => {
239
- setValue(e.target.value);
240
- }}
241
- />
242
- );
236
+
237
+ useEffect(() => {
238
+ setValue(args.value);
239
+ }, [args.value]);
240
+
241
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
242
+ setValue(e.target.value);
243
+ };
244
+
245
+ return <Input {...args} value={value} onChange={handleChange} />;
243
246
  },
244
247
  };
245
248
 
@@ -250,15 +253,16 @@ export const Clearable: Story = {
250
253
  },
251
254
  render: (args) => {
252
255
  const [value, setValue] = useState('');
253
- return (
254
- <Input
255
- {...args}
256
- value={value}
257
- onChange={(e) => {
258
- setValue(e.target.value);
259
- }}
260
- />
261
- );
256
+
257
+ useEffect(() => {
258
+ setValue(args.value);
259
+ }, [args.value]);
260
+
261
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
262
+ setValue(e.target.value);
263
+ };
264
+
265
+ return <Input {...args} value={value} onChange={handleChange} />;
262
266
  },
263
267
  };
264
268
 
@@ -269,15 +273,16 @@ export const Icon: Story = {
269
273
  },
270
274
  render: (args) => {
271
275
  const [value, setValue] = useState('');
272
- return (
273
- <Input
274
- {...args}
275
- value={value}
276
- onChange={(e) => {
277
- setValue(e.target.value);
278
- }}
279
- />
280
- );
276
+
277
+ useEffect(() => {
278
+ setValue(args.value);
279
+ }, [args.value]);
280
+
281
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
282
+ setValue(e.target.value);
283
+ };
284
+
285
+ return <Input {...args} value={value} onChange={handleChange} />;
281
286
  },
282
287
  };
283
288
 
@@ -288,14 +293,15 @@ export const Required: Story = {
288
293
  },
289
294
  render: (args) => {
290
295
  const [value, setValue] = useState('');
291
- return (
292
- <Input
293
- {...args}
294
- value={value}
295
- onChange={(e) => {
296
- setValue(e.target.value);
297
- }}
298
- />
299
- );
296
+
297
+ useEffect(() => {
298
+ setValue(args.value);
299
+ }, [args.value]);
300
+
301
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
302
+ setValue(e.target.value);
303
+ };
304
+
305
+ return <Input {...args} value={value} onChange={handleChange} />;
300
306
  },
301
307
  };
@@ -8,6 +8,10 @@
8
8
  --pf-input-text-color: var(--pf-gray-color);
9
9
  --pf-input-placeholder-text-color: var(--pf-gray-color-300);
10
10
  --pf-input-help-text-color: var(--pf-gray-color-400);
11
+ --pf-input-disabled-background-color: var(--pf-gray-color-100);
12
+ --pf-input-border-color: var(--pf-gray-color);
13
+ --pf-input-disabled-color: var(--pf-gray-color-400);
14
+ --pf-input-border-color: var(--pf-gray-color);
11
15
 
12
16
  // input Radius
13
17
  --pf-input-rounded: var(--pf-rounded);
@@ -20,6 +24,9 @@
20
24
  --pf-input-text-color: var(--pf-gray-color-100);
21
25
  --pf-input-placeholder-text-color: var(--pf-gray-color);
22
26
  --pf-input-help-text-color: var(--pf-gray-color-200);
27
+ --pf-input-disabled-background-color: var(--pf-primary-color-200);
28
+ --pf-input-disabled-border-color: var(--pf-gray-color-300);
29
+ --pf-input-disabled-color: var(--pf-gray-color-400);
23
30
  }
24
31
 
25
32
  .input {
@@ -56,9 +63,9 @@
56
63
  }
57
64
 
58
65
  &:disabled {
59
- background-color: var(--pf-gray-color-100);
60
- border-color: var(--pf-gray-color-300);
61
- color: var(--pf-gray-color-400);
66
+ background-color: var(--pf-input-disabled-background-color);
67
+ border-color: var(--pf-input-disabled-border-color);
68
+ color: var(--pf-input-disabled-color);
62
69
  }
63
70
  &--has-icon {
64
71
  padding-left: var(--pf-padding-7);
@@ -0,0 +1,19 @@
1
+ import { Canvas, Meta, Controls } from '@storybook/blocks';
2
+ import * as Textarea from './Textarea.stories';
3
+
4
+ <Meta title="Forms/Textarea" name="Textarea" />
5
+
6
+ # Textarea
7
+
8
+ The Textarea component is the building block of any form. Below you will find the accepted properties for this component. It is encouraged to build forms utilizing [React Hook Form](https://react-hook-form.com/) library in your application. This will facilitate form state management and enforce best practices. (***Our components are compatible with but do not provide the plugin***)
9
+
10
+ <Canvas
11
+ of={Textarea.Default}
12
+ source={{
13
+ code: `
14
+ <Textarea name="first_name" isRequired helpText="This Is Help Text" placeholder="This is a placeholder" value="''" onChange={handleChange} label="Label Name" />
15
+ `,
16
+ }}
17
+ />
18
+
19
+ <Controls of={Textarea.Default} />
@@ -0,0 +1,363 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { Textarea, TextareaProps } from './Textarea';
3
+ import { SetStateAction, useEffect, useState } from 'react';
4
+
5
+ const meta: Meta = {
6
+ title: 'Forms/Textarea',
7
+ component: Textarea,
8
+ argTypes: {
9
+ onChange: {
10
+ control: false,
11
+ description: 'onChange event handler',
12
+ table: {
13
+ category: 'Callbacks',
14
+ type: {
15
+ summary: '(e: React.ChangeEvent<HTMLInputElement>) => void',
16
+ },
17
+ },
18
+ action: 'onChange',
19
+ },
20
+ label: {
21
+ control: 'text',
22
+ description: 'The label for the textarea field',
23
+ table: {
24
+ category: 'Props',
25
+ type: {
26
+ summary: 'string',
27
+ },
28
+ },
29
+ defaultValue: { summary: '' },
30
+ },
31
+ name: {
32
+ control: 'text',
33
+ description: 'The name for the textarea field',
34
+ table: {
35
+ category: 'Props',
36
+ type: {
37
+ summary: 'string',
38
+ },
39
+ },
40
+ defaultValue: { summary: '' },
41
+ },
42
+ placeholder: {
43
+ control: 'text',
44
+ description: 'The placeholder for the textarea field',
45
+ table: {
46
+ category: 'Props',
47
+ type: {
48
+ summary: 'string',
49
+ },
50
+ },
51
+ defaultValue: { summary: '' },
52
+ },
53
+ value: {
54
+ control: 'text',
55
+ description: 'The value for the textarea field',
56
+ table: {
57
+ category: 'Props',
58
+ type: {
59
+ summary: 'string',
60
+ },
61
+ },
62
+ defaultValue: { summary: '' },
63
+ },
64
+ isRequired: {
65
+ control: 'boolean',
66
+ description: 'Toggles the required astherisc on the label',
67
+ table: {
68
+ category: 'Props',
69
+ type: {
70
+ summary: 'boolean',
71
+ },
72
+ },
73
+ defaultValue: { summary: 'false' },
74
+ },
75
+ isDisabled: {
76
+ control: 'boolean',
77
+ description: 'Toggles the disabled state of the textarea field',
78
+ table: {
79
+ category: 'Props',
80
+ type: {
81
+ summary: 'boolean',
82
+ },
83
+ },
84
+ defaultValue: { summary: 'false' },
85
+ },
86
+ errorList: {
87
+ control: false,
88
+ description: 'An array of error messages',
89
+ table: {
90
+ category: 'Props',
91
+ type: {
92
+ summary: 'string[]',
93
+ },
94
+ },
95
+ defaultValue: { summary: '[]' },
96
+ },
97
+ helpText: {
98
+ control: 'text',
99
+ description: 'The help text for the textarea field',
100
+ table: {
101
+ category: 'Props',
102
+ type: {
103
+ summary: 'string',
104
+ },
105
+ },
106
+ defaultValue: { summary: '' },
107
+ },
108
+ hasHiddenLabel: {
109
+ control: 'boolean',
110
+ description: 'Hides the label visually (retains it for screen readers)',
111
+ table: {
112
+ category: 'Props',
113
+ type: {
114
+ summary: 'boolean',
115
+ },
116
+ },
117
+ defaultValue: { summary: 'false' },
118
+ },
119
+ autofocus: {
120
+ control: 'boolean',
121
+ description: ' Specifies that a text area should automatically get focus when the page loads',
122
+ table: {
123
+ category: 'Props',
124
+ type: {
125
+ summary: 'boolean',
126
+ },
127
+ },
128
+ defaultValue: { summary: 'false' },
129
+ },
130
+ rows: {
131
+ control: 'number',
132
+ description: 'The number of rows for the textarea field',
133
+ table: {
134
+ category: 'Props',
135
+ type: {
136
+ summary: 'number',
137
+ },
138
+ },
139
+ defaultValue: { summary: undefined },
140
+ },
141
+ cols: {
142
+ control: 'number',
143
+ description: 'Specifies the visible width of a text area',
144
+ table: {
145
+ category: 'Props',
146
+ type: {
147
+ summary: 'number',
148
+ },
149
+ },
150
+ defaultValue: { summary: undefined },
151
+ },
152
+ readonly: {
153
+ control: 'boolean',
154
+ description: 'Sets the textarea field to readonly',
155
+ table: {
156
+ category: 'Props',
157
+ type: {
158
+ summary: 'boolean',
159
+ },
160
+ },
161
+ defaultValue: { summary: 'false' },
162
+ },
163
+ wrap: {
164
+ control: 'text',
165
+ description: 'Sets the wrap attribute for the textarea field',
166
+ table: {
167
+ category: 'Props',
168
+ type: {
169
+ summary: 'string',
170
+ },
171
+ },
172
+ defaultValue: { summary: 'soft' },
173
+ },
174
+ form: {
175
+ control: 'text',
176
+ description: 'Specifies which form the text area belongs to',
177
+ table: {
178
+ category: 'Props',
179
+ type: {
180
+ summary: 'string',
181
+ },
182
+ },
183
+ defaultValue: { summary: '' },
184
+ },
185
+ maxLength: {
186
+ control: 'number',
187
+ description: 'Specifies the maximum number of characters allowed in the text area',
188
+ table: {
189
+ category: 'Props',
190
+ type: {
191
+ summary: 'number',
192
+ },
193
+ },
194
+ defaultValue: { summary: undefined },
195
+ },
196
+ ref: {
197
+ table: {
198
+ disable: true,
199
+ },
200
+ },
201
+ },
202
+ };
203
+
204
+ export default meta;
205
+
206
+ type Story = StoryObj<typeof Textarea>;
207
+
208
+ export const Default: Story = {
209
+ args: {
210
+ isRequired: true,
211
+ helpText: 'This Is Help Text',
212
+ label: 'Label Name',
213
+ name: 'textarea',
214
+ placeholder: 'Please enter a value',
215
+ hasHiddenLabel: false,
216
+ isDisabled: false,
217
+ errorList: [],
218
+ value: '',
219
+ readonly: false,
220
+ cols: 0,
221
+ rows: 0,
222
+ wrap: 'soft',
223
+ form: '',
224
+ maxLength: 200,
225
+ autofocus: false,
226
+ },
227
+ render: (args) => {
228
+ const [value, setValue] = useState(args.value);
229
+
230
+ useEffect(() => {
231
+ setValue(args.value);
232
+ }, [args.value]);
233
+
234
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
235
+ setValue(e.target.value);
236
+ };
237
+
238
+ return <Textarea {...args} value={value} onChange={handleChange} />;
239
+ },
240
+ };
241
+
242
+ export const Errors: Story = {
243
+ args: {
244
+ isRequired: true,
245
+ label: 'Label Name',
246
+ name: 'textarea',
247
+ placeholder: 'Please enter a value',
248
+ hasHiddenLabel: false,
249
+ isDisabled: false,
250
+ errorList: ['This Is An Error', 'This Is Another Error'],
251
+ value: '',
252
+ },
253
+ render: (args) => {
254
+ const [value, setValue] = useState(args.value);
255
+
256
+ useEffect(() => {
257
+ setValue(args.value);
258
+ }, [args.value]);
259
+
260
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
261
+ setValue(e.target.value);
262
+ };
263
+
264
+ return <Textarea {...args} value={value} onChange={handleChange} />;
265
+ },
266
+ };
267
+
268
+ export const HiddenLabel: Story = {
269
+ args: {
270
+ isRequired: true,
271
+ label: 'Label Name',
272
+ name: 'textarea',
273
+ placeholder: 'Please enter a value',
274
+ hasHiddenLabel: true,
275
+ isDisabled: false,
276
+ value: '',
277
+ },
278
+ render: (args) => {
279
+ const [value, setValue] = useState(args.value);
280
+
281
+ useEffect(() => {
282
+ setValue(args.value);
283
+ }, [args.value]);
284
+
285
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
286
+ setValue(e.target.value);
287
+ };
288
+
289
+ return <Textarea {...args} value={value} onChange={handleChange} />;
290
+ },
291
+ };
292
+
293
+ export const HelpText: Story = {
294
+ args: {
295
+ helpText: 'This Is Help Text',
296
+ label: 'Label Name',
297
+ name: 'textarea',
298
+ placeholder: 'Please enter a value',
299
+ isDisabled: false,
300
+ value: '',
301
+ },
302
+ render: (args) => {
303
+ const [value, setValue] = useState(args.value);
304
+
305
+ useEffect(() => {
306
+ setValue(args.value);
307
+ }, [args.value]);
308
+
309
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
310
+ setValue(e.target.value);
311
+ };
312
+
313
+ return <Textarea {...args} value={value} onChange={handleChange} />;
314
+ },
315
+ };
316
+
317
+ export const Disabled: Story = {
318
+ args: {
319
+ helpText: 'This Is Help Text',
320
+ label: 'Label Name',
321
+ name: 'textarea',
322
+ placeholder: 'Please enter a value',
323
+ isDisabled: true,
324
+ value: '',
325
+ },
326
+ render: (args) => {
327
+ const [value, setValue] = useState(args.value);
328
+
329
+ useEffect(() => {
330
+ setValue(args.value);
331
+ }, [args.value]);
332
+
333
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
334
+ setValue(e.target.value);
335
+ };
336
+
337
+ return <Textarea {...args} value={value} onChange={handleChange} />;
338
+ },
339
+ };
340
+
341
+ export const Readonly: Story = {
342
+ args: {
343
+ helpText: 'This Is Help Text',
344
+ label: 'Label Name',
345
+ name: 'textarea',
346
+ placeholder: 'Please enter a value',
347
+ value: '',
348
+ readonly: true,
349
+ },
350
+ render: (args) => {
351
+ const [value, setValue] = useState(args.value);
352
+
353
+ useEffect(() => {
354
+ setValue(args.value);
355
+ }, [args.value]);
356
+
357
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
358
+ setValue(e.target.value);
359
+ };
360
+
361
+ return <Textarea {...args} value={value} onChange={handleChange} />;
362
+ },
363
+ };
@@ -0,0 +1,85 @@
1
+ import React from 'react';
2
+ import { Label } from '../subcomponents/Label';
3
+ import { ErrorList } from '../subcomponents/ErrorList';
4
+
5
+ export interface TextareaProps {
6
+ ref?: React.LegacyRef<HTMLTextAreaElement>;
7
+ label: string;
8
+ name: string;
9
+ placeholder: string;
10
+ value: string;
11
+ onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
12
+ isRequired?: boolean;
13
+ isDisabled?: boolean;
14
+ errorList?: string[];
15
+ helpText?: string;
16
+ hasHiddenLabel?: boolean;
17
+ rows?: number;
18
+ cols?: number;
19
+ readonly?: boolean;
20
+ wrap?: 'hard' | 'soft';
21
+ form?: string;
22
+ maxLength?: number;
23
+ autofocus?: boolean;
24
+ }
25
+
26
+ export const Textarea = ({
27
+ ref,
28
+ label,
29
+ name,
30
+ placeholder,
31
+ value,
32
+ onChange,
33
+ isRequired,
34
+ isDisabled,
35
+ errorList,
36
+ helpText,
37
+ hasHiddenLabel,
38
+ rows,
39
+ cols,
40
+ readonly,
41
+ wrap,
42
+ form,
43
+ maxLength,
44
+ autofocus,
45
+ ...rest
46
+ }: TextareaProps) => {
47
+ const hasErrors = errorList && errorList.length > 0;
48
+
49
+ return (
50
+ <div className="form-control">
51
+ <Label label={label} name={name} isRequired={isRequired} hasHiddenLabel={hasHiddenLabel} />
52
+ <div className="textarea-wrapper">
53
+ <textarea
54
+ ref={ref}
55
+ rows={rows}
56
+ cols={cols}
57
+ autoFocus={autofocus}
58
+ wrap={wrap}
59
+ form={form}
60
+ value={value}
61
+ maxLength={maxLength}
62
+ readOnly={readonly}
63
+ data-testid={`form-textarea-${name}`}
64
+ name={name}
65
+ disabled={isDisabled}
66
+ required={isRequired}
67
+ placeholder={placeholder}
68
+ onChange={onChange}
69
+ className={`textarea ${hasErrors ? 'error' : ''}`}
70
+ aria-invalid={hasErrors}
71
+ aria-describedby={hasErrors || helpText ? `${name}-helper` : undefined}
72
+ aria-required={isRequired}
73
+ aria-label={label}
74
+ {...rest}
75
+ />
76
+ </div>
77
+ {hasErrors && <ErrorList errorList={errorList} name={name} />}
78
+ {helpText && (
79
+ <div data-testid={`${name}-help-text`} className="help-text" id={`${name}-helper`}>
80
+ {helpText}
81
+ </div>
82
+ )}
83
+ </div>
84
+ );
85
+ };
@@ -0,0 +1,103 @@
1
+ import { render, screen, act } from '@testing-library/react';
2
+ import { Textarea } from '@/components/forms/textarea/Textarea';
3
+ import { ChangeEvent } from 'react';
4
+ import userEvent from '@testing-library/user-event';
5
+
6
+ const handleOnChange = jest.fn();
7
+
8
+ describe('Textarea', () => {
9
+ it('renders the input field', () => {
10
+ render(
11
+ <Textarea
12
+ isRequired={true}
13
+ label="Enter your name"
14
+ helpText="In order to submit the form, this field is required."
15
+ name="name"
16
+ placeholder="Please enter a value"
17
+ value={''}
18
+ onChange={handleOnChange}
19
+ />,
20
+ );
21
+ expect(screen.getByText('Enter your name')).toBeInTheDocument();
22
+ });
23
+
24
+ it('adds the error class when errors exist', () => {
25
+ render(
26
+ <Textarea
27
+ isRequired={true}
28
+ errorList={['You require a username value.']}
29
+ label="Enter your name"
30
+ helpText="In order to submit the form, this field is required."
31
+ name="name"
32
+ placeholder="Please enter a value"
33
+ value={'test'}
34
+ onChange={handleOnChange}
35
+ />,
36
+ );
37
+ const textareaElement = screen.getByTestId('form-textarea-name');
38
+ expect(textareaElement).toHaveClass('error');
39
+ });
40
+ it('does not highlight the Textarea when no errors exist', () => {
41
+ render(
42
+ <Textarea
43
+ isRequired={true}
44
+ label="Enter your name"
45
+ helpText="In order to submit the form, this field is required."
46
+ name="name"
47
+ placeholder="Please enter a value"
48
+ value={'test'}
49
+ onChange={handleOnChange}
50
+ />,
51
+ );
52
+ const textareaElement = screen.getByTestId('form-textarea-name');
53
+ expect(textareaElement).not.toHaveClass('error');
54
+ });
55
+ it('renders help text when help text exists', () => {
56
+ render(
57
+ <Textarea
58
+ isRequired={true}
59
+ label="Enter your name"
60
+ helpText="In order to submit the form, this field is required."
61
+ name="name"
62
+ placeholder="Please enter a value"
63
+ value={'test'}
64
+ onChange={handleOnChange}
65
+ />,
66
+ );
67
+ const helpText = screen.getByText('In order to submit the form, this field is required.');
68
+ expect(helpText).toBeInTheDocument();
69
+ expect(helpText).toBeVisible();
70
+ });
71
+ it('does not render help text when help text does not exist', () => {
72
+ render(
73
+ <Textarea
74
+ isRequired={true}
75
+ label="Enter your name"
76
+ name="name"
77
+ placeholder="Please enter a value"
78
+ value={'test'}
79
+ onChange={handleOnChange}
80
+ />,
81
+ );
82
+ const helpText = screen.queryByTestId('name-help-text');
83
+ expect(helpText).not.toBeInTheDocument();
84
+ expect(helpText).toBeNull();
85
+ });
86
+
87
+ it('emits the value when user types', async () => {
88
+ const handleOnChange = jest.fn();
89
+ render(
90
+ <Textarea
91
+ isRequired={true}
92
+ label="Enter your name"
93
+ name="name"
94
+ placeholder="Please enter a value"
95
+ value={''}
96
+ onChange={handleOnChange}
97
+ />,
98
+ );
99
+ const textareaElement = screen.getByTestId('form-textarea-name');
100
+ await userEvent.type(textareaElement, 't');
101
+ expect(handleOnChange).toHaveBeenCalled();
102
+ });
103
+ });
@@ -0,0 +1 @@
1
+ export { Textarea } from './Textarea';
@@ -0,0 +1,102 @@
1
+ // Common Variables
2
+ :root,
3
+ :root [data-theme='light'],
4
+ :root [data-theme='dark'] {
5
+ // Typography
6
+ --pf-textarea-background-color: var(--pf-white-color);
7
+ --pf-textarea-border-color: var(--pf-gray-color);
8
+ --pf-textarea-text-color: var(--pf-gray-color);
9
+ --pf-textarea-placeholder-text-color: var(--pf-gray-color-300);
10
+ --pf-textarea-help-text-color: var(--pf-gray-color-400);
11
+ --pf-textarea-disabled-background-color: var(--pf-gray-color-100);
12
+ --pf-textarea-border-color: var(--pf-gray-color);
13
+ --pf-textarea-disabled-color: var(--pf-gray-color-400);
14
+
15
+ // textarea Radius
16
+ --pf-textarea-rounded: var(--pf-rounded);
17
+ }
18
+
19
+ // Dark Theme Specific Variables
20
+ :root [data-theme='dark'] {
21
+ --pf-textarea-background-color: var(--pf-primary-color);
22
+ --pf-textarea-border-color: var(--pf-gray-color-100);
23
+ --pf-textarea-text-color: var(--pf-gray-color-100);
24
+ --pf-textarea-placeholder-text-color: var(--pf-gray-color);
25
+ --pf-textarea-help-text-color: var(--pf-gray-color-200);
26
+ --pf-textarea-disabled-background-color: var(--pf-primary-color-200);
27
+ --pf-textarea-disabled-border-color: var(--pf-gray-color-300);
28
+ --pf-textarea-disabled-color: var(--pf-gray-color-400);
29
+ }
30
+
31
+ .textarea {
32
+ background-color: var(--pf-textarea-background-color);
33
+ border: 1px solid var(--pf-textarea-border-color);
34
+ border-radius: var(--pf-textarea-rounded);
35
+ color: var(--pf-textarea-text-color);
36
+ padding: 10px;
37
+ width: 100%;
38
+ box-sizing: border-box;
39
+ &::placeholder {
40
+ color: var(--pf-textarea-placeholder-text-color);
41
+ }
42
+
43
+ &:focus {
44
+ border-color: var(--pf-primary-color);
45
+ }
46
+
47
+ &.error {
48
+ border-color: var(--pf-error-color);
49
+ }
50
+
51
+ &.success {
52
+ border-color: var(--pf-success-color);
53
+ }
54
+
55
+ &.warning {
56
+ border-color: var(--pf-warning-color);
57
+ }
58
+
59
+ &.info {
60
+ border-color: var(--pf-info-color);
61
+ }
62
+
63
+ &:disabled {
64
+ background-color: var(--pf-textarea-disabled-background-color);
65
+ border-color: var(--pf-textarea-disabled-border-color);
66
+ color: var(--pf-textarea-disabled-color);
67
+ }
68
+ &--has-icon {
69
+ padding-left: var(--pf-padding-7);
70
+ }
71
+ }
72
+
73
+ .form-control {
74
+ .error-list {
75
+ list-style: none;
76
+ padding: 0;
77
+ margin: 0;
78
+ margin-top: var(--pf-margin-2);
79
+ margin-bottom: var(--pf-margin-2);
80
+ color: var(--pf-error-color);
81
+ }
82
+ .help-text {
83
+ margin-top: var(--pf-margin-2);
84
+ margin-bottom: var(--pf-margin-2);
85
+ color: var(--pf-textarea-help-text-color);
86
+ font-size: var(--pf-font-size-subtitle2);
87
+ }
88
+ .is-visually-hidden {
89
+ position: absolute;
90
+ width: 1px;
91
+ height: 1px;
92
+ padding: 0;
93
+ margin: -1px;
94
+ overflow: hidden;
95
+ clip: rect(0, 0, 0, 0);
96
+ white-space: nowrap;
97
+ border: 0;
98
+ }
99
+ .form-label {
100
+ margin-bottom: var(--pf-margin-2);
101
+ }
102
+ }
@@ -9,6 +9,7 @@
9
9
  @import '../components/forms/input/styles/Input.scss';
10
10
  @import '../components/forms/radio/styles/Radio.scss';
11
11
  @import '../components/forms/checkbox/styles/Checkbox.scss';
12
+ @import '../components/forms/textarea/styles/Textarea.scss';
12
13
 
13
14
  @import '../components/forms/toggle/styles/Toggle.scss';
14
15
  @import 'typography';