@udixio/ui-react 2.9.13 → 2.9.15
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/CHANGELOG.md +40 -0
- package/dist/index.cjs +3 -3
- package/dist/index.js +4348 -3781
- package/dist/lib/components/AnchorPositioner.d.ts +11 -0
- package/dist/lib/components/AnchorPositioner.d.ts.map +1 -0
- package/dist/lib/components/Button.d.ts.map +1 -1
- package/dist/lib/components/Card.d.ts +2 -2
- package/dist/lib/components/Card.d.ts.map +1 -1
- package/dist/lib/components/Checkbox.d.ts +15 -0
- package/dist/lib/components/Checkbox.d.ts.map +1 -0
- package/dist/lib/components/DatePicker.d.ts +9 -0
- package/dist/lib/components/DatePicker.d.ts.map +1 -0
- package/dist/lib/components/FabMenu.d.ts.map +1 -1
- package/dist/lib/components/IconButton.d.ts.map +1 -1
- package/dist/lib/components/TabGroup.d.ts +1 -0
- package/dist/lib/components/TabGroup.d.ts.map +1 -1
- package/dist/lib/components/TabGroupContext.d.ts +1 -0
- package/dist/lib/components/TabGroupContext.d.ts.map +1 -1
- package/dist/lib/components/TabPanel.d.ts +1 -0
- package/dist/lib/components/TabPanel.d.ts.map +1 -1
- package/dist/lib/components/TabPanels.d.ts +1 -0
- package/dist/lib/components/TabPanels.d.ts.map +1 -1
- package/dist/lib/components/TextField.d.ts +4 -5
- package/dist/lib/components/TextField.d.ts.map +1 -1
- package/dist/lib/components/Tooltip.d.ts +1 -1
- package/dist/lib/components/Tooltip.d.ts.map +1 -1
- package/dist/lib/components/index.d.ts +3 -0
- package/dist/lib/components/index.d.ts.map +1 -1
- package/dist/lib/effects/State.d.ts +3 -1
- package/dist/lib/effects/State.d.ts.map +1 -1
- package/dist/lib/effects/smooth-scroll.effect.d.ts +14 -0
- package/dist/lib/effects/smooth-scroll.effect.d.ts.map +1 -1
- package/dist/lib/hooks/index.d.ts +0 -1
- package/dist/lib/hooks/index.d.ts.map +1 -1
- package/dist/lib/interfaces/card.interface.d.ts +1 -1
- package/dist/lib/interfaces/card.interface.d.ts.map +1 -1
- package/dist/lib/interfaces/checkbox.interface.d.ts +38 -0
- package/dist/lib/interfaces/checkbox.interface.d.ts.map +1 -0
- package/dist/lib/interfaces/date-picker.interface.d.ts +67 -0
- package/dist/lib/interfaces/date-picker.interface.d.ts.map +1 -0
- package/dist/lib/interfaces/icon-button.interface.d.ts +2 -1
- package/dist/lib/interfaces/icon-button.interface.d.ts.map +1 -1
- package/dist/lib/interfaces/index.d.ts +1 -0
- package/dist/lib/interfaces/index.d.ts.map +1 -1
- package/dist/lib/interfaces/text-field.interface.d.ts +8 -5
- package/dist/lib/interfaces/text-field.interface.d.ts.map +1 -1
- package/dist/lib/interfaces/tooltip.interface.d.ts +2 -0
- package/dist/lib/interfaces/tooltip.interface.d.ts.map +1 -1
- package/dist/lib/styles/card.style.d.ts +5 -5
- package/dist/lib/styles/checkbox.style.d.ts +45 -0
- package/dist/lib/styles/checkbox.style.d.ts.map +1 -0
- package/dist/lib/styles/date-picker.style.d.ts +45 -0
- package/dist/lib/styles/date-picker.style.d.ts.map +1 -0
- package/dist/lib/styles/fab.style.d.ts +2 -2
- package/dist/lib/styles/icon-button.style.d.ts +10 -4
- package/dist/lib/styles/icon-button.style.d.ts.map +1 -1
- package/dist/lib/styles/index.d.ts +1 -0
- package/dist/lib/styles/index.d.ts.map +1 -1
- package/dist/lib/styles/navigation-rail-item.style.d.ts +2 -2
- package/dist/lib/styles/side-sheet.style.d.ts +2 -2
- package/dist/lib/styles/slider.style.d.ts +2 -2
- package/dist/lib/styles/tab.style.d.ts +2 -2
- package/dist/lib/styles/text-field.style.d.ts +22 -13
- package/dist/lib/styles/text-field.style.d.ts.map +1 -1
- package/dist/lib/styles/tooltip.style.d.ts +8 -4
- package/dist/lib/styles/tooltip.style.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/lib/components/AnchorPositioner.tsx +132 -0
- package/src/lib/components/Button.tsx +1 -0
- package/src/lib/components/Card.tsx +9 -4
- package/src/lib/components/Checkbox.tsx +120 -0
- package/src/lib/components/DatePicker.tsx +432 -0
- package/src/lib/components/FabMenu.tsx +4 -5
- package/src/lib/components/IconButton.tsx +9 -7
- package/src/lib/components/TabGroup.tsx +8 -6
- package/src/lib/components/TabGroupContext.tsx +1 -1
- package/src/lib/components/TabPanel.tsx +1 -0
- package/src/lib/components/TabPanels.tsx +1 -0
- package/src/lib/components/TextField.tsx +222 -123
- package/src/lib/components/Tooltip.tsx +13 -13
- package/src/lib/components/index.ts +3 -0
- package/src/lib/effects/State.tsx +4 -1
- package/src/lib/effects/smooth-scroll.effect.tsx +15 -1
- package/src/lib/hooks/index.ts +0 -1
- package/src/lib/interfaces/card.interface.ts +1 -1
- package/src/lib/interfaces/checkbox.interface.ts +39 -0
- package/src/lib/interfaces/date-picker.interface.ts +79 -0
- package/src/lib/interfaces/icon-button.interface.ts +2 -1
- package/src/lib/interfaces/index.ts +1 -0
- package/src/lib/interfaces/text-field.interface.ts +8 -5
- package/src/lib/interfaces/tooltip.interface.ts +2 -0
- package/src/lib/styles/checkbox.style.ts +64 -0
- package/src/lib/styles/date-picker.style.ts +43 -0
- package/src/lib/styles/index.ts +1 -0
- package/src/lib/styles/side-sheet.style.ts +2 -2
- package/src/lib/styles/text-field.style.ts +2 -2
- package/src/stories/containment/card.stories.tsx +1 -1
- package/dist/lib/hooks/useTooltipPosition.d.ts +0 -22
- package/dist/lib/hooks/useTooltipPosition.d.ts.map +0 -1
- package/src/lib/hooks/useTooltipPosition.ts +0 -95
|
@@ -1,24 +1,28 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
1
|
+
import React, { useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { Icon } from '../icon';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
faCalendarDays,
|
|
5
|
+
faCircleExclamation,
|
|
6
|
+
} from '@fortawesome/free-solid-svg-icons';
|
|
4
7
|
import { motion } from 'motion/react';
|
|
5
|
-
import {
|
|
8
|
+
import { DatePicker } from './DatePicker';
|
|
9
|
+
import { Button } from './Button';
|
|
6
10
|
|
|
7
11
|
import TextareaAutosize from 'react-textarea-autosize';
|
|
8
12
|
import { useTextFieldStyle } from '../styles/text-field.style';
|
|
9
13
|
import { classNames } from '../utils';
|
|
10
14
|
import { ReactProps } from '../utils/component';
|
|
11
|
-
import {
|
|
15
|
+
import { AnchorPositioner } from './AnchorPositioner';
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
18
|
* Text fields let users enter text into a UI
|
|
15
19
|
* @status beta
|
|
16
20
|
* @category Input
|
|
17
21
|
* @devx
|
|
18
|
-
* - `
|
|
19
|
-
* - `
|
|
22
|
+
* - Supports controlled (`value`) and uncontrolled (`defaultValue`) usage.
|
|
23
|
+
* - `multiline` switches to textarea mode.
|
|
20
24
|
* @a11y
|
|
21
|
-
* -
|
|
25
|
+
* - `aria-describedby` links supporting text/error to input.
|
|
22
26
|
*/
|
|
23
27
|
export const TextField = ({
|
|
24
28
|
variant = 'filled',
|
|
@@ -33,51 +37,40 @@ export const TextField = ({
|
|
|
33
37
|
trailingIcon,
|
|
34
38
|
leadingIcon,
|
|
35
39
|
type = 'text',
|
|
36
|
-
|
|
40
|
+
multiline = false,
|
|
37
41
|
autoComplete = 'on',
|
|
38
42
|
onChange,
|
|
39
|
-
value:
|
|
40
|
-
|
|
43
|
+
value: valueProp,
|
|
44
|
+
defaultValue,
|
|
45
|
+
showSupportingText,
|
|
46
|
+
id: idProp,
|
|
47
|
+
style,
|
|
48
|
+
ref,
|
|
41
49
|
...restProps
|
|
42
50
|
}: ReactProps<TextFieldInterface>) => {
|
|
43
|
-
const
|
|
51
|
+
const generatedId = useId();
|
|
52
|
+
const id = idProp || generatedId;
|
|
53
|
+
const helperTextId = `${id}-helper`;
|
|
54
|
+
|
|
55
|
+
const isControlled = valueProp !== undefined;
|
|
56
|
+
const [internalValue, setInternalValue] = useState(defaultValue ?? '');
|
|
57
|
+
const value = isControlled ? valueProp : internalValue;
|
|
58
|
+
|
|
44
59
|
const [isFocused, setIsFocused] = useState(false);
|
|
45
|
-
const [showErrorIcon, setShowErrorIcon] = useState(
|
|
46
|
-
const [showSupportingText, setShowSupportingText] = useState(
|
|
47
|
-
defaultShowSupportingText,
|
|
48
|
-
);
|
|
60
|
+
const [showErrorIcon, setShowErrorIcon] = useState(!!errorText?.length);
|
|
49
61
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}, [defaultValue]);
|
|
62
|
+
const internalRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null);
|
|
63
|
+
const inputRef = (ref as any) || internalRef;
|
|
53
64
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
setShowErrorIcon(true);
|
|
57
|
-
} else {
|
|
58
|
-
setShowErrorIcon(false);
|
|
59
|
-
}
|
|
60
|
-
}, [errorText]);
|
|
65
|
+
const textFieldRef = useRef<HTMLDivElement>(null);
|
|
66
|
+
const calendarTriggerRef = useRef<HTMLDivElement>(null);
|
|
61
67
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
setShowSupportingText(defaultShowSupportingText);
|
|
65
|
-
} else {
|
|
66
|
-
if (supportingText?.length) {
|
|
67
|
-
setShowSupportingText(true);
|
|
68
|
-
} else {
|
|
69
|
-
setShowSupportingText(false);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}, [showSupportingText, supportingText]);
|
|
68
|
+
const hasSupportingText =
|
|
69
|
+
showSupportingText ?? (!!errorText?.length || !!supportingText?.length);
|
|
73
70
|
|
|
74
71
|
useEffect(() => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
}, [isFocused]);
|
|
79
|
-
|
|
80
|
-
const inputRef = React.useRef<HTMLInputElement & HTMLTextAreaElement>(null);
|
|
72
|
+
setShowErrorIcon(!!errorText?.length);
|
|
73
|
+
}, [errorText]);
|
|
81
74
|
|
|
82
75
|
const focusInput = () => {
|
|
83
76
|
if (inputRef.current && !isFocused) {
|
|
@@ -87,28 +80,82 @@ export const TextField = ({
|
|
|
87
80
|
|
|
88
81
|
const handleOnFocus = () => {
|
|
89
82
|
setIsFocused(true);
|
|
83
|
+
setShowErrorIcon(false);
|
|
90
84
|
};
|
|
91
85
|
|
|
92
86
|
const handleChange = (
|
|
93
|
-
event: React.ChangeEvent<HTMLInputElement
|
|
87
|
+
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
|
94
88
|
) => {
|
|
95
89
|
const newValue = event.target.value;
|
|
96
|
-
|
|
90
|
+
|
|
91
|
+
if (!isControlled) {
|
|
92
|
+
setInternalValue(newValue);
|
|
93
|
+
}
|
|
97
94
|
|
|
98
95
|
setShowErrorIcon(false);
|
|
99
96
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
onChange(newValue);
|
|
97
|
+
if (onChange) {
|
|
98
|
+
onChange(event);
|
|
103
99
|
}
|
|
104
100
|
};
|
|
105
101
|
|
|
106
102
|
const handleBlur = () => {
|
|
107
103
|
setIsFocused(false);
|
|
104
|
+
if (errorText?.length) {
|
|
105
|
+
setShowErrorIcon(true);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Date Picker Logic
|
|
110
|
+
const isDateInput = type === 'date';
|
|
111
|
+
const [showDatePicker, setShowDatePicker] = useState(false);
|
|
112
|
+
const [tempDate, setTempDate] = useState<Date | null>(null);
|
|
113
|
+
|
|
114
|
+
const initialDateValue = useMemo(() => {
|
|
115
|
+
const val = String(value);
|
|
116
|
+
if (!val) return null;
|
|
117
|
+
const [y, m, d] = val.split('-').map(Number);
|
|
118
|
+
if (y && m && d) return new Date(y, m - 1, d);
|
|
119
|
+
return null;
|
|
120
|
+
}, [value]);
|
|
121
|
+
|
|
122
|
+
const handleDatePickerOpen = () => {
|
|
123
|
+
if (disabled) return;
|
|
124
|
+
setTempDate(initialDateValue);
|
|
125
|
+
setShowDatePicker(true);
|
|
108
126
|
};
|
|
109
127
|
|
|
128
|
+
const handleDateConfirm = () => {
|
|
129
|
+
const newValue = tempDate ? tempDate.toLocaleDateString('en-CA') : '';
|
|
130
|
+
|
|
131
|
+
if (!isControlled) {
|
|
132
|
+
setInternalValue(newValue);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (onChange) {
|
|
136
|
+
// Create a synthetic event
|
|
137
|
+
const event = {
|
|
138
|
+
target: {
|
|
139
|
+
value: newValue,
|
|
140
|
+
name,
|
|
141
|
+
type,
|
|
142
|
+
},
|
|
143
|
+
} as React.ChangeEvent<HTMLInputElement>;
|
|
144
|
+
onChange(event);
|
|
145
|
+
}
|
|
146
|
+
setShowDatePicker(false);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const effectiveTrailingIcon =
|
|
150
|
+
isDateInput && !trailingIcon ? faCalendarDays : trailingIcon;
|
|
151
|
+
|
|
152
|
+
// Enhance styles for date input
|
|
153
|
+
const inputDateClass = isDateInput
|
|
154
|
+
? '[&::-webkit-calendar-picker-indicator]:hidden cursor-pointer'
|
|
155
|
+
: '';
|
|
156
|
+
|
|
110
157
|
const styles = useTextFieldStyle({
|
|
111
|
-
showSupportingText,
|
|
158
|
+
showSupportingText: hasSupportingText,
|
|
112
159
|
isFocused,
|
|
113
160
|
showErrorIcon,
|
|
114
161
|
disabled,
|
|
@@ -121,37 +168,31 @@ export const TextField = ({
|
|
|
121
168
|
supportingText,
|
|
122
169
|
type,
|
|
123
170
|
leadingIcon,
|
|
124
|
-
trailingIcon,
|
|
171
|
+
trailingIcon: effectiveTrailingIcon,
|
|
125
172
|
variant,
|
|
126
173
|
errorText,
|
|
127
|
-
value,
|
|
174
|
+
value: String(value),
|
|
128
175
|
suffix,
|
|
129
|
-
|
|
176
|
+
multiline,
|
|
130
177
|
});
|
|
131
178
|
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
case 'textAreas':
|
|
142
|
-
TextComponent = 'textarea';
|
|
143
|
-
textComponentProps = {};
|
|
144
|
-
break;
|
|
145
|
-
case 'singleLine':
|
|
146
|
-
default:
|
|
147
|
-
TextComponent = 'input';
|
|
148
|
-
textComponentProps = { type: type };
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
179
|
+
const TextComponent = multiline ? TextareaAutosize : 'input';
|
|
180
|
+
const textComponentProps = multiline ? {} : { type };
|
|
181
|
+
|
|
182
|
+
const isFloating =
|
|
183
|
+
isFocused ||
|
|
184
|
+
(typeof value === 'string' && value.length > 0) ||
|
|
185
|
+
type == 'date'; // Float label when picker open
|
|
186
|
+
const showLegend = isFloating && variant === 'outlined';
|
|
187
|
+
const showLabel = !showLegend;
|
|
151
188
|
|
|
152
189
|
return (
|
|
153
|
-
<div className={styles.textField} {
|
|
154
|
-
<fieldset
|
|
190
|
+
<div ref={textFieldRef} className={styles.textField} style={style}>
|
|
191
|
+
<fieldset
|
|
192
|
+
onClick={focusInput}
|
|
193
|
+
className={styles.content}
|
|
194
|
+
role="presentation"
|
|
195
|
+
>
|
|
155
196
|
<div className={styles.stateLayer}></div>
|
|
156
197
|
{leadingIcon && (
|
|
157
198
|
<div className={styles.leadingIcon}>
|
|
@@ -163,68 +204,81 @@ export const TextField = ({
|
|
|
163
204
|
</div>
|
|
164
205
|
)}
|
|
165
206
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
</motion.span>
|
|
185
|
-
</span>
|
|
186
|
-
</motion.legend>
|
|
187
|
-
)}
|
|
207
|
+
<motion.legend
|
|
208
|
+
aria-hidden="true"
|
|
209
|
+
variants={{
|
|
210
|
+
hidden: { width: 0, padding: 0 },
|
|
211
|
+
visible: { width: 'auto', padding: '0 8px' },
|
|
212
|
+
}}
|
|
213
|
+
initial={showLegend ? 'visible' : 'hidden'}
|
|
214
|
+
animate={showLegend ? 'visible' : 'hidden'}
|
|
215
|
+
className={
|
|
216
|
+
'max-w-full ml-2 px-2 text-body-small h-0 overflow-hidden whitespace-nowrap'
|
|
217
|
+
}
|
|
218
|
+
transition={{ duration: 0.2 }}
|
|
219
|
+
>
|
|
220
|
+
<span className={'transform inline-flex -translate-y-1/2 opacity-0'}>
|
|
221
|
+
{label}
|
|
222
|
+
</span>
|
|
223
|
+
</motion.legend>
|
|
224
|
+
|
|
188
225
|
<div className={'flex-1 relative'}>
|
|
189
|
-
{
|
|
226
|
+
{showLabel && (
|
|
190
227
|
<motion.label
|
|
191
|
-
htmlFor={
|
|
228
|
+
htmlFor={id}
|
|
192
229
|
className={classNames(
|
|
193
|
-
'absolute left-4 transition-all duration-300',
|
|
230
|
+
'absolute left-4 transition-all duration-300 pointer-events-none',
|
|
194
231
|
{
|
|
195
|
-
'text-body-small top-2':
|
|
196
|
-
variant == 'filled' && !(!isFocused && !value.length),
|
|
232
|
+
'text-body-small top-2': variant == 'filled' && isFloating,
|
|
197
233
|
'text-body-large top-1/2 transform -translate-y-1/2': !(
|
|
198
|
-
variant == 'filled' &&
|
|
234
|
+
variant == 'filled' && isFloating
|
|
199
235
|
),
|
|
200
236
|
},
|
|
201
237
|
)}
|
|
202
238
|
transition={{ duration: 0.3 }}
|
|
239
|
+
layoutId={variant === 'outlined' ? `${id}-label` : undefined}
|
|
203
240
|
>
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
241
|
+
<span className={styles.label}>{label}</span>
|
|
242
|
+
</motion.label>
|
|
243
|
+
)}
|
|
244
|
+
|
|
245
|
+
{showLegend && (
|
|
246
|
+
<motion.label
|
|
247
|
+
htmlFor={id}
|
|
248
|
+
className={classNames(
|
|
249
|
+
'absolute left-2 -top-3 px-1 text-body-small z-10',
|
|
250
|
+
styles.label,
|
|
251
|
+
)}
|
|
252
|
+
layoutId={`${id}-label`}
|
|
253
|
+
transition={{ duration: 0.3 }}
|
|
254
|
+
>
|
|
255
|
+
{label}
|
|
211
256
|
</motion.label>
|
|
212
257
|
)}
|
|
258
|
+
|
|
213
259
|
<TextComponent
|
|
214
|
-
ref={inputRef}
|
|
260
|
+
ref={inputRef as any}
|
|
215
261
|
value={value}
|
|
216
262
|
onChange={handleChange}
|
|
217
|
-
className={styles.input}
|
|
218
|
-
id={
|
|
263
|
+
className={classNames(styles.input, inputDateClass)}
|
|
264
|
+
id={id}
|
|
219
265
|
name={name}
|
|
220
266
|
placeholder={isFocused ? (placeholder ?? undefined) : ''}
|
|
221
|
-
onFocus={
|
|
267
|
+
onFocus={(e) => {
|
|
268
|
+
handleOnFocus();
|
|
269
|
+
if (isDateInput) {
|
|
270
|
+
// Maybe open picker on focus? User preference.
|
|
271
|
+
// Often better on click/icon click.
|
|
272
|
+
// But let's stick to icon click for now or explicit open.
|
|
273
|
+
}
|
|
274
|
+
}}
|
|
222
275
|
onBlur={handleBlur}
|
|
223
276
|
disabled={disabled}
|
|
224
277
|
autoComplete={autoComplete}
|
|
225
278
|
aria-invalid={!!errorText?.length}
|
|
226
|
-
aria-
|
|
279
|
+
aria-describedby={hasSupportingText ? helperTextId : undefined}
|
|
227
280
|
{...textComponentProps}
|
|
281
|
+
{...(restProps as any)}
|
|
228
282
|
/>
|
|
229
283
|
</div>
|
|
230
284
|
|
|
@@ -232,21 +286,31 @@ export const TextField = ({
|
|
|
232
286
|
|
|
233
287
|
{!showErrorIcon && (
|
|
234
288
|
<>
|
|
235
|
-
{
|
|
289
|
+
{effectiveTrailingIcon && (
|
|
236
290
|
<div
|
|
291
|
+
ref={isDateInput ? calendarTriggerRef : undefined}
|
|
237
292
|
onClick={(event) => {
|
|
238
293
|
event.stopPropagation();
|
|
294
|
+
if (isDateInput) handleDatePickerOpen();
|
|
239
295
|
}}
|
|
240
|
-
className={
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
trailingIcon
|
|
244
|
-
) : (
|
|
245
|
-
<Icon className={'h-5'} icon={trailingIcon}></Icon>
|
|
296
|
+
className={classNames(
|
|
297
|
+
styles.trailingIcon,
|
|
298
|
+
isDateInput && 'cursor-pointer',
|
|
246
299
|
)}
|
|
300
|
+
>
|
|
301
|
+
<div className="flex items-center justify-center w-full h-full">
|
|
302
|
+
{React.isValidElement(effectiveTrailingIcon) ? (
|
|
303
|
+
effectiveTrailingIcon
|
|
304
|
+
) : (
|
|
305
|
+
<Icon
|
|
306
|
+
className={'h-5'}
|
|
307
|
+
icon={effectiveTrailingIcon as any}
|
|
308
|
+
/>
|
|
309
|
+
)}
|
|
310
|
+
</div>
|
|
247
311
|
</div>
|
|
248
312
|
)}
|
|
249
|
-
{!
|
|
313
|
+
{!effectiveTrailingIcon && suffix && (
|
|
250
314
|
<span className={styles.suffix}>{suffix}</span>
|
|
251
315
|
)}
|
|
252
316
|
</>
|
|
@@ -255,7 +319,7 @@ export const TextField = ({
|
|
|
255
319
|
{showErrorIcon && (
|
|
256
320
|
<div
|
|
257
321
|
className={classNames(styles.trailingIcon, {
|
|
258
|
-
' absolute right-0': !
|
|
322
|
+
' absolute right-0': !effectiveTrailingIcon,
|
|
259
323
|
})}
|
|
260
324
|
>
|
|
261
325
|
<Icon
|
|
@@ -265,8 +329,9 @@ export const TextField = ({
|
|
|
265
329
|
</div>
|
|
266
330
|
)}
|
|
267
331
|
</fieldset>
|
|
268
|
-
|
|
269
|
-
|
|
332
|
+
|
|
333
|
+
{hasSupportingText && (
|
|
334
|
+
<p className={styles.supportingText} id={helperTextId}>
|
|
270
335
|
{errorText?.length
|
|
271
336
|
? errorText
|
|
272
337
|
: supportingText?.length
|
|
@@ -274,6 +339,40 @@ export const TextField = ({
|
|
|
274
339
|
: '\u00A0'}
|
|
275
340
|
</p>
|
|
276
341
|
)}
|
|
342
|
+
|
|
343
|
+
{isDateInput && showDatePicker && (
|
|
344
|
+
<>
|
|
345
|
+
<div
|
|
346
|
+
className="fixed inset-0 z-40 bg-transparent"
|
|
347
|
+
onClick={() => setShowDatePicker(false)}
|
|
348
|
+
/>
|
|
349
|
+
<AnchorPositioner anchorRef={textFieldRef} position="bottom">
|
|
350
|
+
<div className="z-50 shadow-xl rounded-[28px] bg-surface-container-high overflow-hidden">
|
|
351
|
+
<DatePicker
|
|
352
|
+
className={''}
|
|
353
|
+
value={tempDate}
|
|
354
|
+
onChange={setTempDate}
|
|
355
|
+
/>
|
|
356
|
+
<div className="flex justify-end gap-2 p-4 pt-0">
|
|
357
|
+
<Button
|
|
358
|
+
variant="text"
|
|
359
|
+
size="small"
|
|
360
|
+
onClick={() => setShowDatePicker(false)}
|
|
361
|
+
>
|
|
362
|
+
Cancel
|
|
363
|
+
</Button>
|
|
364
|
+
<Button
|
|
365
|
+
variant="filled"
|
|
366
|
+
size="small"
|
|
367
|
+
onClick={handleDateConfirm}
|
|
368
|
+
>
|
|
369
|
+
OK
|
|
370
|
+
</Button>
|
|
371
|
+
</div>
|
|
372
|
+
</div>
|
|
373
|
+
</AnchorPositioner>
|
|
374
|
+
</>
|
|
375
|
+
)}
|
|
277
376
|
</div>
|
|
278
377
|
);
|
|
279
378
|
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { cloneElement, isValidElement, useEffect, useRef } from 'react';
|
|
2
2
|
import { MotionProps } from '../utils';
|
|
3
3
|
import { Button } from './Button';
|
|
4
|
+
import { AnchorPositioner } from './AnchorPositioner';
|
|
4
5
|
import { ToolTipInterface } from '../interfaces';
|
|
5
6
|
import { useToolTipStyle } from '../styles';
|
|
6
7
|
import { AnimatePresence, motion } from 'motion/react';
|
|
7
|
-
import {
|
|
8
|
-
import { useTooltipTrigger, useTooltipPosition } from '../hooks';
|
|
8
|
+
import { useTooltipTrigger } from '../hooks';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Tooltips display brief labels or messages
|
|
@@ -36,8 +36,12 @@ export const Tooltip = ({
|
|
|
36
36
|
defaultOpen = false,
|
|
37
37
|
onOpenChange,
|
|
38
38
|
id,
|
|
39
|
+
anchorRef,
|
|
39
40
|
...props
|
|
40
41
|
}: MotionProps<ToolTipInterface>) => {
|
|
42
|
+
const defaultPosition = variant === 'rich' ? 'bottom-right' : 'bottom';
|
|
43
|
+
const effectivePosition = positionProp || defaultPosition;
|
|
44
|
+
|
|
41
45
|
transition = { duration: 0.3, ...transition };
|
|
42
46
|
|
|
43
47
|
if (!children && !targetRef) {
|
|
@@ -50,6 +54,7 @@ export const Tooltip = ({
|
|
|
50
54
|
|
|
51
55
|
const internalRef = useRef<HTMLElement | null>(null);
|
|
52
56
|
const resolvedRef = targetRef || internalRef;
|
|
57
|
+
const positioningRef = anchorRef || resolvedRef;
|
|
53
58
|
|
|
54
59
|
// Use the trigger hook for state management and accessibility
|
|
55
60
|
const { triggerProps, tooltipProps, isOpen } = useTooltipTrigger({
|
|
@@ -62,14 +67,6 @@ export const Tooltip = ({
|
|
|
62
67
|
id,
|
|
63
68
|
});
|
|
64
69
|
|
|
65
|
-
// Use the position hook for auto-positioning
|
|
66
|
-
const { resolvedPosition } = useTooltipPosition({
|
|
67
|
-
targetRef: resolvedRef,
|
|
68
|
-
position: positionProp,
|
|
69
|
-
variant,
|
|
70
|
-
isOpen,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
70
|
// Apply trigger props to the target element
|
|
74
71
|
const enhancedChildren =
|
|
75
72
|
!targetRef && isValidElement(children)
|
|
@@ -150,7 +147,7 @@ export const Tooltip = ({
|
|
|
150
147
|
className,
|
|
151
148
|
title,
|
|
152
149
|
text,
|
|
153
|
-
position:
|
|
150
|
+
position: effectivePosition,
|
|
154
151
|
trigger,
|
|
155
152
|
targetRef: targetRef as any,
|
|
156
153
|
children: children as any,
|
|
@@ -172,7 +169,10 @@ export const Tooltip = ({
|
|
|
172
169
|
{enhancedChildren}
|
|
173
170
|
<AnimatePresence>
|
|
174
171
|
{isOpen && (
|
|
175
|
-
<
|
|
172
|
+
<AnchorPositioner
|
|
173
|
+
anchorRef={positioningRef}
|
|
174
|
+
position={effectivePosition}
|
|
175
|
+
>
|
|
176
176
|
<motion.div
|
|
177
177
|
initial={'close'}
|
|
178
178
|
variants={variants}
|
|
@@ -209,7 +209,7 @@ export const Tooltip = ({
|
|
|
209
209
|
)}
|
|
210
210
|
</div>
|
|
211
211
|
</motion.div>
|
|
212
|
-
</
|
|
212
|
+
</AnchorPositioner>
|
|
213
213
|
)}
|
|
214
214
|
</AnimatePresence>
|
|
215
215
|
</>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
export * from './AnchorPositioner';
|
|
1
2
|
export * from './Button';
|
|
2
3
|
export * from './Card';
|
|
3
4
|
export * from './Carousel';
|
|
4
5
|
export * from './CarouselItem';
|
|
5
6
|
export * from './CarouselItem';
|
|
7
|
+
export * from './Checkbox';
|
|
6
8
|
export * from './Chip';
|
|
7
9
|
export * from './Chips';
|
|
8
10
|
export * from './Divider';
|
|
@@ -24,3 +26,4 @@ export * from './TextField';
|
|
|
24
26
|
export * from './NavigationRailItem';
|
|
25
27
|
export * from './NavigationRail';
|
|
26
28
|
export * from './Tooltip';
|
|
29
|
+
export * from './DatePicker';
|
|
@@ -18,6 +18,7 @@ export interface StateInterface {
|
|
|
18
18
|
| 'state-layer';
|
|
19
19
|
className?: string;
|
|
20
20
|
style?: React.CSSProperties;
|
|
21
|
+
children?: React.ReactNode;
|
|
21
22
|
};
|
|
22
23
|
states: { isClient: boolean };
|
|
23
24
|
elements: ['stateLayer'];
|
|
@@ -27,6 +28,7 @@ export const State = ({
|
|
|
27
28
|
style,
|
|
28
29
|
colorName,
|
|
29
30
|
stateClassName = 'state-ripple-group',
|
|
31
|
+
children,
|
|
30
32
|
className,
|
|
31
33
|
}: ReactProps<StateInterface>) => {
|
|
32
34
|
const ref = useRef<HTMLDivElement>(null);
|
|
@@ -67,6 +69,7 @@ export const State = ({
|
|
|
67
69
|
}}
|
|
68
70
|
>
|
|
69
71
|
{isClient && <RippleEffect triggerRef={groupStateRef} />}
|
|
72
|
+
{children}
|
|
70
73
|
</div>
|
|
71
74
|
);
|
|
72
75
|
};
|
|
@@ -76,8 +79,8 @@ const cardConfig: ClassNameComponent<StateInterface> = ({
|
|
|
76
79
|
stateClassName,
|
|
77
80
|
}) => ({
|
|
78
81
|
stateLayer: classNames([
|
|
79
|
-
stateClassName,
|
|
80
82
|
'w-full top-0 left-0 h-full absolute pointer-events-none overflow-hidden',
|
|
83
|
+
stateClassName,
|
|
81
84
|
]),
|
|
82
85
|
});
|
|
83
86
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef
|
|
1
|
+
import { ReactNode, useEffect, useRef } from 'react';
|
|
2
2
|
import Lenis from 'lenis';
|
|
3
3
|
|
|
4
4
|
export type SmoothScrollProps = {
|
|
@@ -36,6 +36,20 @@ export type SmoothScrollProps = {
|
|
|
36
36
|
/**
|
|
37
37
|
* SmoothScroll component using Lenis for smooth scrolling.
|
|
38
38
|
* This component enables smooth scrolling at the document level.
|
|
39
|
+
*
|
|
40
|
+
* @warning **Use with caution.** Overriding native scroll behavior can cause:
|
|
41
|
+
* - **Accessibility issues**: Screen readers, keyboard navigation, and assistive technologies
|
|
42
|
+
* may not work correctly with custom scroll implementations.
|
|
43
|
+
* - **Anchor links broken**: `scrollIntoView()`, hash navigation (`#section`), and
|
|
44
|
+
* `window.scrollTo()` may behave unexpectedly or be ignored.
|
|
45
|
+
* - **Third-party library conflicts**: Libraries relying on native scroll events
|
|
46
|
+
* (infinite scroll, lazy loading, scroll-triggered animations) may malfunction.
|
|
47
|
+
* - **Browser features disabled**: Find-in-page (Ctrl+F), autoscroll, and native
|
|
48
|
+
* momentum scrolling on trackpads may not work as expected.
|
|
49
|
+
* - **Performance overhead**: The RAF loop runs continuously, which may impact
|
|
50
|
+
* battery life and performance on low-end devices.
|
|
51
|
+
* - **Mobile issues**: Touch gestures, pull-to-refresh, and overscroll behaviors
|
|
52
|
+
* can conflict with smooth scroll implementations.
|
|
39
53
|
*/
|
|
40
54
|
export const SmoothScroll = ({
|
|
41
55
|
transition = 1.2,
|
package/src/lib/hooks/index.ts
CHANGED