@kroo-web/design-system 1.0.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/.storybook/main.js +19 -0
- package/.storybook/preview-head.html +8 -0
- package/.storybook/preview.js +21 -0
- package/README.MD +17 -0
- package/package.json +35 -0
- package/src/hooks/useWindowSize.ts +29 -0
- package/src/index.ts +3 -0
- package/src/marketing/styles/tokens/variables.css +0 -0
- package/src/product/components/TextField/index.tsx +126 -0
- package/src/product/components/TextField/textField.module.css +115 -0
- package/src/product/components/TextField/textField.stories.mdx +113 -0
- package/src/product/components/TextField/textField.stories.tsx +263 -0
- package/src/product/components/TextField/textField.test.tsx +111 -0
- package/src/product/index.ts +3 -0
- package/src/product/styles/tokens/variables.css +36 -0
- package/src/styles/global.css +20 -0
- package/src/styles/tokens/variables.css +15 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** @type { import('@storybook/react-vite').StorybookConfig } */
|
|
2
|
+
const config = {
|
|
3
|
+
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
|
4
|
+
addons: [
|
|
5
|
+
"@storybook/addon-links",
|
|
6
|
+
"@storybook/addon-essentials",
|
|
7
|
+
"@storybook/addon-onboarding",
|
|
8
|
+
"@storybook/addon-interactions",
|
|
9
|
+
"@storybook/addon-docs"
|
|
10
|
+
],
|
|
11
|
+
framework: {
|
|
12
|
+
name: "@storybook/react-vite",
|
|
13
|
+
options: {},
|
|
14
|
+
},
|
|
15
|
+
docs: {
|
|
16
|
+
autodocs: "tag",
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
export default config;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
2
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
3
|
+
<link
|
|
4
|
+
href="https://fonts.googleapis.com/css2?family=Brygada+1918:ital,wght@0,400;0,500;0,700;1,500&family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap"
|
|
5
|
+
rel="stylesheet"
|
|
6
|
+
crossorigin="anonymous"
|
|
7
|
+
/>
|
|
8
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** @type { import('@storybook/react').Preview } */
|
|
2
|
+
import "../src/styles/global.css";
|
|
3
|
+
const preview = {
|
|
4
|
+
parameters: {
|
|
5
|
+
docs: {
|
|
6
|
+
toc: {
|
|
7
|
+
headingSelector: "h2, h3, h4, h5, h6",
|
|
8
|
+
title: "Contents"
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
actions: { argTypesRegex: "^on[A-Z].*" },
|
|
12
|
+
controls: {
|
|
13
|
+
matchers: {
|
|
14
|
+
color: /(background|color)$/i,
|
|
15
|
+
date: /Date$/i,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default preview;
|
package/README.MD
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Kroo Web Component Lib / Design system
|
|
2
|
+
|
|
3
|
+
## Usage
|
|
4
|
+
|
|
5
|
+
### Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @kroo-web/design-system -S
|
|
9
|
+
```
|
|
10
|
+
or
|
|
11
|
+
```bash
|
|
12
|
+
yarn add @kroo-web/design-system -S
|
|
13
|
+
```
|
|
14
|
+
or
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @kroo-web/design-system -S
|
|
17
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kroo-web/design-system",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"storybook": "storybook dev -p 6006",
|
|
9
|
+
"build-storybook": "storybook build",
|
|
10
|
+
"deploy": "npm publish --access public"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@storybook/addon-essentials": "^7.6.17",
|
|
17
|
+
"@storybook/addon-interactions": "^7.6.17",
|
|
18
|
+
"@storybook/addon-links": "^7.6.17",
|
|
19
|
+
"@storybook/addon-onboarding": "^1.0.11",
|
|
20
|
+
"@storybook/blocks": "^7.6.17",
|
|
21
|
+
"@storybook/react": "^7.6.17",
|
|
22
|
+
"@storybook/react-vite": "^7.6.17",
|
|
23
|
+
"@storybook/test": "^7.6.17",
|
|
24
|
+
"prop-types": "^15.8.1",
|
|
25
|
+
"react": "^18.2.0",
|
|
26
|
+
"react-dom": "^18.2.0",
|
|
27
|
+
"storybook": "^7.6.17"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@storybook/addon-docs": "^7.6.17",
|
|
31
|
+
"clsx": "^2.1.0",
|
|
32
|
+
"framer-motion": "^11.0.8",
|
|
33
|
+
"react-hook-form": "^7.51.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useWindowSize() {
|
|
4
|
+
const [windowSize, setWindowSize] = useState<{
|
|
5
|
+
width: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
}>({
|
|
8
|
+
width: 600,
|
|
9
|
+
height: undefined,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
// Handler to call on window resize
|
|
14
|
+
function handleResize() {
|
|
15
|
+
// Set window width/height to state
|
|
16
|
+
setWindowSize({
|
|
17
|
+
width: window.innerWidth || 600,
|
|
18
|
+
height: window.innerHeight,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
// Add event listener
|
|
22
|
+
window.addEventListener('resize', handleResize, { passive: true });
|
|
23
|
+
// Call handler right away so state gets updated with initial window size
|
|
24
|
+
handleResize();
|
|
25
|
+
// Remove event listener on cleanup
|
|
26
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
27
|
+
}, []); // Empty array ensures that effect is only run on mount
|
|
28
|
+
return windowSize;
|
|
29
|
+
}
|
package/src/index.ts
ADDED
|
File without changes
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
DetailedHTMLProps,
|
|
3
|
+
InputHTMLAttributes,
|
|
4
|
+
ReactNode,
|
|
5
|
+
useState,
|
|
6
|
+
} from 'react';
|
|
7
|
+
import { motion } from 'framer-motion';
|
|
8
|
+
import clsx from 'clsx';
|
|
9
|
+
import { FieldValues, Path, UseFormRegister } from 'react-hook-form';
|
|
10
|
+
import { useWindowSize } from '../../../hooks/useWindowSize';
|
|
11
|
+
import styles from "./textField.module.css"
|
|
12
|
+
|
|
13
|
+
export type TTextFieldProps<T extends FieldValues> = {
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
name: Path<T>;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
helper?: {
|
|
19
|
+
message: ReactNode | string;
|
|
20
|
+
};
|
|
21
|
+
error?: {
|
|
22
|
+
message: ReactNode | string;
|
|
23
|
+
};
|
|
24
|
+
/** React Hook form requirement if you dont pass it in the component will act like a normal uncontrolled input */
|
|
25
|
+
register?: UseFormRegister<T>;
|
|
26
|
+
type?: DetailedHTMLProps<
|
|
27
|
+
InputHTMLAttributes<HTMLInputElement>,
|
|
28
|
+
HTMLInputElement
|
|
29
|
+
>['type'];
|
|
30
|
+
prefix?: string | ReactNode;
|
|
31
|
+
suffix?: string | ReactNode;
|
|
32
|
+
value?: string;
|
|
33
|
+
className?: string;
|
|
34
|
+
rightContent?: ReactNode | string;
|
|
35
|
+
leftContent?: ReactNode | string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const TextField = <T extends FieldValues>({
|
|
39
|
+
id,
|
|
40
|
+
label,
|
|
41
|
+
helper,
|
|
42
|
+
error,
|
|
43
|
+
register,
|
|
44
|
+
name,
|
|
45
|
+
type,
|
|
46
|
+
prefix,
|
|
47
|
+
suffix,
|
|
48
|
+
disabled,
|
|
49
|
+
value,
|
|
50
|
+
className,
|
|
51
|
+
rightContent,
|
|
52
|
+
leftContent,
|
|
53
|
+
}: TTextFieldProps<T>) => {
|
|
54
|
+
const { width } = useWindowSize();
|
|
55
|
+
const [hasValue, setHasValue] = useState(value || false);
|
|
56
|
+
const isDesktop = width > 768;
|
|
57
|
+
|
|
58
|
+
const variants = {
|
|
59
|
+
focused: {
|
|
60
|
+
fontSize: isDesktop ? '1rem' : '0.875rem',
|
|
61
|
+
top: '1rem',
|
|
62
|
+
},
|
|
63
|
+
notFocused: {
|
|
64
|
+
transform: 'translateY(-50%)',
|
|
65
|
+
fontSize: isDesktop ? '1.25rem' : '1.125rem',
|
|
66
|
+
top: '50%',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className={styles.outer}>
|
|
72
|
+
<div
|
|
73
|
+
className={clsx(styles.container, error && styles['container--error'])}
|
|
74
|
+
>
|
|
75
|
+
<motion.label
|
|
76
|
+
className={styles.label}
|
|
77
|
+
initial="notFocused"
|
|
78
|
+
variants={variants}
|
|
79
|
+
htmlFor={`input-${id}`}
|
|
80
|
+
animate={hasValue ? 'focused' : 'notFocused'}
|
|
81
|
+
>
|
|
82
|
+
{prefix && <span>{prefix}</span>} {label}{' '}
|
|
83
|
+
{suffix && <span>{suffix}</span>}
|
|
84
|
+
</motion.label>
|
|
85
|
+
<input
|
|
86
|
+
className={clsx(styles[`input--${className}`], styles.input)}
|
|
87
|
+
id={`input-${id}`}
|
|
88
|
+
type={type}
|
|
89
|
+
aria-describedby={`error-${id}`}
|
|
90
|
+
disabled={disabled}
|
|
91
|
+
{...(register && register(name),
|
|
92
|
+
{
|
|
93
|
+
onFocus: () => {
|
|
94
|
+
setHasValue(true);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
onBlur: (e) => {
|
|
98
|
+
setHasValue(!!e.target.value);
|
|
99
|
+
},
|
|
100
|
+
})}
|
|
101
|
+
value={value}
|
|
102
|
+
/>
|
|
103
|
+
<div>
|
|
104
|
+
{rightContent && (
|
|
105
|
+
<div className={styles.rightContent}>{rightContent}</div>
|
|
106
|
+
)}
|
|
107
|
+
{leftContent && (
|
|
108
|
+
<div className={styles.leftContent}>{leftContent}</div>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
<div className={styles.inputDescription}>
|
|
113
|
+
{helper && (
|
|
114
|
+
<span id={`feedback-${id}`} className={styles.helper}>
|
|
115
|
+
{helper.message}
|
|
116
|
+
</span>
|
|
117
|
+
)}
|
|
118
|
+
{error && (
|
|
119
|
+
<span id={`error-${id}`} className={styles.error}>
|
|
120
|
+
{error.message}
|
|
121
|
+
</span>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
.outer {
|
|
2
|
+
margin-bottom: 1rem;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.container {
|
|
6
|
+
position: relative;
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.label {
|
|
12
|
+
position: absolute;
|
|
13
|
+
z-index: 3;
|
|
14
|
+
left: 1.25rem;
|
|
15
|
+
color: #666666;
|
|
16
|
+
pointer-events: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.input {
|
|
20
|
+
all: unset;
|
|
21
|
+
background: white;
|
|
22
|
+
padding: 1rem 1.25rem;
|
|
23
|
+
padding-top: 1.70rem;
|
|
24
|
+
border-radius: var(--product-radius-2);
|
|
25
|
+
z-index: 1;
|
|
26
|
+
font-size: 1.125rem;
|
|
27
|
+
caret-color: var(--primary-love-pink-100);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.inputDescription {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
gap: 0.5rem;
|
|
34
|
+
padding-top: 0.5rem;
|
|
35
|
+
padding-left: 1.5rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.container--error > label {
|
|
39
|
+
color: var(--product-error);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.container--error > input {
|
|
43
|
+
border: var(--product-border-width-1) solid var(--product-error);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.error {
|
|
47
|
+
color: var(--product-error);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.helper {
|
|
51
|
+
color: rgba(var(--grey-60-rgb));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.rightContent {
|
|
55
|
+
position: absolute;
|
|
56
|
+
right: 1.5rem;
|
|
57
|
+
top: 50%;
|
|
58
|
+
transform: translateY(-50%);
|
|
59
|
+
z-index: 2;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.leftContent {
|
|
63
|
+
position: absolute;
|
|
64
|
+
left: 1.5rem;
|
|
65
|
+
top: 50%;
|
|
66
|
+
transform: translateY(-50%);
|
|
67
|
+
z-index: 2;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
.input:focus {
|
|
72
|
+
border: var(--product-border-width-1) solid rgba(var(--primary-dark-rgb));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.input--focus {
|
|
76
|
+
border: var(--product-border-width-1) solid rgba(var(--primary-dark-rgb));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.input:hover {
|
|
80
|
+
outline: var(--product-border-width-1) solid #E6E6E6;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.input--hover {
|
|
84
|
+
outline: var(--product-border-width-1) solid #E6E6E6;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.input:disabled {
|
|
88
|
+
background: #E6E6E6;
|
|
89
|
+
cursor: not-allowed;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.input:disabled:hover {
|
|
93
|
+
outline: none;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@media (min-width: 768px) {
|
|
99
|
+
.input {
|
|
100
|
+
padding: 1.5rem;
|
|
101
|
+
padding-top: 1.8rem;
|
|
102
|
+
font-size: 1.25rem;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.label {
|
|
106
|
+
left: 1.5rem;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Meta, Canvas } from "@storybook/addon-docs";
|
|
2
|
+
import { TextField } from '.'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
<Meta title="Design System Product/C1 - TextField / Documentation" />
|
|
6
|
+
|
|
7
|
+
# TextField
|
|
8
|
+
|
|
9
|
+
TextFields are text inputs that allow users to input custom text entries with a keyboard. Various decorations can be displayed around the field to communicate the entry requirements.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Example
|
|
13
|
+
|
|
14
|
+
<Canvas>
|
|
15
|
+
<TextField
|
|
16
|
+
label="Label"
|
|
17
|
+
/>
|
|
18
|
+
</Canvas>
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Usage
|
|
22
|
+
|
|
23
|
+
In the Nextjs repo and this component is expected to be used with react-hook-form and accepts the
|
|
24
|
+
register prop from useForm to hook it upto the form.
|
|
25
|
+
|
|
26
|
+
However, it can be used as a standalone component as well without registering in a HTML form if needed.
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { useForm } from 'react-hook-form'
|
|
30
|
+
import { TextField } from 'design-system-product'
|
|
31
|
+
|
|
32
|
+
const { register, handleSubmit } = useForm()
|
|
33
|
+
|
|
34
|
+
const onSubmit = (data) => {
|
|
35
|
+
console.log(data)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
40
|
+
<TextField
|
|
41
|
+
label="Label"
|
|
42
|
+
{...register('example')}
|
|
43
|
+
/>
|
|
44
|
+
</form>
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Labelling
|
|
49
|
+
|
|
50
|
+
The label prop is required and is used to display the label for the input field. A label is required for accessiblity and should
|
|
51
|
+
always be provided.
|
|
52
|
+
|
|
53
|
+
<Canvas>
|
|
54
|
+
<TextField
|
|
55
|
+
label="Label" />
|
|
56
|
+
</Canvas>
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
### Required Fields
|
|
62
|
+
|
|
63
|
+
Currently we do not support the required prop with the TextField component due to currently our forms assumes that everything
|
|
64
|
+
is required and we have no reason to ask for everything to be required. Instead please use the suffix prop to add an (optional) tag
|
|
65
|
+
to the label.
|
|
66
|
+
|
|
67
|
+
<Canvas>
|
|
68
|
+
<TextField
|
|
69
|
+
label="Label"
|
|
70
|
+
suffix="(optional)" />
|
|
71
|
+
</Canvas>
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## Visual Options
|
|
75
|
+
|
|
76
|
+
### Validation
|
|
77
|
+
Validation will be handled by react-hook-form or by passing an error with a message to the error prop.
|
|
78
|
+
The error prop will display an error message below the input field and change the border of the input field to red.
|
|
79
|
+
|
|
80
|
+
<Canvas>
|
|
81
|
+
<TextField
|
|
82
|
+
label="Label"
|
|
83
|
+
error="Error Message" />
|
|
84
|
+
</Canvas>
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
### Read Only
|
|
90
|
+
The ReadOnly prop is used to make the input field read only. This is useful when you want to display and copy the value of the input field but not allow the user to edit it. Use this instead of the disabled state.
|
|
91
|
+
|
|
92
|
+
<Canvas>
|
|
93
|
+
<TextField
|
|
94
|
+
value="Read Only Value"
|
|
95
|
+
label="Label"
|
|
96
|
+
readOnly />
|
|
97
|
+
</Canvas>
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
### Disabled
|
|
102
|
+
The disabled prop is used to disable the input field. This is useful when you want to prevent the user from editing the input field. Use this instead of the readOnly state.
|
|
103
|
+
|
|
104
|
+
<Canvas>
|
|
105
|
+
<TextField
|
|
106
|
+
value="Example Value"
|
|
107
|
+
label="Label"
|
|
108
|
+
disabled />
|
|
109
|
+
</Canvas>
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { TextField } from '.';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Design System Product/C1 - TextField',
|
|
7
|
+
component: TextField,
|
|
8
|
+
parameters: {},
|
|
9
|
+
} as Meta<typeof TextField>;
|
|
10
|
+
|
|
11
|
+
type Story = StoryObj<typeof TextField>;
|
|
12
|
+
|
|
13
|
+
export const Default: Story = {
|
|
14
|
+
render: () => (
|
|
15
|
+
<>
|
|
16
|
+
<TextField id="default-no-value" label="Default" name="example" />
|
|
17
|
+
<TextField
|
|
18
|
+
className="hover"
|
|
19
|
+
id="default2-no-value"
|
|
20
|
+
label="Hovered"
|
|
21
|
+
name="example2"
|
|
22
|
+
/>
|
|
23
|
+
<TextField
|
|
24
|
+
className="focus"
|
|
25
|
+
id="default3-no-value"
|
|
26
|
+
label="Focused"
|
|
27
|
+
name="example3"
|
|
28
|
+
/>
|
|
29
|
+
|
|
30
|
+
<TextField
|
|
31
|
+
id="default-value"
|
|
32
|
+
label="Default"
|
|
33
|
+
name="example"
|
|
34
|
+
value="Example text"
|
|
35
|
+
/>
|
|
36
|
+
<TextField
|
|
37
|
+
className="hover"
|
|
38
|
+
id="default2-value"
|
|
39
|
+
label="Hovered"
|
|
40
|
+
name="example2"
|
|
41
|
+
value="Example text"
|
|
42
|
+
/>
|
|
43
|
+
<TextField
|
|
44
|
+
className="focus"
|
|
45
|
+
id="default3-value"
|
|
46
|
+
label="Focused"
|
|
47
|
+
name="example3"
|
|
48
|
+
value="Example text"
|
|
49
|
+
/>
|
|
50
|
+
<TextField
|
|
51
|
+
id="default4-value"
|
|
52
|
+
label="Disabled "
|
|
53
|
+
name="example4"
|
|
54
|
+
disabled
|
|
55
|
+
/>
|
|
56
|
+
</>
|
|
57
|
+
),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const Error: Story = {
|
|
61
|
+
render: () => (
|
|
62
|
+
<>
|
|
63
|
+
<TextField
|
|
64
|
+
id="error"
|
|
65
|
+
label="Default"
|
|
66
|
+
name="error1"
|
|
67
|
+
error={{ message: 'This is an error' }}
|
|
68
|
+
/>
|
|
69
|
+
<TextField
|
|
70
|
+
className="hover"
|
|
71
|
+
id="error2"
|
|
72
|
+
label="Hovered"
|
|
73
|
+
name="error2"
|
|
74
|
+
error={{ message: 'This is an error' }}
|
|
75
|
+
/>
|
|
76
|
+
<TextField
|
|
77
|
+
className="focus"
|
|
78
|
+
id="error3"
|
|
79
|
+
label="Focused"
|
|
80
|
+
name="error3"
|
|
81
|
+
error={{ message: 'This is an error' }}
|
|
82
|
+
/>
|
|
83
|
+
<TextField
|
|
84
|
+
id="error4-value"
|
|
85
|
+
label="Default"
|
|
86
|
+
name="error4-value"
|
|
87
|
+
value="Example text"
|
|
88
|
+
error={{ message: 'This is an error' }}
|
|
89
|
+
/>
|
|
90
|
+
<TextField
|
|
91
|
+
className="hover"
|
|
92
|
+
id="error5-value"
|
|
93
|
+
label="Hovered"
|
|
94
|
+
name="error5-value"
|
|
95
|
+
value="Example text"
|
|
96
|
+
error={{ message: 'This is an error' }}
|
|
97
|
+
/>
|
|
98
|
+
<TextField
|
|
99
|
+
className="focus"
|
|
100
|
+
id="error5-value"
|
|
101
|
+
label="Focused"
|
|
102
|
+
name="error5-value"
|
|
103
|
+
value="Example text"
|
|
104
|
+
error={{ message: 'This is an error' }}
|
|
105
|
+
/>
|
|
106
|
+
<TextField
|
|
107
|
+
id="error4"
|
|
108
|
+
label="Disabled"
|
|
109
|
+
name="error4"
|
|
110
|
+
error={{ message: 'This is an error' }}
|
|
111
|
+
disabled
|
|
112
|
+
/>
|
|
113
|
+
</>
|
|
114
|
+
),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const HelperText: Story = {
|
|
118
|
+
render: () => (
|
|
119
|
+
<>
|
|
120
|
+
<TextField
|
|
121
|
+
id="helper"
|
|
122
|
+
label="Default"
|
|
123
|
+
name="helper1"
|
|
124
|
+
helper={{ message: 'This is helper text' }}
|
|
125
|
+
/>
|
|
126
|
+
<TextField
|
|
127
|
+
className="hover"
|
|
128
|
+
id="helper2"
|
|
129
|
+
label="Hovered"
|
|
130
|
+
name="helper2"
|
|
131
|
+
helper={{ message: 'This is helper text' }}
|
|
132
|
+
/>
|
|
133
|
+
<TextField
|
|
134
|
+
className="focus"
|
|
135
|
+
id="helper3"
|
|
136
|
+
label="Focused"
|
|
137
|
+
name="helper3"
|
|
138
|
+
helper={{ message: 'This is helper text' }}
|
|
139
|
+
/>
|
|
140
|
+
<TextField
|
|
141
|
+
id="helper4-text"
|
|
142
|
+
label="Default"
|
|
143
|
+
name="helper4-text"
|
|
144
|
+
helper={{ message: 'This is helper text' }}
|
|
145
|
+
value="Example text"
|
|
146
|
+
/>
|
|
147
|
+
<TextField
|
|
148
|
+
className="hover"
|
|
149
|
+
id="helper5-text"
|
|
150
|
+
label="Hovered"
|
|
151
|
+
name="helper5-text"
|
|
152
|
+
helper={{ message: 'This is helper text' }}
|
|
153
|
+
value="Example text"
|
|
154
|
+
/>
|
|
155
|
+
<TextField
|
|
156
|
+
className="focus"
|
|
157
|
+
id="helper6-text"
|
|
158
|
+
label="Focused"
|
|
159
|
+
name="helper6-text"
|
|
160
|
+
helper={{ message: 'This is helper text' }}
|
|
161
|
+
value="Example text"
|
|
162
|
+
/>
|
|
163
|
+
<TextField
|
|
164
|
+
id="helper4"
|
|
165
|
+
label="Disabled"
|
|
166
|
+
name="helper4"
|
|
167
|
+
helper={{ message: 'This is helper text' }}
|
|
168
|
+
disabled
|
|
169
|
+
/>
|
|
170
|
+
</>
|
|
171
|
+
),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const RightContent: Story = {
|
|
175
|
+
render: () => (
|
|
176
|
+
<>
|
|
177
|
+
<TextField
|
|
178
|
+
id="right"
|
|
179
|
+
label="Default"
|
|
180
|
+
name="right1"
|
|
181
|
+
rightContent={<button>Example</button>}
|
|
182
|
+
/>
|
|
183
|
+
<TextField
|
|
184
|
+
className="hover"
|
|
185
|
+
id="right2"
|
|
186
|
+
label="Hovered"
|
|
187
|
+
name="right2"
|
|
188
|
+
rightContent={<button>Example</button>}
|
|
189
|
+
/>
|
|
190
|
+
<TextField
|
|
191
|
+
className="focus"
|
|
192
|
+
id="right3"
|
|
193
|
+
label="Focused"
|
|
194
|
+
name="right3"
|
|
195
|
+
rightContent={<button>Example</button>}
|
|
196
|
+
/>
|
|
197
|
+
<TextField
|
|
198
|
+
id="right4-text"
|
|
199
|
+
label="Default"
|
|
200
|
+
name="right4-text"
|
|
201
|
+
value="Example text"
|
|
202
|
+
rightContent={<button>Example</button>}
|
|
203
|
+
/>
|
|
204
|
+
<TextField
|
|
205
|
+
className="hover"
|
|
206
|
+
id="right5-text"
|
|
207
|
+
label="Hovered"
|
|
208
|
+
name="right5-text"
|
|
209
|
+
value="Example text"
|
|
210
|
+
rightContent={<button>Example</button>}
|
|
211
|
+
/>
|
|
212
|
+
<TextField
|
|
213
|
+
className="focus"
|
|
214
|
+
id="right6-text"
|
|
215
|
+
label="Focused"
|
|
216
|
+
name="right6-text"
|
|
217
|
+
value="Example text"
|
|
218
|
+
rightContent={<button>Example</button>}
|
|
219
|
+
/>
|
|
220
|
+
|
|
221
|
+
<TextField
|
|
222
|
+
id="right4"
|
|
223
|
+
label="Disabled"
|
|
224
|
+
name="right4"
|
|
225
|
+
rightContent={<button>Example</button>}
|
|
226
|
+
disabled
|
|
227
|
+
/>
|
|
228
|
+
</>
|
|
229
|
+
),
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const LeftContent: Story = {
|
|
233
|
+
render: () => (
|
|
234
|
+
<>
|
|
235
|
+
<h1>No Designs</h1>
|
|
236
|
+
<br />
|
|
237
|
+
<br />
|
|
238
|
+
|
|
239
|
+
<TextField id="left" label="Default" name="left1" leftContent="Example" />
|
|
240
|
+
<TextField
|
|
241
|
+
className="hover"
|
|
242
|
+
id="left2"
|
|
243
|
+
label="Hovered"
|
|
244
|
+
name="left2"
|
|
245
|
+
leftContent="Example"
|
|
246
|
+
/>
|
|
247
|
+
<TextField
|
|
248
|
+
className="focus"
|
|
249
|
+
id="left3"
|
|
250
|
+
label="Focused"
|
|
251
|
+
name="left3"
|
|
252
|
+
leftContent="Example"
|
|
253
|
+
/>
|
|
254
|
+
<TextField
|
|
255
|
+
id="left4"
|
|
256
|
+
label="Disabled"
|
|
257
|
+
name="left4"
|
|
258
|
+
leftContent="Example"
|
|
259
|
+
disabled
|
|
260
|
+
/>
|
|
261
|
+
</>
|
|
262
|
+
),
|
|
263
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { TextField } from '.';
|
|
5
|
+
|
|
6
|
+
describe('<TextField />', () => {
|
|
7
|
+
it('should render', () => {
|
|
8
|
+
const component = render(
|
|
9
|
+
<TextField
|
|
10
|
+
id="example-TextField"
|
|
11
|
+
label="example-TextField"
|
|
12
|
+
name="example"
|
|
13
|
+
/>,
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
expect(component).toBeTruthy();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should show the helper text when supplied to the component', () => {
|
|
20
|
+
const component = render(
|
|
21
|
+
<TextField
|
|
22
|
+
helper={{ message: 'Helper text' }}
|
|
23
|
+
id="example-TextField"
|
|
24
|
+
label="example-TextField"
|
|
25
|
+
name="example"
|
|
26
|
+
/>,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
expect(component.getByText('Helper text')).toBeTruthy();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should show the error text when supplied to the component', () => {
|
|
33
|
+
const component = render(
|
|
34
|
+
<TextField
|
|
35
|
+
error={{ message: 'Error text' }}
|
|
36
|
+
id="example-TextField"
|
|
37
|
+
label="example-TextField"
|
|
38
|
+
name="example"
|
|
39
|
+
/>,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(component.getByText('Error text')).toBeTruthy();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should show the prefix when supplied to the component', () => {
|
|
46
|
+
const component = render(
|
|
47
|
+
<TextField
|
|
48
|
+
id="example-TextField"
|
|
49
|
+
label="example-TextField"
|
|
50
|
+
name="example"
|
|
51
|
+
prefix="Prefix"
|
|
52
|
+
/>,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(component.getByText('Prefix')).toBeTruthy();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should show the suffix when supplied to the component', () => {
|
|
59
|
+
const component = render(
|
|
60
|
+
<TextField
|
|
61
|
+
id="example-TextField"
|
|
62
|
+
label="example-TextField"
|
|
63
|
+
name="example"
|
|
64
|
+
suffix="Suffix"
|
|
65
|
+
/>,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
expect(component.getByText('Suffix')).toBeTruthy();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should show the label when supplied to the component', () => {
|
|
72
|
+
const component = render(
|
|
73
|
+
<TextField
|
|
74
|
+
id="example-TextField"
|
|
75
|
+
label="example-TextField"
|
|
76
|
+
name="example"
|
|
77
|
+
/>,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(component.getByText('example-TextField')).toBeTruthy();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('Should show an error message if one is present', () => {
|
|
84
|
+
const component = render(
|
|
85
|
+
<TextField
|
|
86
|
+
error={{ message: 'Error text' }}
|
|
87
|
+
id="example-TextField"
|
|
88
|
+
label="example-TextField"
|
|
89
|
+
name="example"
|
|
90
|
+
/>,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(component.getByText('Error text')).toBeTruthy();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should be able to input text into the input field', async () => {
|
|
97
|
+
const component = render(
|
|
98
|
+
<TextField
|
|
99
|
+
id="example-TextField"
|
|
100
|
+
label="example-TextField"
|
|
101
|
+
name="example"
|
|
102
|
+
/>,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const input = component.getByLabelText('example-TextField');
|
|
106
|
+
|
|
107
|
+
await userEvent.type(input, 'Hello, World!');
|
|
108
|
+
|
|
109
|
+
expect(input).toHaveValue('Hello, World!');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
/* Product Variables */
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
/* Colors */
|
|
7
|
+
--product-error: rgba(var(--system-red-rgb));
|
|
8
|
+
|
|
9
|
+
/* Spacing */
|
|
10
|
+
--product-spacing-1: 0.25rem;
|
|
11
|
+
--product-spacing-2: 0.50rem;
|
|
12
|
+
--product-spacing-3: 0.75rem;
|
|
13
|
+
--product-spacing-4: 1rem;
|
|
14
|
+
--product-spacing-5: 1.25rem;
|
|
15
|
+
--product-spacing-6: 1.50rem;
|
|
16
|
+
--product-spacing-7: 2rem;
|
|
17
|
+
--product-spacing-8: 2.50rem;
|
|
18
|
+
--product-spacing-9: 3rem;
|
|
19
|
+
--product-spacing-10: 3.50rem;
|
|
20
|
+
--product-spacing-11: 4rem;
|
|
21
|
+
|
|
22
|
+
/* Border Radius */
|
|
23
|
+
--product-radius-1: 0.25rem;
|
|
24
|
+
--product-radius-2: 0.50rem;
|
|
25
|
+
--product-radius-3: 0.75rem;
|
|
26
|
+
--product-radius-4: 1rem;
|
|
27
|
+
|
|
28
|
+
/* Border Width */
|
|
29
|
+
--product-border-width-1: 2px;
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/* Component Specific Variables */
|
|
33
|
+
|
|
34
|
+
--product-text-field-spacing: 0.625rem;
|
|
35
|
+
--product-radio-select-spacing: 1.25rem;
|
|
36
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
/* Import all tokens from each section of Design System */
|
|
3
|
+
@import "./tokens/variables.css";
|
|
4
|
+
|
|
5
|
+
* {
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
padding: 0;
|
|
8
|
+
margin: 0;
|
|
9
|
+
font-family: var(--font-primary);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.sr-only:not(:focus):not(:active) {
|
|
13
|
+
clip: rect(0 0 0 0);
|
|
14
|
+
clip-path: inset(50%);
|
|
15
|
+
height: 1px;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
position: absolute;
|
|
18
|
+
white-space: nowrap;
|
|
19
|
+
width: 1px;
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* PRODUCT TOKENS */
|
|
2
|
+
@import url("../../product/styles/tokens/variables.css");
|
|
3
|
+
|
|
4
|
+
/* MARKETING TOKENS */
|
|
5
|
+
|
|
6
|
+
@import url("../../marketing/styles/tokens/variables.css");
|
|
7
|
+
|
|
8
|
+
/* GLOBAL TOKENS */
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
--font-primary: "Inter", sans-serif;
|
|
12
|
+
--font-secondary: "Brygada 1918", sans-serif;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": [
|
|
4
|
+
"dom",
|
|
5
|
+
"dom.iterable",
|
|
6
|
+
"esnext"
|
|
7
|
+
],
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"module": "esnext",
|
|
14
|
+
"moduleResolution": "node",
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"jsx": "preserve",
|
|
18
|
+
"incremental": true,
|
|
19
|
+
"paths": {
|
|
20
|
+
"@/*": ["./src/*"],
|
|
21
|
+
"@hooks/*": ["./src/hooks/*"]
|
|
22
|
+
},
|
|
23
|
+
"forceConsistentCasingInFileNames": true,
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
}
|