Skip to content

Commit c3328b1

Browse files
Add Input, TextArea, Checkbox, and Button components with styles; refactor DocumentBlockForm and DocumentForm to utilize new components
1 parent a2b86ed commit c3328b1

15 files changed

Lines changed: 730 additions & 88 deletions

File tree

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
.button {
2+
display: inline-flex;
3+
align-items: center;
4+
justify-content: center;
5+
gap: 8px;
6+
border: 1px solid transparent;
7+
border-radius: 5px;
8+
font-size: 16px;
9+
font-family: sans-serif;
10+
font-weight: 500;
11+
cursor: pointer;
12+
transition: all 0.2s ease;
13+
outline: none;
14+
text-decoration: none;
15+
box-sizing: border-box;
16+
user-select: none;
17+
position: relative;
18+
}
19+
20+
.button:focus {
21+
outline: none;
22+
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
23+
}
24+
25+
.button:disabled {
26+
cursor: not-allowed;
27+
opacity: 0.6;
28+
}
29+
30+
/* Variants */
31+
.primary {
32+
background-color: #007bff;
33+
color: white;
34+
border-color: #007bff;
35+
}
36+
37+
.primary:hover:not(:disabled) {
38+
background-color: #0056b3;
39+
border-color: #0056b3;
40+
}
41+
42+
.primary:active:not(:disabled) {
43+
background-color: #004085;
44+
border-color: #004085;
45+
}
46+
47+
.secondary {
48+
background-color: #f5f5f5;
49+
color: #333;
50+
border-color: #ccc;
51+
}
52+
53+
.secondary:hover:not(:disabled) {
54+
background-color: #e9ecef;
55+
border-color: #adb5bd;
56+
}
57+
58+
.secondary:active:not(:disabled) {
59+
background-color: #dee2e6;
60+
border-color: #adb5bd;
61+
}
62+
63+
.danger {
64+
background-color: #ff4d4f;
65+
color: white;
66+
border-color: #ff4d4f;
67+
}
68+
69+
.danger:hover:not(:disabled) {
70+
background-color: #d9363e;
71+
border-color: #d9363e;
72+
}
73+
74+
.danger:active:not(:disabled) {
75+
background-color: #c82333;
76+
border-color: #c82333;
77+
}
78+
79+
.ghost {
80+
background-color: transparent;
81+
color: #007bff;
82+
border-color: transparent;
83+
}
84+
85+
.ghost:hover:not(:disabled) {
86+
background-color: rgba(0, 123, 255, 0.1);
87+
border-color: transparent;
88+
}
89+
90+
.ghost:active:not(:disabled) {
91+
background-color: rgba(0, 123, 255, 0.2);
92+
border-color: transparent;
93+
}
94+
95+
/* Sizes */
96+
.small {
97+
padding: 6px 12px;
98+
font-size: 14px;
99+
min-height: 32px;
100+
}
101+
102+
.medium {
103+
padding: 10px 16px;
104+
font-size: 16px;
105+
min-height: 40px;
106+
}
107+
108+
.large {
109+
padding: 12px 20px;
110+
font-size: 18px;
111+
min-height: 48px;
112+
}
113+
114+
/* Loading spinner */
115+
.spinner {
116+
width: 16px;
117+
height: 16px;
118+
border: 2px solid transparent;
119+
border-top: 2px solid currentColor;
120+
border-radius: 50%;
121+
animation: spin 1s linear infinite;
122+
}
123+
124+
@keyframes spin {
125+
0% {
126+
transform: rotate(0deg);
127+
}
128+
100% {
129+
transform: rotate(360deg);
130+
}
131+
}
132+
133+
/* Responsive design */
134+
@media (max-width: 768px) {
135+
.button {
136+
font-size: 16px; /* Prevents zoom on iOS */
137+
}
138+
139+
.small {
140+
padding: 8px 14px;
141+
font-size: 14px;
142+
}
143+
144+
.medium {
145+
padding: 12px 18px;
146+
font-size: 16px;
147+
}
148+
149+
.large {
150+
padding: 14px 22px;
151+
font-size: 18px;
152+
}
153+
}

src/components/Button/Button.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use client'
2+
3+
import React from 'react'
4+
import classes from './Button.module.css'
5+
6+
export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost'
7+
export type ButtonSize = 'small' | 'medium' | 'large'
8+
9+
export interface ButtonProps
10+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
11+
variant?: ButtonVariant
12+
size?: ButtonSize
13+
loading?: boolean
14+
children: React.ReactNode
15+
}
16+
17+
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
18+
(
19+
{
20+
variant = 'primary',
21+
size = 'medium',
22+
loading = false,
23+
disabled,
24+
className,
25+
children,
26+
...props
27+
},
28+
ref
29+
) => {
30+
const isDisabled = disabled || loading
31+
32+
return (
33+
<button
34+
ref={ref}
35+
className={`${classes.button} ${classes[variant]} ${classes[size]} ${className || ''}`}
36+
disabled={isDisabled}
37+
{...props}
38+
>
39+
{loading && <span className={classes.spinner} />}
40+
{children}
41+
</button>
42+
)
43+
}
44+
)
45+
46+
Button.displayName = 'Button'

src/components/Button/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { Button } from './Button'
2+
export type { ButtonProps, ButtonVariant, ButtonSize } from './Button'
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
.fieldset {
2+
display: flex;
3+
flex-direction: row;
4+
align-items: center;
5+
gap: 10px;
6+
border: none;
7+
margin: 0;
8+
padding: 0;
9+
}
10+
11+
.checkboxContainer {
12+
display: flex;
13+
align-items: center;
14+
gap: 10px;
15+
cursor: pointer;
16+
}
17+
18+
.checkbox {
19+
width: 18px;
20+
height: 18px;
21+
margin: 0;
22+
cursor: pointer;
23+
accent-color: #007bff;
24+
border: 2px solid #ccc;
25+
border-radius: 3px;
26+
background-color: #f5f5f5;
27+
transition: all 0.2s ease;
28+
flex-shrink: 0;
29+
margin-top: 2px;
30+
}
31+
32+
.checkbox:focus {
33+
outline: none;
34+
border-color: #007bff;
35+
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
36+
}
37+
38+
.checkbox:checked {
39+
background-color: #007bff;
40+
border-color: #007bff;
41+
}
42+
43+
.checkbox:disabled {
44+
background-color: #e9ecef;
45+
border-color: #ccc;
46+
cursor: not-allowed;
47+
opacity: 0.6;
48+
}
49+
50+
.checkbox.error {
51+
border-color: #ff4d4f;
52+
}
53+
54+
.checkbox.error:focus {
55+
border-color: #ff4d4f;
56+
box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.25);
57+
}
58+
59+
.label {
60+
font-size: 16px;
61+
font-family: sans-serif;
62+
color: #333;
63+
font-weight: 500;
64+
cursor: pointer;
65+
line-height: 1.4;
66+
user-select: none;
67+
margin: 0;
68+
padding: 0;
69+
}
70+
71+
.required {
72+
color: #ff4d4f;
73+
margin-left: 4px;
74+
}
75+
76+
.errorMessage {
77+
color: #ff4d4f;
78+
font-size: 14px;
79+
font-family: sans-serif;
80+
margin-left: 10px;
81+
}
82+
83+
.helperText {
84+
color: #6c757d;
85+
font-size: 14px;
86+
font-family: sans-serif;
87+
margin-left: 10px;
88+
}
89+
90+
/* Responsive design */
91+
@media (max-width: 768px) {
92+
.checkbox {
93+
width: 20px;
94+
height: 20px;
95+
}
96+
97+
.label {
98+
font-size: 16px; /* Prevents zoom on iOS */
99+
}
100+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use client'
2+
3+
import React from 'react'
4+
import classes from './Checkbox.module.css'
5+
6+
export interface CheckboxProps
7+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
8+
label: string
9+
error?: string
10+
helperText?: string
11+
required?: boolean
12+
}
13+
14+
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
15+
(
16+
{ label, error, helperText, required = false, id, className, ...props },
17+
ref
18+
) => {
19+
const checkboxId =
20+
id || `checkbox-${Math.random().toString(36).substr(2, 9)}`
21+
const errorId = `${checkboxId}-error`
22+
const helperId = `${checkboxId}-helper`
23+
24+
return (
25+
<fieldset className={classes.fieldset}>
26+
<div className={classes.checkboxContainer}>
27+
<input
28+
ref={ref}
29+
id={checkboxId}
30+
type="checkbox"
31+
className={`${classes.checkbox} ${error ? classes.error : ''} ${className || ''}`}
32+
aria-invalid={error ? 'true' : 'false'}
33+
aria-describedby={
34+
error ? errorId : helperText ? helperId : undefined
35+
}
36+
required={required}
37+
{...props}
38+
/>
39+
<label htmlFor={checkboxId} className={classes.label}>
40+
{label}
41+
{required && <span className={classes.required}>*</span>}
42+
</label>
43+
</div>
44+
{error && (
45+
<span id={errorId} className={classes.errorMessage} role="alert">
46+
{error}
47+
</span>
48+
)}
49+
{helperText && !error && (
50+
<span id={helperId} className={classes.helperText}>
51+
{helperText}
52+
</span>
53+
)}
54+
</fieldset>
55+
)
56+
}
57+
)
58+
59+
Checkbox.displayName = 'Checkbox'

src/components/Checkbox/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { Checkbox } from './Checkbox'
2+
export type { CheckboxProps } from './Checkbox'

0 commit comments

Comments
 (0)