@hellboy/ds 0.1.2
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/README.md +111 -0
- package/dist/index.css +3699 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +1087 -0
- package/dist/index.d.ts +1087 -0
- package/dist/index.js +3391 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3287 -0
- package/dist/index.mjs.map +1 -0
- package/dist/theme.css +55 -0
- package/hellboy-ds-0.1.2.tgz +0 -0
- package/package.json +42 -0
- package/src/components/badge/Badge.tsx +29 -0
- package/src/components/badge/index.ts +1 -0
- package/src/components/banner/Banner.tsx +48 -0
- package/src/components/banner/banner.css +44 -0
- package/src/components/banner/index.ts +1 -0
- package/src/components/button/button.tsx +127 -0
- package/src/components/button/index.ts +1 -0
- package/src/components/card/card.tsx +57 -0
- package/src/components/card/index.ts +1 -0
- package/src/components/checkbox/Checkbox.tsx +98 -0
- package/src/components/checkbox/index.ts +1 -0
- package/src/components/code-block/code-block.tsx +44 -0
- package/src/components/code-block/index.ts +1 -0
- package/src/components/color-control/color-control.tsx +322 -0
- package/src/components/color-control/index.ts +1 -0
- package/src/components/drag-handle/DragHandle.tsx +78 -0
- package/src/components/drag-handle/index.ts +1 -0
- package/src/components/drawer/drawer.tsx +82 -0
- package/src/components/drawer/index.ts +1 -0
- package/src/components/floating-bar/floating-bar.tsx +52 -0
- package/src/components/floating-bar/index.ts +2 -0
- package/src/components/footer/footer.tsx +28 -0
- package/src/components/footer/index.ts +1 -0
- package/src/components/grid/Grid.tsx +53 -0
- package/src/components/grid/index.ts +1 -0
- package/src/components/header/header.tsx +57 -0
- package/src/components/header/index.ts +1 -0
- package/src/components/icons/icons.tsx +44 -0
- package/src/components/icons/index.ts +1 -0
- package/src/components/index.ts +29 -0
- package/src/components/input/DatePicker.tsx +133 -0
- package/src/components/input/Input.tsx +220 -0
- package/src/components/input/InputDate.tsx +10 -0
- package/src/components/input/InputDateTime.tsx +10 -0
- package/src/components/input/InputEmail.tsx +10 -0
- package/src/components/input/InputField.tsx +137 -0
- package/src/components/input/InputNumber.tsx +10 -0
- package/src/components/input/InputPassword.tsx +10 -0
- package/src/components/input/InputSearch.tsx +10 -0
- package/src/components/input/InputTel.tsx +10 -0
- package/src/components/input/InputText.tsx +10 -0
- package/src/components/input/InputTime.tsx +10 -0
- package/src/components/input/InputUrl.tsx +10 -0
- package/src/components/input/TimePicker.tsx +151 -0
- package/src/components/input/index.ts +11 -0
- package/src/components/layout/Layout.tsx +244 -0
- package/src/components/layout/index.ts +1 -0
- package/src/components/list/List.tsx +159 -0
- package/src/components/list/index.ts +1 -0
- package/src/components/navbar/MenuCategory.tsx +20 -0
- package/src/components/navbar/MenuGroup.tsx +288 -0
- package/src/components/navbar/MenuItem.tsx +65 -0
- package/src/components/navbar/Navbar.tsx +23 -0
- package/src/components/navbar/index.ts +4 -0
- package/src/components/page/index.ts +1 -0
- package/src/components/page/page.tsx +46 -0
- package/src/components/page-index/PageIndex.tsx +275 -0
- package/src/components/page-index/index.ts +1 -0
- package/src/components/popover/index.ts +1 -0
- package/src/components/popover/popover.tsx +199 -0
- package/src/components/radio/Radio.tsx +176 -0
- package/src/components/radio/index.ts +1 -0
- package/src/components/section/index.ts +1 -0
- package/src/components/section/section.tsx +66 -0
- package/src/components/select/Select.tsx +212 -0
- package/src/components/select/index.ts +1 -0
- package/src/components/slider/Slider.tsx +267 -0
- package/src/components/slider/index.ts +1 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/switch.tsx +99 -0
- package/src/components/table/Table.tsx +147 -0
- package/src/components/table/index.ts +1 -0
- package/src/components/theme-control/index.ts +1 -0
- package/src/components/theme-control/theme-control.tsx +78 -0
- package/src/components/tooltip/index.ts +1 -0
- package/src/components/tooltip/tooltip.tsx +207 -0
- package/src/contexts/NavbarTooltipContext.tsx +48 -0
- package/src/contexts/index.ts +1 -0
- package/src/foundations/motion.md +136 -0
- package/src/index.ts +40 -0
- package/src/style/_shared/field.css +69 -0
- package/src/style/components/badge/badge.css +74 -0
- package/src/style/components/button/button.css +244 -0
- package/src/style/components/card/card.css +69 -0
- package/src/style/components/checkbox.css +142 -0
- package/src/style/components/code-block/code-block.css +34 -0
- package/src/style/components/color-control/color-control.css +126 -0
- package/src/style/components/drag-handle/drag-handle.css +68 -0
- package/src/style/components/drawer/drawer.css +210 -0
- package/src/style/components/floating-bar/floating-bar.css +39 -0
- package/src/style/components/footer/footer.css +108 -0
- package/src/style/components/grid/grid.css +33 -0
- package/src/style/components/header/header.css +44 -0
- package/src/style/components/icons/icons.css +44 -0
- package/src/style/components/input/input.css +393 -0
- package/src/style/components/layout/layout.css +205 -0
- package/src/style/components/list/list.css +140 -0
- package/src/style/components/navbar/navbar.css +342 -0
- package/src/style/components/page/page.css +46 -0
- package/src/style/components/page-index/page-index.css +158 -0
- package/src/style/components/popover/popover.css +44 -0
- package/src/style/components/radio.css +178 -0
- package/src/style/components/section/section.css +67 -0
- package/src/style/components/select/select.css +143 -0
- package/src/style/components/slider/slider.css +159 -0
- package/src/style/components/switch/switch.css +267 -0
- package/src/style/components/table/table.css +108 -0
- package/src/style/components/theme-control/theme-control.css +35 -0
- package/src/style/components/tooltip/tooltip.css +52 -0
- package/src/style/foundations/global.css +316 -0
- package/src/style/foundations/motion.css +164 -0
- package/src/style/foundations/spacing.css +51 -0
- package/src/style/foundations/typography.css +39 -0
- package/src/style/foundations/z-index.css +81 -0
- package/src/style/modes/dark.css +146 -0
- package/src/style/modes/light.css +147 -0
- package/src/style/semantic.css +52 -0
- package/src/style/styles.css +51 -0
- package/src/style/themes/theme.json +37 -0
- package/src/utils/README.md +305 -0
- package/src/utils/USER_PREFERENCES.md +558 -0
- package/src/utils/theme.ts +127 -0
- package/src/utils/user-preferences.ts +577 -0
- package/tsconfig.json +25 -0
- package/tsup.config.ts +52 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Header, type HeaderProps } from './header';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Icon as IconifyIcon } from '@iconify/react';
|
|
3
|
+
import '../../style/components/icons/icons.css';
|
|
4
|
+
|
|
5
|
+
export type IconName = string; // Allow any string for famicons icons
|
|
6
|
+
|
|
7
|
+
export interface IconProps {
|
|
8
|
+
name: IconName;
|
|
9
|
+
size?: number;
|
|
10
|
+
className?: string;
|
|
11
|
+
/** Render a skeleton placeholder instead of the real icon (useful while downloading/loading) */
|
|
12
|
+
loading?: boolean;
|
|
13
|
+
/** aria-label for icon-only buttons etc. */
|
|
14
|
+
'aria-label'?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const Icon: React.FC<IconProps> = ({ name, size = 24, className, loading = false, ...rest }) => {
|
|
18
|
+
// If name already has famicons: prefix, use it as-is, otherwise add the prefix
|
|
19
|
+
const iconName = name.startsWith('famicons:') ? name : `famicons:${name}`;
|
|
20
|
+
|
|
21
|
+
const classes = ['icon', className].filter(Boolean).join(' ');
|
|
22
|
+
|
|
23
|
+
if (loading) {
|
|
24
|
+
const style: React.CSSProperties = { width: size, height: size };
|
|
25
|
+
return (
|
|
26
|
+
<span
|
|
27
|
+
className={`${classes} icon--skeleton`}
|
|
28
|
+
style={style}
|
|
29
|
+
aria-hidden={rest['aria-label'] ? 'false' : 'true'}
|
|
30
|
+
{...(rest['aria-label'] ? { 'aria-label': rest['aria-label'] } : {})}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<IconifyIcon
|
|
37
|
+
icon={iconName}
|
|
38
|
+
width={size}
|
|
39
|
+
height={size}
|
|
40
|
+
className={classes}
|
|
41
|
+
{...rest}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Icon, type IconProps, type IconName } from './icons';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design System Components
|
|
3
|
+
* Export all available components
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * from './button';
|
|
7
|
+
export * from './badge';
|
|
8
|
+
export * from './checkbox';
|
|
9
|
+
export * from './switch';
|
|
10
|
+
export * from './layout';
|
|
11
|
+
export * from './page';
|
|
12
|
+
export * from './header';
|
|
13
|
+
export * from './section';
|
|
14
|
+
export * from './code-block';
|
|
15
|
+
export * from './navbar';
|
|
16
|
+
export * from './icons';
|
|
17
|
+
export * from './theme-control';
|
|
18
|
+
export * from './card';
|
|
19
|
+
export * from './color-control';
|
|
20
|
+
export * from './grid';
|
|
21
|
+
export * from './slider';
|
|
22
|
+
export * from './drag-handle';
|
|
23
|
+
export * from './footer';
|
|
24
|
+
export * from './input';
|
|
25
|
+
export * from './list';
|
|
26
|
+
export * from './radio';
|
|
27
|
+
export * from './select';
|
|
28
|
+
export * from './banner';
|
|
29
|
+
export * from './floating-bar';
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
interface DatePickerProps {
|
|
4
|
+
value: string;
|
|
5
|
+
onChange: (value: string) => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const MONTHS = [
|
|
10
|
+
'January', 'February', 'March', 'April', 'May', 'June',
|
|
11
|
+
'July', 'August', 'September', 'October', 'November', 'December'
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const DAYS_OF_WEEK = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
15
|
+
|
|
16
|
+
export const DatePicker: React.FC<DatePickerProps> = ({ value, onChange, disabled }) => {
|
|
17
|
+
const parseDate = (dateStr: string) => {
|
|
18
|
+
if (!dateStr) {
|
|
19
|
+
const today = new Date();
|
|
20
|
+
return { year: today.getFullYear(), month: today.getMonth(), day: today.getDate() };
|
|
21
|
+
}
|
|
22
|
+
const [year, month, day] = dateStr.split('-').map(Number);
|
|
23
|
+
return { year, month: month - 1, day };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const { year: initialYear, month: initialMonth, day: selectedDay } = parseDate(value);
|
|
27
|
+
const [currentYear, setCurrentYear] = React.useState(initialYear);
|
|
28
|
+
const [currentMonth, setCurrentMonth] = React.useState(initialMonth);
|
|
29
|
+
|
|
30
|
+
const getDaysInMonth = (year: number, month: number) => {
|
|
31
|
+
return new Date(year, month + 1, 0).getDate();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const getFirstDayOfMonth = (year: number, month: number) => {
|
|
35
|
+
return new Date(year, month, 1).getDay();
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const handleDateSelect = (day: number) => {
|
|
39
|
+
if (disabled) return;
|
|
40
|
+
const formattedDate = `${currentYear}-${String(currentMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
|
41
|
+
onChange(formattedDate);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handlePrevMonth = () => {
|
|
45
|
+
if (currentMonth === 0) {
|
|
46
|
+
setCurrentMonth(11);
|
|
47
|
+
setCurrentYear(currentYear - 1);
|
|
48
|
+
} else {
|
|
49
|
+
setCurrentMonth(currentMonth - 1);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleNextMonth = () => {
|
|
54
|
+
if (currentMonth === 11) {
|
|
55
|
+
setCurrentMonth(0);
|
|
56
|
+
setCurrentYear(currentYear + 1);
|
|
57
|
+
} else {
|
|
58
|
+
setCurrentMonth(currentMonth + 1);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const daysInMonth = getDaysInMonth(currentYear, currentMonth);
|
|
63
|
+
const firstDay = getFirstDayOfMonth(currentYear, currentMonth);
|
|
64
|
+
const days: (number | null)[] = [
|
|
65
|
+
...Array(firstDay).fill(null),
|
|
66
|
+
...Array.from({ length: daysInMonth }, (_, i) => i + 1)
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className="date-picker">
|
|
71
|
+
<div className="date-picker__header">
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
className="date-picker__nav"
|
|
75
|
+
onClick={handlePrevMonth}
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
aria-label="Previous month"
|
|
78
|
+
>
|
|
79
|
+
‹
|
|
80
|
+
</button>
|
|
81
|
+
<div className="date-picker__title">
|
|
82
|
+
<select
|
|
83
|
+
className="date-picker__select"
|
|
84
|
+
value={currentMonth}
|
|
85
|
+
onChange={(e) => setCurrentMonth(Number(e.target.value))}
|
|
86
|
+
disabled={disabled}
|
|
87
|
+
>
|
|
88
|
+
{MONTHS.map((month, idx) => (
|
|
89
|
+
<option key={month} value={idx}>{month}</option>
|
|
90
|
+
))}
|
|
91
|
+
</select>
|
|
92
|
+
<select
|
|
93
|
+
className="date-picker__select"
|
|
94
|
+
value={currentYear}
|
|
95
|
+
onChange={(e) => setCurrentYear(Number(e.target.value))}
|
|
96
|
+
disabled={disabled}
|
|
97
|
+
>
|
|
98
|
+
{Array.from({ length: 100 }, (_, i) => currentYear - 50 + i).map(year => (
|
|
99
|
+
<option key={year} value={year}>{year}</option>
|
|
100
|
+
))}
|
|
101
|
+
</select>
|
|
102
|
+
</div>
|
|
103
|
+
<button
|
|
104
|
+
type="button"
|
|
105
|
+
className="date-picker__nav"
|
|
106
|
+
onClick={handleNextMonth}
|
|
107
|
+
disabled={disabled}
|
|
108
|
+
aria-label="Next month"
|
|
109
|
+
>
|
|
110
|
+
›
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
113
|
+
<div className="date-picker__weekdays">
|
|
114
|
+
{DAYS_OF_WEEK.map(day => (
|
|
115
|
+
<div key={day} className="date-picker__weekday">{day}</div>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
<div className="date-picker__days">
|
|
119
|
+
{days.map((day, idx) => (
|
|
120
|
+
<button
|
|
121
|
+
key={idx}
|
|
122
|
+
type="button"
|
|
123
|
+
className={`date-picker__day ${day === selectedDay ? 'date-picker__day--selected' : ''} ${!day ? 'date-picker__day--empty' : ''}`}
|
|
124
|
+
onClick={() => day && handleDateSelect(day)}
|
|
125
|
+
disabled={!day || disabled}
|
|
126
|
+
>
|
|
127
|
+
{day || ''}
|
|
128
|
+
</button>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { InputField, InputFieldProps } from './InputField';
|
|
3
|
+
import { Popover } from '../popover/popover';
|
|
4
|
+
import { DatePicker } from './DatePicker';
|
|
5
|
+
import { TimePicker } from './TimePicker';
|
|
6
|
+
import '../../style/components/input/input.css';
|
|
7
|
+
|
|
8
|
+
export type InputType = 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search' | 'date' | 'time' | 'datetime-local';
|
|
9
|
+
|
|
10
|
+
export interface InputProps extends Omit<InputFieldProps, 'type' | 'iconRight' | 'onRightIconClick'> {
|
|
11
|
+
/**
|
|
12
|
+
* Input type with extended support for date/time pickers
|
|
13
|
+
* @default 'text'
|
|
14
|
+
*/
|
|
15
|
+
type?: InputType;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Show milliseconds in time picker (only for time type)
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
showMilliseconds?: boolean;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Minute step in time picker (only for time type)
|
|
25
|
+
* @default 1
|
|
26
|
+
*/
|
|
27
|
+
minuteStep?: 1 | 5 | 15 | 30;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Show clear button for search inputs when they have value
|
|
31
|
+
* @default true
|
|
32
|
+
*/
|
|
33
|
+
showClear?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const TYPE_ICON_MAP: Record<string, string> = {
|
|
37
|
+
email: 'mail',
|
|
38
|
+
password: 'lock-closed',
|
|
39
|
+
search: 'search',
|
|
40
|
+
date: 'calendar',
|
|
41
|
+
time: 'time',
|
|
42
|
+
'datetime-local': 'calendar',
|
|
43
|
+
tel: 'call',
|
|
44
|
+
url: 'link',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const formatDisplayValue = (val: string, inputType: InputType) => {
|
|
48
|
+
if (!val) return '';
|
|
49
|
+
if (inputType === 'date') {
|
|
50
|
+
const [year, month, day] = val.split('-');
|
|
51
|
+
return `${day}/${month}/${year}`;
|
|
52
|
+
}
|
|
53
|
+
if (inputType === 'time') {
|
|
54
|
+
return val; // Já vem formatado como HH:MM:SS
|
|
55
|
+
}
|
|
56
|
+
if (inputType === 'datetime-local') {
|
|
57
|
+
const [datePart, timePart] = val.split('T');
|
|
58
|
+
if (datePart && timePart) {
|
|
59
|
+
const [year, month, day] = datePart.split('-');
|
|
60
|
+
return `${day}/${month}/${year} ${timePart}`;
|
|
61
|
+
}
|
|
62
|
+
return val;
|
|
63
|
+
}
|
|
64
|
+
return val;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
68
|
+
(
|
|
69
|
+
{
|
|
70
|
+
type = 'text',
|
|
71
|
+
label,
|
|
72
|
+
helperText,
|
|
73
|
+
error,
|
|
74
|
+
icon,
|
|
75
|
+
fullWidth = false,
|
|
76
|
+
size = 'md',
|
|
77
|
+
className = '',
|
|
78
|
+
disabled,
|
|
79
|
+
id,
|
|
80
|
+
showMilliseconds = false,
|
|
81
|
+
minuteStep = 1,
|
|
82
|
+
showClear = true,
|
|
83
|
+
...props
|
|
84
|
+
},
|
|
85
|
+
ref
|
|
86
|
+
) => {
|
|
87
|
+
const [showPassword, setShowPassword] = React.useState(false);
|
|
88
|
+
const [showDateTimePicker, setShowDateTimePicker] = React.useState(false);
|
|
89
|
+
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
90
|
+
|
|
91
|
+
// Determine icons
|
|
92
|
+
const leftIcon = icon || TYPE_ICON_MAP[type];
|
|
93
|
+
const isPasswordType = type === 'password';
|
|
94
|
+
const actualType = isPasswordType && showPassword ? 'text' : (type !== 'date' && type !== 'time' && type !== 'datetime-local' ? type : 'text');
|
|
95
|
+
|
|
96
|
+
// Right icon - either custom, password toggle, date/time picker, clear button, or type-based
|
|
97
|
+
const isDateTimeType = type === 'date' || type === 'time' || type === 'datetime-local';
|
|
98
|
+
const isSearchType = type === 'search';
|
|
99
|
+
const hasValue = props.value && String(props.value).length > 0;
|
|
100
|
+
|
|
101
|
+
const rightIconName = isPasswordType
|
|
102
|
+
? showPassword
|
|
103
|
+
? 'eye-off'
|
|
104
|
+
: 'eye'
|
|
105
|
+
: isDateTimeType
|
|
106
|
+
? 'chevron-down'
|
|
107
|
+
: isSearchType && hasValue && showClear
|
|
108
|
+
? 'x'
|
|
109
|
+
: undefined;
|
|
110
|
+
|
|
111
|
+
const handleRightIconClick = () => {
|
|
112
|
+
if (isPasswordType) {
|
|
113
|
+
setShowPassword(!showPassword);
|
|
114
|
+
} else if (isDateTimeType) {
|
|
115
|
+
setShowDateTimePicker(!showDateTimePicker);
|
|
116
|
+
} else if (isSearchType && hasValue && showClear) {
|
|
117
|
+
if (props.onChange) {
|
|
118
|
+
props.onChange({ target: { value: '' } } as any);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const handleDateTimeChange = (newValue: string) => {
|
|
124
|
+
if (props.onChange) {
|
|
125
|
+
props.onChange({ target: { value: newValue } } as any);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (!isDateTimeType) {
|
|
130
|
+
return (
|
|
131
|
+
<InputField
|
|
132
|
+
ref={ref || inputRef}
|
|
133
|
+
type={actualType}
|
|
134
|
+
label={label}
|
|
135
|
+
helperText={helperText}
|
|
136
|
+
error={error}
|
|
137
|
+
icon={leftIcon}
|
|
138
|
+
iconRight={rightIconName}
|
|
139
|
+
onRightIconClick={handleRightIconClick}
|
|
140
|
+
fullWidth={fullWidth}
|
|
141
|
+
size={size}
|
|
142
|
+
className={className}
|
|
143
|
+
disabled={disabled}
|
|
144
|
+
id={id}
|
|
145
|
+
{...props}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Date/Time picker rendering
|
|
151
|
+
return (
|
|
152
|
+
<div className={`input-container ${fullWidth ? 'input-container--full-width' : ''} ${className}`.trim()}>
|
|
153
|
+
{label && (
|
|
154
|
+
<label htmlFor={id} className="input__label">
|
|
155
|
+
{label}
|
|
156
|
+
</label>
|
|
157
|
+
)}
|
|
158
|
+
<Popover
|
|
159
|
+
trigger={
|
|
160
|
+
<InputField
|
|
161
|
+
ref={ref || inputRef}
|
|
162
|
+
type="text"
|
|
163
|
+
icon={leftIcon}
|
|
164
|
+
iconRight={rightIconName}
|
|
165
|
+
onRightIconClick={() => setShowDateTimePicker(!showDateTimePicker)}
|
|
166
|
+
fullWidth={fullWidth}
|
|
167
|
+
size={size}
|
|
168
|
+
disabled={disabled}
|
|
169
|
+
id={id}
|
|
170
|
+
value={formatDisplayValue(String(props.value || ''), type)}
|
|
171
|
+
placeholder={props.placeholder}
|
|
172
|
+
readOnly
|
|
173
|
+
/>
|
|
174
|
+
}
|
|
175
|
+
isOpen={showDateTimePicker}
|
|
176
|
+
onToggle={() => setShowDateTimePicker(!showDateTimePicker)}
|
|
177
|
+
placement="bottom"
|
|
178
|
+
>
|
|
179
|
+
<div style={{ padding: '1rem' }}>
|
|
180
|
+
{(type === 'date' || type === 'datetime-local') && (
|
|
181
|
+
<DatePicker
|
|
182
|
+
value={(String(props.value || '')).split('T')[0]}
|
|
183
|
+
onChange={(newDate) => {
|
|
184
|
+
if (type === 'datetime-local') {
|
|
185
|
+
const time = (String(props.value || '')).split('T')[1] || '00:00:00';
|
|
186
|
+
handleDateTimeChange(`${newDate}T${time}`);
|
|
187
|
+
} else {
|
|
188
|
+
handleDateTimeChange(newDate);
|
|
189
|
+
setShowDateTimePicker(false);
|
|
190
|
+
}
|
|
191
|
+
}}
|
|
192
|
+
disabled={disabled}
|
|
193
|
+
/>
|
|
194
|
+
)}
|
|
195
|
+
{(type === 'time' || type === 'datetime-local') && (
|
|
196
|
+
<TimePicker
|
|
197
|
+
value={(String(props.value || '')).split('T')[1] || '00:00:00'}
|
|
198
|
+
onChange={(newTime) => {
|
|
199
|
+
if (type === 'datetime-local') {
|
|
200
|
+
const date = (String(props.value || '')).split('T')[0] || new Date().toISOString().split('T')[0];
|
|
201
|
+
handleDateTimeChange(`${date}T${newTime}`);
|
|
202
|
+
} else {
|
|
203
|
+
handleDateTimeChange(newTime);
|
|
204
|
+
}
|
|
205
|
+
}}
|
|
206
|
+
disabled={disabled}
|
|
207
|
+
showMilliseconds={showMilliseconds}
|
|
208
|
+
minuteStep={minuteStep}
|
|
209
|
+
/>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
</Popover>
|
|
213
|
+
{error && <p className="input__message input__message--error">{error}</p>}
|
|
214
|
+
{helperText && !error && <p className="input__message">{helperText}</p>}
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
Input.displayName = 'Input';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Input, InputProps } from './Input';
|
|
3
|
+
|
|
4
|
+
export interface InputDateProps extends Omit<InputProps, 'type'> {}
|
|
5
|
+
|
|
6
|
+
export const InputDate = React.forwardRef<HTMLInputElement, InputDateProps>((props, ref) => {
|
|
7
|
+
return <Input ref={ref} {...props} type="date" />;
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
InputDate.displayName = 'InputDate';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Input, InputProps } from './Input';
|
|
3
|
+
|
|
4
|
+
export interface InputDateTimeProps extends Omit<InputProps, 'type'> {}
|
|
5
|
+
|
|
6
|
+
export const InputDateTime = React.forwardRef<HTMLInputElement, InputDateTimeProps>((props, ref) => {
|
|
7
|
+
return <Input ref={ref} {...props} type="datetime-local" />;
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
InputDateTime.displayName = 'InputDateTime';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Input, InputProps } from './Input';
|
|
3
|
+
|
|
4
|
+
export interface InputEmailProps extends Omit<InputProps, 'type'> {}
|
|
5
|
+
|
|
6
|
+
export const InputEmail = React.forwardRef<HTMLInputElement, InputEmailProps>((props, ref) => {
|
|
7
|
+
return <Input ref={ref} {...props} type="email" />;
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
InputEmail.displayName = 'InputEmail';
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Icon } from '../icons/icons';
|
|
3
|
+
import '../../style/components/input/input.css';
|
|
4
|
+
|
|
5
|
+
export interface InputFieldProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'type'> {
|
|
6
|
+
/**
|
|
7
|
+
* Input native type (text, email, password, etc.)
|
|
8
|
+
*/
|
|
9
|
+
type?: string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Label text
|
|
13
|
+
*/
|
|
14
|
+
label?: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Helper text shown below the input
|
|
18
|
+
*/
|
|
19
|
+
helperText?: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Error message - when present, input is in error state
|
|
23
|
+
*/
|
|
24
|
+
error?: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Icon name from our icon library (shown on the left)
|
|
28
|
+
*/
|
|
29
|
+
icon?: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Icon name for the right side
|
|
33
|
+
*/
|
|
34
|
+
iconRight?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Full width input
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
fullWidth?: boolean;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Size variant
|
|
44
|
+
* @default 'md'
|
|
45
|
+
*/
|
|
46
|
+
size?: 'sm' | 'md' | 'lg';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Custom class name
|
|
50
|
+
*/
|
|
51
|
+
className?: string;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Right icon click handler
|
|
55
|
+
*/
|
|
56
|
+
onRightIconClick?: () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(
|
|
60
|
+
(
|
|
61
|
+
{
|
|
62
|
+
type = 'text',
|
|
63
|
+
label,
|
|
64
|
+
helperText,
|
|
65
|
+
error,
|
|
66
|
+
icon,
|
|
67
|
+
iconRight,
|
|
68
|
+
fullWidth = false,
|
|
69
|
+
size = 'md',
|
|
70
|
+
className = '',
|
|
71
|
+
disabled,
|
|
72
|
+
id,
|
|
73
|
+
onRightIconClick,
|
|
74
|
+
...props
|
|
75
|
+
},
|
|
76
|
+
ref
|
|
77
|
+
) => {
|
|
78
|
+
const inputId = id || `input-${React.useId()}`;
|
|
79
|
+
|
|
80
|
+
const containerClasses = [
|
|
81
|
+
'input-container',
|
|
82
|
+
fullWidth && 'input-container--full-width',
|
|
83
|
+
className,
|
|
84
|
+
]
|
|
85
|
+
.filter(Boolean)
|
|
86
|
+
.join(' ');
|
|
87
|
+
|
|
88
|
+
const inputClasses = [
|
|
89
|
+
'input',
|
|
90
|
+
`input--${size}`,
|
|
91
|
+
error && 'input--error',
|
|
92
|
+
disabled && 'input--disabled',
|
|
93
|
+
icon && 'input--has-icon-left',
|
|
94
|
+
iconRight && 'input--has-icon-right',
|
|
95
|
+
]
|
|
96
|
+
.filter(Boolean)
|
|
97
|
+
.join(' ');
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div className={containerClasses}>
|
|
101
|
+
{label && (
|
|
102
|
+
<label htmlFor={inputId} className="input__label">
|
|
103
|
+
{label}
|
|
104
|
+
</label>
|
|
105
|
+
)}
|
|
106
|
+
<div className="input__wrapper">
|
|
107
|
+
{icon && (
|
|
108
|
+
<span className="input__icon input__icon--left" aria-hidden="true">
|
|
109
|
+
<Icon name={icon as any} size={16} />
|
|
110
|
+
</span>
|
|
111
|
+
)}
|
|
112
|
+
<input
|
|
113
|
+
ref={ref}
|
|
114
|
+
type={type}
|
|
115
|
+
id={inputId}
|
|
116
|
+
className={inputClasses}
|
|
117
|
+
disabled={disabled}
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
{iconRight && (
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
className="input__icon input__icon--right"
|
|
124
|
+
onClick={onRightIconClick}
|
|
125
|
+
tabIndex={-1}
|
|
126
|
+
aria-label="Icon action"
|
|
127
|
+
>
|
|
128
|
+
<Icon name={iconRight as any} size={16} />
|
|
129
|
+
</button>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
{error && <p className="input__message input__message--error">{error}</p>}
|
|
133
|
+
{helperText && !error && <p className="input__message">{helperText}</p>}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Input, InputProps } from './Input';
|
|
3
|
+
|
|
4
|
+
export interface InputNumberProps extends Omit<InputProps, 'type'> {}
|
|
5
|
+
|
|
6
|
+
export const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props, ref) => {
|
|
7
|
+
return <Input ref={ref} {...props} type="number" />;
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
InputNumber.displayName = 'InputNumber';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Input, InputProps } from './Input';
|
|
3
|
+
|
|
4
|
+
export interface InputPasswordProps extends Omit<InputProps, 'type'> {}
|
|
5
|
+
|
|
6
|
+
export const InputPassword = React.forwardRef<HTMLInputElement, InputPasswordProps>((props, ref) => {
|
|
7
|
+
return <Input ref={ref} {...props} type="password" />;
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
InputPassword.displayName = 'InputPassword';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Input, InputProps } from './Input';
|
|
3
|
+
|
|
4
|
+
export interface InputSearchProps extends Omit<InputProps, 'type'> {}
|
|
5
|
+
|
|
6
|
+
export const InputSearch = React.forwardRef<HTMLInputElement, InputSearchProps>((props, ref) => {
|
|
7
|
+
return <Input ref={ref} {...props} type="search" />;
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
InputSearch.displayName = 'InputSearch';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Input, InputProps } from './Input';
|
|
3
|
+
|
|
4
|
+
export interface InputTelProps extends Omit<InputProps, 'type'> {}
|
|
5
|
+
|
|
6
|
+
export const InputTel = React.forwardRef<HTMLInputElement, InputTelProps>((props, ref) => {
|
|
7
|
+
return <Input ref={ref} {...props} type="tel" />;
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
InputTel.displayName = 'InputTel';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Input, InputProps } from './Input';
|
|
3
|
+
|
|
4
|
+
export interface InputTextProps extends Omit<InputProps, 'type'> {}
|
|
5
|
+
|
|
6
|
+
export const InputText = React.forwardRef<HTMLInputElement, InputTextProps>((props, ref) => {
|
|
7
|
+
return <Input ref={ref} {...props} type="text" />;
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
InputText.displayName = 'InputText';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Input, InputProps } from './Input';
|
|
3
|
+
|
|
4
|
+
export interface InputTimeProps extends Omit<InputProps, 'type'> {}
|
|
5
|
+
|
|
6
|
+
export const InputTime = React.forwardRef<HTMLInputElement, InputTimeProps>((props, ref) => {
|
|
7
|
+
return <Input ref={ref} {...props} type="time" />;
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
InputTime.displayName = 'InputTime';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Input, InputProps } from './Input';
|
|
3
|
+
|
|
4
|
+
export interface InputUrlProps extends Omit<InputProps, 'type'> {}
|
|
5
|
+
|
|
6
|
+
export const InputUrl = React.forwardRef<HTMLInputElement, InputUrlProps>((props, ref) => {
|
|
7
|
+
return <Input ref={ref} {...props} type="url" />;
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
InputUrl.displayName = 'InputUrl';
|