@scripso-homepad/ui 0.4.1 → 0.4.3
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/dist/{chunk-C7GHBVMM.js → chunk-66WR57JJ.js} +53 -10
- package/dist/chunk-66WR57JJ.js.map +1 -0
- package/dist/index.cjs +2297 -391
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +144 -2
- package/dist/index.d.ts +144 -2
- package/dist/index.js +1978 -96
- package/dist/index.js.map +1 -1
- package/dist/web/index.cjs +30411 -759
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.css +78 -0
- package/dist/web/index.css.map +1 -0
- package/dist/web/index.d.cts +267 -6
- package/dist/web/index.d.ts +267 -6
- package/dist/web/index.js +30179 -601
- package/dist/web/index.js.map +1 -1
- package/package.json +5 -4
- package/src/components/Calendar.stories.tsx +337 -0
- package/src/components/Calendar.tsx +441 -0
- package/src/components/DatePicker.stories.tsx +219 -0
- package/src/components/DatePicker.tsx +366 -0
- package/src/components/Input.tsx +5 -1
- package/src/components/Select.stories.tsx +134 -0
- package/src/components/Select.tsx +462 -0
- package/src/css.d.ts +1 -0
- package/src/icons/CalendarIcon.tsx +28 -0
- package/src/icons/CalendarIcon.web.tsx +34 -0
- package/src/icons/ChevronDownIcon.tsx +11 -4
- package/src/icons/ChevronDownIcon.web.tsx +6 -4
- package/src/icons/ChevronNavIcons.tsx +44 -0
- package/src/icons/ChevronNavIcons.web.tsx +57 -0
- package/src/icons/LockIcon.tsx +30 -0
- package/src/icons/LockIcon.web.tsx +35 -0
- package/src/icons/calendarIconPaths.ts +2 -0
- package/src/icons/chevronDownPath.ts +1 -0
- package/src/icons/chevronNavPaths.ts +2 -0
- package/src/icons/lockIconPaths.ts +2 -0
- package/src/index.ts +43 -0
- package/src/theme/calendar.ts +357 -0
- package/src/theme/input.ts +16 -6
- package/src/theme/select.ts +249 -0
- package/src/utils/calendarDays.ts +273 -0
- package/src/utils/calendarLocaleContext.tsx +95 -0
- package/src/utils/calendarLocalization.ts +187 -0
- package/src/utils/countryDropdownScrollStyles.ts +53 -13
- package/src/utils/formatMultiSelectDisplay.ts +56 -0
- package/src/web/components/Badge.stories.tsx +43 -0
- package/src/web/components/Badge.tsx +35 -0
- package/src/web/components/Pagination.stories.tsx +78 -0
- package/src/web/components/Pagination.tsx +153 -0
- package/src/web/components/StatusBadge.tsx +20 -0
- package/src/web/components/Table.stories.tsx +415 -0
- package/src/web/components/Table.tsx +3 -0
- package/src/web/components/TableActionButton.tsx +67 -0
- package/src/web/components/TableActionsMenu.tsx +208 -0
- package/src/web/components/TableIconText.tsx +23 -0
- package/src/web/components/TableRowStatusActions.tsx +33 -0
- package/src/web/components/TableStatusActionsCell.tsx +29 -0
- package/src/web/components/pagination-utils.ts +39 -0
- package/src/web/components/table/DataTable.tsx +123 -0
- package/src/web/components/table/TablePrimitives.tsx +214 -0
- package/src/web/components/table/TableScrollArea.tsx +51 -0
- package/src/web/components/table/constants.ts +59 -0
- package/src/web/components/table/data-table-class-names.ts +26 -0
- package/src/web/components/table/index.ts +44 -0
- package/src/web/components/table/table-row-context.tsx +19 -0
- package/src/web/components/table/use-horizontal-scroll-state.ts +72 -0
- package/src/web/components/table-scroll.css +81 -0
- package/src/web/hooks/useOnClickOutside.ts +20 -5
- package/src/web/icons/TableMenuActivateIcon.tsx +27 -0
- package/src/web/icons/TableMenuCheckIcon.tsx +27 -0
- package/src/web/icons/TableMenuCloseIcon.tsx +27 -0
- package/src/web/icons/TableMenuEditIcon.tsx +27 -0
- package/src/web/icons/TableMenuInfoIcon.tsx +27 -0
- package/src/web/icons/TableMenuRefreshIcon.tsx +27 -0
- package/src/web/icons/TableMenuSuspendIcon.tsx +34 -0
- package/src/web/icons/TableMenuTrashIcon.tsx +27 -0
- package/src/web/icons/TableMoreIcon.tsx +41 -0
- package/src/web/icons/TableSortIcon.tsx +37 -0
- package/src/web/index.ts +74 -0
- package/src/web/layout/AppHeader.stories.tsx +2 -0
- package/src/web/layout/AppHeader.tsx +18 -14
- package/src/web/layout/DashboardLayout.stories.tsx +1 -0
- package/src/web/layout/DashboardLayout.tsx +8 -4
- package/src/web/layout/story-helpers.tsx +10 -2
- package/dist/chunk-C7GHBVMM.js.map +0 -1
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, type ComponentRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Modal,
|
|
4
|
+
Pressable,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
View,
|
|
8
|
+
type StyleProp,
|
|
9
|
+
type ViewStyle,
|
|
10
|
+
} from "react-native";
|
|
11
|
+
import { CalendarIcon } from "../icons/CalendarIcon";
|
|
12
|
+
import {
|
|
13
|
+
CALENDAR_FIELD_ICON_SIZE,
|
|
14
|
+
CALENDAR_FIELD_WIDTH,
|
|
15
|
+
CALENDAR_PANEL_HEIGHT,
|
|
16
|
+
CALENDAR_PANEL_WIDTH,
|
|
17
|
+
calendarFieldMetrics,
|
|
18
|
+
calendarFieldPlaceholderTypography,
|
|
19
|
+
calendarFieldTypography,
|
|
20
|
+
calendarLabelTypography,
|
|
21
|
+
resolveCalendarDropdownLeft,
|
|
22
|
+
resolveCalendarDropdownTop,
|
|
23
|
+
} from "../theme/calendar";
|
|
24
|
+
import { getSelectFieldStyles, resolveInputVisualState, SELECT_LABEL_GAP } from "../theme/select";
|
|
25
|
+
import { colors } from "../theme/tokens";
|
|
26
|
+
import {
|
|
27
|
+
formatCalendarDate,
|
|
28
|
+
formatCalendarDateRange,
|
|
29
|
+
type DateRange,
|
|
30
|
+
} from "../utils/calendarDays";
|
|
31
|
+
import { getCalendarTranslations } from "../utils/calendarLocalization";
|
|
32
|
+
import { useCalendarLocale } from "../utils/calendarLocaleContext";
|
|
33
|
+
import { useApplyWebClassName } from "../utils/useApplyWebClassName";
|
|
34
|
+
import {
|
|
35
|
+
Calendar,
|
|
36
|
+
type CalendarLabels,
|
|
37
|
+
type CalendarRangeProps,
|
|
38
|
+
type CalendarSingleProps,
|
|
39
|
+
} from "./Calendar";
|
|
40
|
+
import { Label } from "./Label";
|
|
41
|
+
|
|
42
|
+
export type { CalendarLabels, DateRange };
|
|
43
|
+
|
|
44
|
+
type DatePickerFieldProps = {
|
|
45
|
+
label?: string;
|
|
46
|
+
placeholder?: string;
|
|
47
|
+
disabled?: boolean;
|
|
48
|
+
error?: string;
|
|
49
|
+
invalid?: boolean;
|
|
50
|
+
hint?: string;
|
|
51
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
52
|
+
className?: string;
|
|
53
|
+
fieldClassName?: string;
|
|
54
|
+
panelClassName?: string;
|
|
55
|
+
labelClassName?: string;
|
|
56
|
+
errorClassName?: string;
|
|
57
|
+
hintClassName?: string;
|
|
58
|
+
/** Opens the calendar popup on mount (useful for Storybook previews). */
|
|
59
|
+
defaultOpen?: boolean;
|
|
60
|
+
/** Closes the popup after a complete range is selected. Defaults to true in range mode. */
|
|
61
|
+
closeOnRangeComplete?: boolean;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type DatePickerSingleProps = Omit<CalendarSingleProps, "style" | "className"> &
|
|
65
|
+
DatePickerFieldProps & {
|
|
66
|
+
mode?: "single";
|
|
67
|
+
formatDate?: (date: Date) => string;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type DatePickerRangeProps = Omit<CalendarRangeProps, "style" | "className"> &
|
|
71
|
+
DatePickerFieldProps & {
|
|
72
|
+
mode: "range";
|
|
73
|
+
formatRange?: (range: DateRange) => string;
|
|
74
|
+
rangeSeparator?: string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type DatePickerProps = DatePickerSingleProps | DatePickerRangeProps;
|
|
78
|
+
|
|
79
|
+
export function DatePicker(props: DatePickerProps) {
|
|
80
|
+
const {
|
|
81
|
+
label,
|
|
82
|
+
placeholder,
|
|
83
|
+
locale,
|
|
84
|
+
labels,
|
|
85
|
+
minDate,
|
|
86
|
+
maxDate,
|
|
87
|
+
today,
|
|
88
|
+
viewDate,
|
|
89
|
+
defaultViewDate,
|
|
90
|
+
onViewDateChange,
|
|
91
|
+
disabled = false,
|
|
92
|
+
error,
|
|
93
|
+
invalid,
|
|
94
|
+
hint,
|
|
95
|
+
containerStyle,
|
|
96
|
+
className,
|
|
97
|
+
fieldClassName,
|
|
98
|
+
panelClassName,
|
|
99
|
+
labelClassName,
|
|
100
|
+
errorClassName,
|
|
101
|
+
hintClassName,
|
|
102
|
+
defaultOpen = false,
|
|
103
|
+
} = props;
|
|
104
|
+
const mode = props.mode ?? "single";
|
|
105
|
+
const closeOnRangeComplete =
|
|
106
|
+
props.mode === "range" ? (props.closeOnRangeComplete ?? true) : false;
|
|
107
|
+
const resolvedLocale = useCalendarLocale(locale);
|
|
108
|
+
const translations = getCalendarTranslations(resolvedLocale);
|
|
109
|
+
const resolvedPlaceholder =
|
|
110
|
+
placeholder ?? (mode === "range" ? translations.rangePlaceholder : translations.datePlaceholder);
|
|
111
|
+
const wrapperRef = useRef<ComponentRef<typeof View>>(null);
|
|
112
|
+
const triggerRef = useRef<ComponentRef<typeof Pressable>>(null);
|
|
113
|
+
const helperRef = useRef<ComponentRef<typeof Text>>(null);
|
|
114
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
115
|
+
const [anchor, setAnchor] = useState<{
|
|
116
|
+
x: number;
|
|
117
|
+
y: number;
|
|
118
|
+
width: number;
|
|
119
|
+
height: number;
|
|
120
|
+
} | null>(null);
|
|
121
|
+
|
|
122
|
+
useApplyWebClassName(wrapperRef, className);
|
|
123
|
+
useApplyWebClassName(triggerRef, fieldClassName);
|
|
124
|
+
useApplyWebClassName(helperRef, error ? errorClassName : hintClassName);
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!defaultOpen || disabled) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const frame = requestAnimationFrame(() => {
|
|
132
|
+
triggerRef.current?.measureInWindow((x, y, width, height) => {
|
|
133
|
+
setAnchor({ x, y, width, height });
|
|
134
|
+
setOpen(true);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return () => cancelAnimationFrame(frame);
|
|
139
|
+
}, [defaultOpen, disabled]);
|
|
140
|
+
|
|
141
|
+
const hasError = Boolean(error) || Boolean(invalid);
|
|
142
|
+
const fieldStyles = getSelectFieldStyles(
|
|
143
|
+
resolveInputVisualState({
|
|
144
|
+
error: hasError,
|
|
145
|
+
disabled,
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const hasValue =
|
|
150
|
+
mode === "single"
|
|
151
|
+
? Boolean(props.value)
|
|
152
|
+
: Boolean(props.mode === "range" && props.value?.start);
|
|
153
|
+
const displayValue = (() => {
|
|
154
|
+
if (props.mode === "range") {
|
|
155
|
+
if (!props.value?.start) {
|
|
156
|
+
return resolvedPlaceholder;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
props.formatRange?.(props.value) ??
|
|
161
|
+
formatCalendarDateRange(props.value, resolvedLocale, props.rangeSeparator)
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (props.value) {
|
|
166
|
+
return props.formatDate?.(props.value) ?? formatCalendarDate(props.value, resolvedLocale);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return resolvedPlaceholder;
|
|
170
|
+
})();
|
|
171
|
+
const isPlaceholder = !hasValue;
|
|
172
|
+
const valueTextStyle = isPlaceholder
|
|
173
|
+
? {
|
|
174
|
+
...calendarFieldPlaceholderTypography,
|
|
175
|
+
color: disabled ? colors.stormGray300 : calendarFieldPlaceholderTypography.color,
|
|
176
|
+
}
|
|
177
|
+
: {
|
|
178
|
+
...calendarFieldTypography,
|
|
179
|
+
color: fieldStyles.value.color,
|
|
180
|
+
};
|
|
181
|
+
const helperMessage = error ?? hint;
|
|
182
|
+
const panelWidth = CALENDAR_PANEL_WIDTH;
|
|
183
|
+
const panelLeft = anchor ? resolveCalendarDropdownLeft(anchor, panelWidth) : 0;
|
|
184
|
+
const dropdownTop = anchor ? resolveCalendarDropdownTop(anchor) : 0;
|
|
185
|
+
const resolvedDefaultViewDate =
|
|
186
|
+
defaultViewDate ??
|
|
187
|
+
(props.mode === "range" ? props.value?.start : props.value) ??
|
|
188
|
+
today;
|
|
189
|
+
|
|
190
|
+
function closePicker() {
|
|
191
|
+
setOpen(false);
|
|
192
|
+
setAnchor(null);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function openPicker() {
|
|
196
|
+
if (disabled) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
triggerRef.current?.measureInWindow((x, y, width, height) => {
|
|
201
|
+
setAnchor({ x, y, width, height });
|
|
202
|
+
setOpen(true);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function handleSingleSelect(date: Date) {
|
|
207
|
+
if (props.mode === "range") {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
props.onChange?.(date);
|
|
212
|
+
closePicker();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function handleRangeSelect(range: DateRange) {
|
|
216
|
+
if (props.mode !== "range") {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
props.onChange?.(range);
|
|
221
|
+
|
|
222
|
+
if (closeOnRangeComplete && range.end) {
|
|
223
|
+
closePicker();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<View ref={wrapperRef} style={[styles.wrapper, containerStyle]}>
|
|
229
|
+
{label ? (
|
|
230
|
+
<Label disabled={disabled} style={styles.label} className={labelClassName}>
|
|
231
|
+
{label}
|
|
232
|
+
</Label>
|
|
233
|
+
) : null}
|
|
234
|
+
|
|
235
|
+
<View style={[fieldStyles.outline, styles.fieldOutline]}>
|
|
236
|
+
<Pressable
|
|
237
|
+
ref={triggerRef}
|
|
238
|
+
accessibilityRole="button"
|
|
239
|
+
accessibilityState={{ disabled, expanded: open }}
|
|
240
|
+
disabled={disabled}
|
|
241
|
+
onPress={openPicker}
|
|
242
|
+
style={[calendarFieldMetrics.container, fieldStyles.container]}
|
|
243
|
+
>
|
|
244
|
+
<Text
|
|
245
|
+
style={[calendarFieldMetrics.value, valueTextStyle]}
|
|
246
|
+
numberOfLines={1}
|
|
247
|
+
>
|
|
248
|
+
{displayValue}
|
|
249
|
+
</Text>
|
|
250
|
+
<View style={styles.iconSlot}>
|
|
251
|
+
<CalendarIcon
|
|
252
|
+
size={CALENDAR_FIELD_ICON_SIZE}
|
|
253
|
+
color={disabled ? colors.stormGray200 : colors.stormGray150}
|
|
254
|
+
/>
|
|
255
|
+
</View>
|
|
256
|
+
</Pressable>
|
|
257
|
+
</View>
|
|
258
|
+
|
|
259
|
+
{helperMessage ? (
|
|
260
|
+
<Text ref={helperRef} style={error ? styles.error : styles.hint}>
|
|
261
|
+
{helperMessage}
|
|
262
|
+
</Text>
|
|
263
|
+
) : null}
|
|
264
|
+
|
|
265
|
+
<Modal visible={open} transparent animationType="fade" onRequestClose={closePicker}>
|
|
266
|
+
<View style={styles.modalRoot}>
|
|
267
|
+
<Pressable
|
|
268
|
+
style={styles.backdrop}
|
|
269
|
+
onPress={closePicker}
|
|
270
|
+
accessibilityRole="button"
|
|
271
|
+
accessibilityLabel={labels?.closeCalendar ?? translations.closeCalendar}
|
|
272
|
+
/>
|
|
273
|
+
{anchor ? (
|
|
274
|
+
<View
|
|
275
|
+
style={[
|
|
276
|
+
styles.panelPosition,
|
|
277
|
+
{
|
|
278
|
+
top: dropdownTop,
|
|
279
|
+
left: panelLeft,
|
|
280
|
+
width: panelWidth,
|
|
281
|
+
height: CALENDAR_PANEL_HEIGHT,
|
|
282
|
+
},
|
|
283
|
+
]}
|
|
284
|
+
>
|
|
285
|
+
{props.mode === "range" ? (
|
|
286
|
+
<Calendar
|
|
287
|
+
mode="range"
|
|
288
|
+
value={props.value}
|
|
289
|
+
onChange={handleRangeSelect}
|
|
290
|
+
viewDate={viewDate}
|
|
291
|
+
defaultViewDate={resolvedDefaultViewDate}
|
|
292
|
+
onViewDateChange={onViewDateChange}
|
|
293
|
+
locale={resolvedLocale}
|
|
294
|
+
labels={labels}
|
|
295
|
+
minDate={minDate}
|
|
296
|
+
maxDate={maxDate}
|
|
297
|
+
today={today}
|
|
298
|
+
className={panelClassName}
|
|
299
|
+
/>
|
|
300
|
+
) : (
|
|
301
|
+
<Calendar
|
|
302
|
+
mode="single"
|
|
303
|
+
value={props.value}
|
|
304
|
+
onChange={handleSingleSelect}
|
|
305
|
+
viewDate={viewDate}
|
|
306
|
+
defaultViewDate={resolvedDefaultViewDate}
|
|
307
|
+
onViewDateChange={onViewDateChange}
|
|
308
|
+
locale={resolvedLocale}
|
|
309
|
+
labels={labels}
|
|
310
|
+
minDate={minDate}
|
|
311
|
+
maxDate={maxDate}
|
|
312
|
+
today={today}
|
|
313
|
+
className={panelClassName}
|
|
314
|
+
/>
|
|
315
|
+
)}
|
|
316
|
+
</View>
|
|
317
|
+
) : null}
|
|
318
|
+
</View>
|
|
319
|
+
</Modal>
|
|
320
|
+
</View>
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const styles = StyleSheet.create({
|
|
325
|
+
wrapper: {
|
|
326
|
+
width: CALENDAR_FIELD_WIDTH,
|
|
327
|
+
maxWidth: "100%",
|
|
328
|
+
gap: SELECT_LABEL_GAP,
|
|
329
|
+
},
|
|
330
|
+
fieldOutline: {
|
|
331
|
+
width: CALENDAR_FIELD_WIDTH,
|
|
332
|
+
maxWidth: "100%",
|
|
333
|
+
alignSelf: "flex-start",
|
|
334
|
+
},
|
|
335
|
+
label: {
|
|
336
|
+
...calendarLabelTypography,
|
|
337
|
+
},
|
|
338
|
+
iconSlot: {
|
|
339
|
+
width: CALENDAR_FIELD_ICON_SIZE,
|
|
340
|
+
height: CALENDAR_FIELD_ICON_SIZE,
|
|
341
|
+
alignItems: "center",
|
|
342
|
+
justifyContent: "center",
|
|
343
|
+
flexShrink: 0,
|
|
344
|
+
},
|
|
345
|
+
modalRoot: {
|
|
346
|
+
flex: 1,
|
|
347
|
+
},
|
|
348
|
+
backdrop: {
|
|
349
|
+
...StyleSheet.absoluteFillObject,
|
|
350
|
+
backgroundColor: colors.transparent,
|
|
351
|
+
},
|
|
352
|
+
panelPosition: {
|
|
353
|
+
position: "absolute",
|
|
354
|
+
zIndex: 1,
|
|
355
|
+
},
|
|
356
|
+
error: {
|
|
357
|
+
fontSize: 12,
|
|
358
|
+
lineHeight: 12,
|
|
359
|
+
color: colors.inputError,
|
|
360
|
+
},
|
|
361
|
+
hint: {
|
|
362
|
+
fontSize: 12,
|
|
363
|
+
lineHeight: 12,
|
|
364
|
+
color: colors.stormGray300,
|
|
365
|
+
},
|
|
366
|
+
});
|
package/src/components/Input.tsx
CHANGED
|
@@ -56,6 +56,8 @@ export interface InputProps extends TextInputProps {
|
|
|
56
56
|
rightIcon?: ReactNode;
|
|
57
57
|
/** Validation or helper error message. Shown instead of `hint` when set. */
|
|
58
58
|
error?: string;
|
|
59
|
+
/** Applies error field styling without showing a message (use with a form-level error). */
|
|
60
|
+
invalid?: boolean;
|
|
59
61
|
/** Helper text shown below the input when there is no error. */
|
|
60
62
|
hint?: string;
|
|
61
63
|
containerStyle?: StyleProp<ViewStyle>;
|
|
@@ -78,6 +80,7 @@ export function Input({
|
|
|
78
80
|
leftIcon,
|
|
79
81
|
rightIcon,
|
|
80
82
|
error,
|
|
83
|
+
invalid,
|
|
81
84
|
hint,
|
|
82
85
|
containerStyle,
|
|
83
86
|
style,
|
|
@@ -108,6 +111,7 @@ export function Input({
|
|
|
108
111
|
useApplyWebClassName(helperRef, error ? errorClassName : hintClassName);
|
|
109
112
|
|
|
110
113
|
const isDisabled = editable === false;
|
|
114
|
+
const hasError = Boolean(error) || Boolean(invalid);
|
|
111
115
|
const passwordToggleEnabled =
|
|
112
116
|
secureTextEntry === true && showPasswordToggle !== false && rightIcon == null;
|
|
113
117
|
const effectiveSecureTextEntry = passwordToggleEnabled
|
|
@@ -115,7 +119,7 @@ export function Input({
|
|
|
115
119
|
: secureTextEntry;
|
|
116
120
|
const visualState = resolveInputVisualState({
|
|
117
121
|
focused,
|
|
118
|
-
error:
|
|
122
|
+
error: hasError,
|
|
119
123
|
disabled: isDisabled,
|
|
120
124
|
});
|
|
121
125
|
const fieldStyles = getInputFieldStyles(visualState);
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { View, StyleSheet } from "react-native";
|
|
4
|
+
import { KeyIcon } from "../icons/KeyIcon";
|
|
5
|
+
import { Select, type SelectOption } from "./Select";
|
|
6
|
+
|
|
7
|
+
const demoOptions: SelectOption[] = Array.from({ length: 7 }, (_, index) => ({
|
|
8
|
+
value: String(index + 1),
|
|
9
|
+
label: `Select Item ${index + 1}`,
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
const meta = {
|
|
13
|
+
title: "Components/Select",
|
|
14
|
+
component: Select,
|
|
15
|
+
args: {
|
|
16
|
+
label: "Label",
|
|
17
|
+
placeholder: "placeholder",
|
|
18
|
+
leftIcon: <KeyIcon />,
|
|
19
|
+
options: demoOptions,
|
|
20
|
+
},
|
|
21
|
+
} satisfies Meta<typeof Select>;
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
type Story = StoryObj<typeof meta>;
|
|
25
|
+
|
|
26
|
+
export const Default: Story = {};
|
|
27
|
+
|
|
28
|
+
export const Controlled: Story = {
|
|
29
|
+
render: function ControlledSelectStory() {
|
|
30
|
+
const [value, setValue] = useState<string | undefined>();
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<View style={styles.field}>
|
|
34
|
+
<Select
|
|
35
|
+
label="Label"
|
|
36
|
+
placeholder="placeholder"
|
|
37
|
+
leftIcon={<KeyIcon />}
|
|
38
|
+
options={demoOptions}
|
|
39
|
+
value={value}
|
|
40
|
+
onValueChange={setValue}
|
|
41
|
+
/>
|
|
42
|
+
</View>
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const Open: Story = {
|
|
48
|
+
render: function OpenSelectStory() {
|
|
49
|
+
const [value, setValue] = useState("1");
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<View style={styles.field}>
|
|
53
|
+
<Select
|
|
54
|
+
label="Label"
|
|
55
|
+
placeholder="placeholder"
|
|
56
|
+
leftIcon={<KeyIcon />}
|
|
57
|
+
options={demoOptions}
|
|
58
|
+
value={value}
|
|
59
|
+
onValueChange={setValue}
|
|
60
|
+
/>
|
|
61
|
+
</View>
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const WithError: Story = {
|
|
67
|
+
args: {
|
|
68
|
+
error: "Please select an item",
|
|
69
|
+
invalid: true,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const Disabled: Story = {
|
|
74
|
+
args: {
|
|
75
|
+
value: "1",
|
|
76
|
+
disabled: true,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const multiSelectOptions: SelectOption[] = Array.from({ length: 8 }, (_, index) => ({
|
|
81
|
+
value: String(index + 1),
|
|
82
|
+
label: `Selected Item ${index + 1}`,
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
export const Multiple: Story = {
|
|
86
|
+
render: function MultipleSelectStory() {
|
|
87
|
+
const [value, setValue] = useState<string[]>(["1", "2", "3", "4", "5"]);
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<View style={styles.field}>
|
|
91
|
+
<Select
|
|
92
|
+
multiple
|
|
93
|
+
label="Label"
|
|
94
|
+
placeholder="placeholder"
|
|
95
|
+
leftIcon={<KeyIcon />}
|
|
96
|
+
options={multiSelectOptions}
|
|
97
|
+
value={value}
|
|
98
|
+
onValueChange={setValue}
|
|
99
|
+
/>
|
|
100
|
+
</View>
|
|
101
|
+
);
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const MultipleNarrow: Story = {
|
|
106
|
+
render: function MultipleNarrowSelectStory() {
|
|
107
|
+
const [value, setValue] = useState<string[]>(["1", "2", "3", "4", "5"]);
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<View style={styles.narrowField}>
|
|
111
|
+
<Select
|
|
112
|
+
multiple
|
|
113
|
+
label="Label"
|
|
114
|
+
placeholder="placeholder"
|
|
115
|
+
leftIcon={<KeyIcon />}
|
|
116
|
+
options={multiSelectOptions}
|
|
117
|
+
value={value}
|
|
118
|
+
onValueChange={setValue}
|
|
119
|
+
/>
|
|
120
|
+
</View>
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const styles = StyleSheet.create({
|
|
126
|
+
field: {
|
|
127
|
+
width: 360,
|
|
128
|
+
maxWidth: "100%",
|
|
129
|
+
},
|
|
130
|
+
narrowField: {
|
|
131
|
+
width: 240,
|
|
132
|
+
maxWidth: "100%",
|
|
133
|
+
},
|
|
134
|
+
});
|