@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 +104 -4
- package/lib/index.esm.css +104 -4
- package/lib/src/components/forms/textarea/Textarea.d.ts +22 -0
- package/lib/src/components/forms/textarea/Textarea.stories.d.ts +11 -0
- package/lib/src/components/forms/textarea/__tests__/Textarea.test.d.ts +1 -0
- package/lib/src/components/forms/textarea/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/forms/checkbox/styles/Checkbox.scss +1 -1
- package/src/components/forms/input/Input.stories.tsx +70 -64
- package/src/components/forms/input/styles/Input.scss +10 -3
- package/src/components/forms/textarea/Textarea.mdx +19 -0
- package/src/components/forms/textarea/Textarea.stories.tsx +363 -0
- package/src/components/forms/textarea/Textarea.tsx +85 -0
- package/src/components/forms/textarea/__tests__/Textarea.test.tsx +103 -0
- package/src/components/forms/textarea/index.ts +1 -0
- package/src/components/forms/textarea/styles/Textarea.scss +102 -0
- package/src/styles/index.scss +1 -0
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-
|
|
917
|
-
border-color: var(--pf-
|
|
918
|
-
color: var(--pf-
|
|
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:
|
|
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-
|
|
917
|
-
border-color: var(--pf-
|
|
918
|
-
color: var(--pf-
|
|
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:
|
|
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 {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Textarea } from './Textarea';
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
// Dark Theme Specific Variables
|
|
12
12
|
:root [data-theme='dark'] {
|
|
13
|
-
--pf-checkbox-background-color:
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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-
|
|
60
|
-
border-color: var(--pf-
|
|
61
|
-
color: var(--pf-
|
|
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
|
+
}
|
package/src/styles/index.scss
CHANGED
|
@@ -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';
|