@praxiis/ui 0.0.1
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/index.d.mts +52556 -0
- package/dist/index.d.ts +52556 -0
- package/dist/index.js +8753 -0
- package/dist/index.mjs +8777 -0
- package/package.json +70 -0
- package/src/__test-utils__/index.tsx +39 -0
- package/src/components/CalendarStrip/CalendarStrip.helpers.ts +106 -0
- package/src/components/CalendarStrip/CalendarStrip.tsx +83 -0
- package/src/components/CalendarStrip/CalendarStrip.types.ts +133 -0
- package/src/components/CalendarStrip/DayCard/DayCard.helpers.ts +44 -0
- package/src/components/CalendarStrip/DayCard/DayCard.tsx +71 -0
- package/src/components/CalendarStrip/DayCard/DayCard.types.ts +134 -0
- package/src/components/CalendarStrip/DayCard/index.ts +2 -0
- package/src/components/CalendarStrip/DayCard/useDayCardLogic.ts +45 -0
- package/src/components/CalendarStrip/index.ts +9 -0
- package/src/components/CalendarStrip/useCalendarStripLogic.ts +53 -0
- package/src/components/EmptyState/EmptyState.helpers.ts +104 -0
- package/src/components/EmptyState/EmptyState.tsx +205 -0
- package/src/components/EmptyState/EmptyState.types.ts +213 -0
- package/src/components/EmptyState/index.ts +44 -0
- package/src/components/EmptyState/useEmptyStateLogic.ts +131 -0
- package/src/components/Header/Header.helpers.ts +93 -0
- package/src/components/Header/Header.tsx +185 -0
- package/src/components/Header/Header.types.ts +153 -0
- package/src/components/Header/index.ts +44 -0
- package/src/components/Header/useHeaderLogic.ts +146 -0
- package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.helpers.ts +50 -0
- package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.tsx +78 -0
- package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.types.ts +99 -0
- package/src/components/ScheduleItem/ScheduleItem/index.ts +16 -0
- package/src/components/ScheduleItem/ScheduleItem/useScheduleItemLogic.ts +31 -0
- package/src/components/ScheduleItem/index.ts +15 -0
- package/src/components/index.ts +40 -0
- package/src/core/index.ts +34 -0
- package/src/core/restyle/RestyleThemeProviderWrapper.tsx +31 -0
- package/src/core/restyle/index.ts +38 -0
- package/src/core/restyle/restylePresetRegistry.ts +195 -0
- package/src/core/restyle/restyleTheme.ts +1352 -0
- package/src/core/restyle/restyleTypes.ts +8 -0
- package/src/core/restyle/useRestyleTheme.ts +10 -0
- package/src/hooks/animations/index.ts +3 -0
- package/src/hooks/animations/useAnimatedValue.ts +10 -0
- package/src/hooks/animations/useEntranceAnimation.ts +106 -0
- package/src/hooks/animations/usePulseAnimation.ts +63 -0
- package/src/hooks/index.ts +30 -0
- package/src/hooks/useReducedMotion.ts +60 -0
- package/src/i18n/index.ts +2 -0
- package/src/i18n/labels/en.ts +120 -0
- package/src/i18n/labels/es.ts +120 -0
- package/src/i18n/labels/index.ts +6 -0
- package/src/i18n/labels/types.ts +165 -0
- package/src/index.tsx +215 -0
- package/src/primitives/actions/Button/Button.helpers.ts +243 -0
- package/src/primitives/actions/Button/Button.tsx +198 -0
- package/src/primitives/actions/Button/Button.types.ts +207 -0
- package/src/primitives/actions/Button/index.ts +41 -0
- package/src/primitives/actions/Button/useButtonLogic.ts +160 -0
- package/src/primitives/actions/IconButton/IconButton.helpers.ts +235 -0
- package/src/primitives/actions/IconButton/IconButton.tsx +177 -0
- package/src/primitives/actions/IconButton/IconButton.types.ts +273 -0
- package/src/primitives/actions/IconButton/index.ts +30 -0
- package/src/primitives/actions/IconButton/useIconButtonLogic.ts +172 -0
- package/src/primitives/actions/index.ts +20 -0
- package/src/primitives/content/Avatar/Avatar.helpers.ts +177 -0
- package/src/primitives/content/Avatar/Avatar.tsx +199 -0
- package/src/primitives/content/Avatar/Avatar.types.ts +222 -0
- package/src/primitives/content/Avatar/index.ts +46 -0
- package/src/primitives/content/Avatar/useAvatarLogic.ts +149 -0
- package/src/primitives/content/Badge/Badge.helpers.ts +175 -0
- package/src/primitives/content/Badge/Badge.tsx +174 -0
- package/src/primitives/content/Badge/Badge.types.ts +223 -0
- package/src/primitives/content/Badge/index.ts +40 -0
- package/src/primitives/content/Badge/useBadgeLogic.ts +128 -0
- package/src/primitives/content/Card/Card.helpers.ts +27 -0
- package/src/primitives/content/Card/Card.tsx +123 -0
- package/src/primitives/content/Card/Card.types.ts +95 -0
- package/src/primitives/content/Card/index.ts +20 -0
- package/src/primitives/content/Card/useCardLogic.ts +48 -0
- package/src/primitives/content/Chip/Chip.helpers.ts +304 -0
- package/src/primitives/content/Chip/Chip.tsx +205 -0
- package/src/primitives/content/Chip/Chip.types.ts +234 -0
- package/src/primitives/content/Chip/index.ts +47 -0
- package/src/primitives/content/Chip/useChipLogic.ts +167 -0
- package/src/primitives/content/Icon/Icon.helpers.ts +54 -0
- package/src/primitives/content/Icon/Icon.tsx +110 -0
- package/src/primitives/content/Icon/Icon.types.ts +95 -0
- package/src/primitives/content/Icon/index.ts +20 -0
- package/src/primitives/content/Icon/useIconLogic.ts +73 -0
- package/src/primitives/content/index.ts +45 -0
- package/src/primitives/feedback/ProgressBar/ProgressBar.helpers.ts +122 -0
- package/src/primitives/feedback/ProgressBar/ProgressBar.tsx +154 -0
- package/src/primitives/feedback/ProgressBar/ProgressBar.types.ts +178 -0
- package/src/primitives/feedback/ProgressBar/index.ts +17 -0
- package/src/primitives/feedback/ProgressBar/useProgressBarLogic.ts +120 -0
- package/src/primitives/feedback/Skeleton/Skeleton.helpers.ts +145 -0
- package/src/primitives/feedback/Skeleton/Skeleton.tsx +155 -0
- package/src/primitives/feedback/Skeleton/Skeleton.types.ts +223 -0
- package/src/primitives/feedback/Skeleton/index.ts +44 -0
- package/src/primitives/feedback/Skeleton/useSkeletonLogic.ts +125 -0
- package/src/primitives/feedback/Spinner/Spinner.helpers.ts +40 -0
- package/src/primitives/feedback/Spinner/Spinner.tsx +105 -0
- package/src/primitives/feedback/Spinner/Spinner.types.ts +114 -0
- package/src/primitives/feedback/Spinner/index.ts +18 -0
- package/src/primitives/feedback/Spinner/useSpinnerLogic.ts +84 -0
- package/src/primitives/feedback/Toast/Toast.helpers.ts +163 -0
- package/src/primitives/feedback/Toast/Toast.tsx +190 -0
- package/src/primitives/feedback/Toast/Toast.types.ts +270 -0
- package/src/primitives/feedback/Toast/ToastContext.tsx +96 -0
- package/src/primitives/feedback/Toast/ToastProvider.tsx +241 -0
- package/src/primitives/feedback/Toast/index.ts +59 -0
- package/src/primitives/feedback/Toast/useToastLogic.ts +112 -0
- package/src/primitives/feedback/index.ts +45 -0
- package/src/primitives/index.ts +158 -0
- package/src/primitives/inputs/Checkbox/Checkbox.helpers.ts +132 -0
- package/src/primitives/inputs/Checkbox/Checkbox.tsx +150 -0
- package/src/primitives/inputs/Checkbox/Checkbox.types.ts +106 -0
- package/src/primitives/inputs/Checkbox/index.ts +30 -0
- package/src/primitives/inputs/Checkbox/useCheckboxLogic.ts +121 -0
- package/src/primitives/inputs/RadioButton/RadioButton.helpers.ts +123 -0
- package/src/primitives/inputs/RadioButton/RadioButton.tsx +159 -0
- package/src/primitives/inputs/RadioButton/RadioButton.types.ts +106 -0
- package/src/primitives/inputs/RadioButton/index.ts +25 -0
- package/src/primitives/inputs/RadioButton/useRadioButtonLogic.ts +117 -0
- package/src/primitives/inputs/SegmentedControl/SegmentedControl.helpers.ts +174 -0
- package/src/primitives/inputs/SegmentedControl/SegmentedControl.tsx +224 -0
- package/src/primitives/inputs/SegmentedControl/SegmentedControl.types.ts +187 -0
- package/src/primitives/inputs/SegmentedControl/index.ts +39 -0
- package/src/primitives/inputs/SegmentedControl/useSegmentedControlLogic.ts +151 -0
- package/src/primitives/inputs/SelectSheet/SelectSheet.helpers.ts +147 -0
- package/src/primitives/inputs/SelectSheet/SelectSheet.tsx +247 -0
- package/src/primitives/inputs/SelectSheet/SelectSheet.types.ts +196 -0
- package/src/primitives/inputs/SelectSheet/SelectSheetOption.tsx +177 -0
- package/src/primitives/inputs/SelectSheet/index.ts +48 -0
- package/src/primitives/inputs/SelectSheet/useSelectSheetLogic.ts +309 -0
- package/src/primitives/inputs/Switch/Switch.helpers.ts +109 -0
- package/src/primitives/inputs/Switch/Switch.tsx +191 -0
- package/src/primitives/inputs/Switch/Switch.types.ts +154 -0
- package/src/primitives/inputs/Switch/index.ts +40 -0
- package/src/primitives/inputs/Switch/useSwitchLogic.ts +192 -0
- package/src/primitives/inputs/TextInput/TextInput.helpers.ts +206 -0
- package/src/primitives/inputs/TextInput/TextInput.tsx +392 -0
- package/src/primitives/inputs/TextInput/TextInput.types.ts +216 -0
- package/src/primitives/inputs/TextInput/index.ts +37 -0
- package/src/primitives/inputs/TextInput/useTextInputLogic.ts +195 -0
- package/src/primitives/inputs/index.ts +52 -0
- package/src/primitives/layout/AnimatedBox.tsx +44 -0
- package/src/primitives/layout/Box.tsx +71 -0
- package/src/primitives/layout/Divider/Divider.helpers.ts +115 -0
- package/src/primitives/layout/Divider/Divider.tsx +139 -0
- package/src/primitives/layout/Divider/Divider.types.ts +178 -0
- package/src/primitives/layout/Divider/index.ts +24 -0
- package/src/primitives/layout/Divider/useDividerLogic.ts +109 -0
- package/src/primitives/layout/FlatList.tsx +66 -0
- package/src/primitives/layout/Pressable.tsx +74 -0
- package/src/primitives/layout/ScrollView.tsx +63 -0
- package/src/primitives/layout/Stack.tsx +69 -0
- package/src/primitives/layout/index.ts +40 -0
- package/src/primitives/navigation/index.ts +6 -0
- package/src/primitives/overlays/Modal/Modal.helpers.ts +31 -0
- package/src/primitives/overlays/Modal/Modal.tsx +264 -0
- package/src/primitives/overlays/Modal/Modal.types.ts +193 -0
- package/src/primitives/overlays/Modal/index.ts +43 -0
- package/src/primitives/overlays/Modal/useModalLogic.ts +103 -0
- package/src/primitives/overlays/index.ts +12 -0
- package/src/primitives/typography/Text.tsx +51 -0
- package/src/primitives/typography/index.ts +1 -0
- package/src/provider/DesignSystemContext.ts +22 -0
- package/src/provider/DesignSystemProvider.tsx +121 -0
- package/src/provider/index.ts +7 -0
- package/src/providers/ThemeProvider/createTheme.ts +304 -0
- package/src/providers/ThemeProvider/defaultTheme.ts +70 -0
- package/src/providers/ThemeProvider/index.ts +34 -0
- package/src/providers/ThemeProvider/types.ts +249 -0
- package/src/providers/index.ts +29 -0
- package/src/tokens/colors.ts +371 -0
- package/src/tokens/index.ts +145 -0
- package/src/tokens/motion.ts +176 -0
- package/src/tokens/radii.ts +82 -0
- package/src/tokens/scales.ts +588 -0
- package/src/tokens/shadows.ts +190 -0
- package/src/tokens/spacing.ts +140 -0
- package/src/tokens/tokens.json +207 -0
- package/src/tokens/typography.ts +251 -0
- package/src/types.ts +50 -0
- package/src/utils/accessibility.ts +169 -0
- package/src/utils/index.ts +25 -0
- package/src/utils/platform.ts +72 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SelectSheet Helper Functions
|
|
3
|
+
*
|
|
4
|
+
* Pure utility functions for SelectSheet component.
|
|
5
|
+
* All values derived from tokens/scales.ts for consistency.
|
|
6
|
+
*
|
|
7
|
+
* @see tokens/scales.ts - SELECT_SHEET_LAYOUT
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { SELECT_SHEET_LAYOUT } from "../../../tokens/scales";
|
|
11
|
+
import type {
|
|
12
|
+
SelectSheetOption,
|
|
13
|
+
} from "./SelectSheet.types";
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// SELECTION LOGIC HELPERS
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Normalize value to array format for internal use.
|
|
21
|
+
*/
|
|
22
|
+
export function normalizeValue<T>(
|
|
23
|
+
value: T | T[] | null,
|
|
24
|
+
isMultiple: boolean
|
|
25
|
+
): T[] {
|
|
26
|
+
if (value === null || value === undefined) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
if (isMultiple) {
|
|
30
|
+
return Array.isArray(value) ? value : [value];
|
|
31
|
+
}
|
|
32
|
+
return Array.isArray(value) ? value : [value];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Denormalize array back to expected format based on mode.
|
|
37
|
+
*/
|
|
38
|
+
export function denormalizeValue<T>(
|
|
39
|
+
values: T[],
|
|
40
|
+
isMultiple: boolean
|
|
41
|
+
): T | T[] | null {
|
|
42
|
+
if (isMultiple) {
|
|
43
|
+
return values;
|
|
44
|
+
}
|
|
45
|
+
return values.length > 0 ? values[0] : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Toggle selection in array.
|
|
50
|
+
*/
|
|
51
|
+
export function toggleSelection<T>(
|
|
52
|
+
currentSelections: T[],
|
|
53
|
+
value: T,
|
|
54
|
+
isMultiple: boolean
|
|
55
|
+
): T[] {
|
|
56
|
+
if (isMultiple) {
|
|
57
|
+
const index = currentSelections.indexOf(value);
|
|
58
|
+
if (index >= 0) {
|
|
59
|
+
return currentSelections.filter((v) => v !== value);
|
|
60
|
+
}
|
|
61
|
+
return [...currentSelections, value];
|
|
62
|
+
}
|
|
63
|
+
// Single mode - replace selection
|
|
64
|
+
return [value];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if value is selected.
|
|
69
|
+
*/
|
|
70
|
+
export function isValueSelected<T>(selections: T[], value: T): boolean {
|
|
71
|
+
return selections.includes(value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// =============================================================================
|
|
75
|
+
// DISPLAY HELPERS
|
|
76
|
+
// =============================================================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get display label for selected value(s).
|
|
80
|
+
*/
|
|
81
|
+
export function getDisplayLabel<T>(
|
|
82
|
+
options: SelectSheetOption<T>[],
|
|
83
|
+
value: T | T[] | null,
|
|
84
|
+
placeholder: string = "Select..."
|
|
85
|
+
): string {
|
|
86
|
+
if (value === null || value === undefined) {
|
|
87
|
+
return placeholder;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const values = Array.isArray(value) ? value : [value];
|
|
91
|
+
if (values.length === 0) {
|
|
92
|
+
return placeholder;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const labels = values
|
|
96
|
+
.map((v) => options.find((o) => o.value === v)?.label)
|
|
97
|
+
.filter(Boolean);
|
|
98
|
+
|
|
99
|
+
if (labels.length === 0) {
|
|
100
|
+
return placeholder;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (labels.length === 1) {
|
|
104
|
+
return labels[0] as string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return `${labels[0]} +${labels.length - 1} more`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// =============================================================================
|
|
111
|
+
// LAYOUT CALCULATION HELPERS
|
|
112
|
+
// =============================================================================
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Calculate list height based on custom prop or default.
|
|
116
|
+
* If customListHeight is provided, uses that.
|
|
117
|
+
* Otherwise uses the default fixed height from tokens.
|
|
118
|
+
*/
|
|
119
|
+
export function calculateListHeight(
|
|
120
|
+
optionCount: number,
|
|
121
|
+
screenHeight: number,
|
|
122
|
+
customListHeight?: number
|
|
123
|
+
): number {
|
|
124
|
+
if (customListHeight) {
|
|
125
|
+
return customListHeight;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Use fixed default height, but don't exceed viewport height
|
|
129
|
+
return Math.min(SELECT_SHEET_LAYOUT.defaultListHeight, screenHeight);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Filter options by search query.
|
|
134
|
+
*/
|
|
135
|
+
export function filterOptions<T>(
|
|
136
|
+
options: SelectSheetOption<T>[],
|
|
137
|
+
query: string
|
|
138
|
+
): SelectSheetOption<T>[] {
|
|
139
|
+
if (!query.trim()) {
|
|
140
|
+
return options;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const normalizedQuery = query.toLowerCase().trim();
|
|
144
|
+
return options.filter((option) =>
|
|
145
|
+
option.label.toLowerCase().includes(normalizedQuery)
|
|
146
|
+
);
|
|
147
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SelectSheet Component
|
|
3
|
+
*
|
|
4
|
+
* @description Selection dialog for picking from a list of options - Molecule
|
|
5
|
+
*
|
|
6
|
+
* SelectSheet presents a scrollable list of options in a modal dialog.
|
|
7
|
+
* Supports single and multiple selection modes with radio/checkbox indicators.
|
|
8
|
+
* Built on top of the Modal component for consistent overlay behavior.
|
|
9
|
+
*
|
|
10
|
+
* ## Selection Modes
|
|
11
|
+
* | Mode | Indicator | Behavior |
|
|
12
|
+
* |----------|------------|-----------------------------------|
|
|
13
|
+
* | single | RadioButton| Selects one, closes on select |
|
|
14
|
+
* | multiple | Checkbox | Selects many, "Done" to confirm |
|
|
15
|
+
*
|
|
16
|
+
* ## Width Behavior
|
|
17
|
+
* | Viewport | Width |
|
|
18
|
+
* |----------|------------------------------------------|
|
|
19
|
+
* | Phone | Full width minus lg (16px) horizontal |
|
|
20
|
+
* | Tablet | Capped at 480px max-width |
|
|
21
|
+
*
|
|
22
|
+
* ## Positions
|
|
23
|
+
* - `center`: Standard dialog centered on screen (default)
|
|
24
|
+
* - `top`: Anchored to top with slide-down animation
|
|
25
|
+
* - `bottom`: Anchored to bottom with slide-up animation
|
|
26
|
+
*
|
|
27
|
+
* ## Features
|
|
28
|
+
* - Single and multiple selection modes
|
|
29
|
+
* - Radio/checkbox indicators (left or right position)
|
|
30
|
+
* - Optional icons and images per option
|
|
31
|
+
* - Virtualized list (FlatList) for performance
|
|
32
|
+
* - Configurable list height
|
|
33
|
+
* - Built on Modal for consistent overlay UX
|
|
34
|
+
* - Full accessibility support
|
|
35
|
+
* - Memoized for performance
|
|
36
|
+
*
|
|
37
|
+
* @see SelectSheet.types.ts - Type definitions
|
|
38
|
+
* @see SelectSheet.helpers.ts - Pure calculation functions
|
|
39
|
+
* @see SelectSheet.a11y.ts - Accessibility prop generation
|
|
40
|
+
* @see SelectSheetOption.tsx - Individual option row
|
|
41
|
+
* @see tokens/scales.ts - SELECT_SHEET_LAYOUT
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* // Single select
|
|
45
|
+
* <SelectSheet
|
|
46
|
+
* visible={isOpen}
|
|
47
|
+
* onClose={() => setOpen(false)}
|
|
48
|
+
* options={[
|
|
49
|
+
* { value: "usd", label: "US Dollar" },
|
|
50
|
+
* { value: "eur", label: "Euro" },
|
|
51
|
+
* ]}
|
|
52
|
+
* value="usd"
|
|
53
|
+
* onChange={setCurrency}
|
|
54
|
+
* />
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // Multiple select with icons
|
|
58
|
+
* <SelectSheet
|
|
59
|
+
* visible={isOpen}
|
|
60
|
+
* onClose={() => setOpen(false)}
|
|
61
|
+
* mode="multiple"
|
|
62
|
+
* title="Select Categories"
|
|
63
|
+
* options={categories.map(c => ({ value: c.id, label: c.name, icon: c.icon }))}
|
|
64
|
+
* value={selectedIds}
|
|
65
|
+
* onChange={setSelectedIds}
|
|
66
|
+
* position="bottom"
|
|
67
|
+
* />
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
import React, { memo, useCallback } from "react";
|
|
71
|
+
import { useDesignSystem } from "../../../provider";
|
|
72
|
+
import { SELECT_SHEET_LAYOUT } from "../../../tokens/scales";
|
|
73
|
+
import { Button } from "../../actions/Button";
|
|
74
|
+
import { Box, FlatList } from "../../layout";
|
|
75
|
+
import type { ModalPosition } from "../../overlays/Modal";
|
|
76
|
+
import { Modal } from "../../overlays/Modal";
|
|
77
|
+
import type { SelectSheetOption, SelectSheetProps } from "./SelectSheet.types";
|
|
78
|
+
import { SelectSheetOptionRow } from "./SelectSheetOption";
|
|
79
|
+
import { useSelectSheetLogic } from "./useSelectSheetLogic";
|
|
80
|
+
|
|
81
|
+
// =============================================================================
|
|
82
|
+
// DEFAULT VALUES
|
|
83
|
+
// =============================================================================
|
|
84
|
+
|
|
85
|
+
const DEFAULT_TITLE = "Select";
|
|
86
|
+
|
|
87
|
+
// =============================================================================
|
|
88
|
+
// COMPONENT IMPLEMENTATION
|
|
89
|
+
// =============================================================================
|
|
90
|
+
|
|
91
|
+
function SelectSheetComponent<T = string>({
|
|
92
|
+
visible,
|
|
93
|
+
onClose,
|
|
94
|
+
options,
|
|
95
|
+
value,
|
|
96
|
+
onChange,
|
|
97
|
+
mode = "single",
|
|
98
|
+
position = "center",
|
|
99
|
+
title = DEFAULT_TITLE,
|
|
100
|
+
doneText,
|
|
101
|
+
listHeight: customListHeight,
|
|
102
|
+
showDividers = true,
|
|
103
|
+
closeOnSelect,
|
|
104
|
+
indicatorPosition = "right",
|
|
105
|
+
testID,
|
|
106
|
+
accessibilityLabel,
|
|
107
|
+
}: SelectSheetProps<T>) {
|
|
108
|
+
const { labels: t } = useDesignSystem();
|
|
109
|
+
const effectiveDoneText = doneText ?? t.designSystem.selectSheet.doneLabel;
|
|
110
|
+
|
|
111
|
+
const {
|
|
112
|
+
filteredOptions,
|
|
113
|
+
handleOptionPress,
|
|
114
|
+
handleConfirm,
|
|
115
|
+
handleCancel,
|
|
116
|
+
isSelected,
|
|
117
|
+
isMultiple,
|
|
118
|
+
layout,
|
|
119
|
+
sheetA11yProps,
|
|
120
|
+
getOptionA11y,
|
|
121
|
+
} = useSelectSheetLogic({
|
|
122
|
+
value,
|
|
123
|
+
mode,
|
|
124
|
+
visible,
|
|
125
|
+
options,
|
|
126
|
+
onChange,
|
|
127
|
+
onClose,
|
|
128
|
+
listHeight: customListHeight,
|
|
129
|
+
title,
|
|
130
|
+
position,
|
|
131
|
+
accessibilityLabel,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Map SelectSheet position to Modal position
|
|
135
|
+
const modalPosition: ModalPosition = position;
|
|
136
|
+
|
|
137
|
+
// Render option item
|
|
138
|
+
const renderOption = useCallback(
|
|
139
|
+
({ item, index }: { item: SelectSheetOption<T>; index: number }) => (
|
|
140
|
+
<SelectSheetOptionRow
|
|
141
|
+
option={item}
|
|
142
|
+
isSelected={isSelected(item.value)}
|
|
143
|
+
isMultiple={isMultiple}
|
|
144
|
+
onPress={() => handleOptionPress(item.value)}
|
|
145
|
+
showDivider={showDividers && index < filteredOptions.length - 1}
|
|
146
|
+
indicatorPosition={indicatorPosition}
|
|
147
|
+
a11yProps={getOptionA11y({
|
|
148
|
+
label: item.label,
|
|
149
|
+
isSelected: isSelected(item.value),
|
|
150
|
+
isDisabled: item.disabled || false,
|
|
151
|
+
isMultiple,
|
|
152
|
+
index,
|
|
153
|
+
total: filteredOptions.length,
|
|
154
|
+
})}
|
|
155
|
+
testID={testID ? `${testID}-option-${index}` : undefined}
|
|
156
|
+
/>
|
|
157
|
+
),
|
|
158
|
+
[
|
|
159
|
+
isSelected,
|
|
160
|
+
isMultiple,
|
|
161
|
+
handleOptionPress,
|
|
162
|
+
showDividers,
|
|
163
|
+
filteredOptions.length,
|
|
164
|
+
indicatorPosition,
|
|
165
|
+
getOptionA11y,
|
|
166
|
+
testID,
|
|
167
|
+
],
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const keyExtractor = useCallback(
|
|
171
|
+
(item: SelectSheetOption<T>, index: number) =>
|
|
172
|
+
`option-${index}-${String(item.value)}`,
|
|
173
|
+
[],
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const getItemLayout = useCallback(
|
|
177
|
+
(
|
|
178
|
+
_data: ArrayLike<SelectSheetOption<T>> | null | undefined,
|
|
179
|
+
index: number,
|
|
180
|
+
) => ({
|
|
181
|
+
length: SELECT_SHEET_LAYOUT.optionRowHeight,
|
|
182
|
+
offset: SELECT_SHEET_LAYOUT.optionRowHeight * index,
|
|
183
|
+
index,
|
|
184
|
+
}),
|
|
185
|
+
[],
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<Modal
|
|
190
|
+
visible={visible}
|
|
191
|
+
onClose={handleCancel}
|
|
192
|
+
title={title}
|
|
193
|
+
position={modalPosition}
|
|
194
|
+
closeOnBackdropPress
|
|
195
|
+
showCloseButton={true}
|
|
196
|
+
accessibilityLabel={accessibilityLabel}
|
|
197
|
+
>
|
|
198
|
+
<Box testID={testID} {...sheetA11yProps}>
|
|
199
|
+
{/* Options List */}
|
|
200
|
+
<Box style={{ maxHeight: layout.listHeight }}>
|
|
201
|
+
<FlatList
|
|
202
|
+
data={filteredOptions}
|
|
203
|
+
renderItem={renderOption}
|
|
204
|
+
keyExtractor={keyExtractor}
|
|
205
|
+
getItemLayout={getItemLayout}
|
|
206
|
+
showsVerticalScrollIndicator={false}
|
|
207
|
+
bounces={false}
|
|
208
|
+
initialNumToRender={10}
|
|
209
|
+
maxToRenderPerBatch={10}
|
|
210
|
+
windowSize={5}
|
|
211
|
+
/>
|
|
212
|
+
</Box>
|
|
213
|
+
|
|
214
|
+
{/* Footer - Done button for multiple mode */}
|
|
215
|
+
{isMultiple && (
|
|
216
|
+
<Box
|
|
217
|
+
paddingHorizontal="lg"
|
|
218
|
+
paddingVertical="md"
|
|
219
|
+
borderTopWidth={1}
|
|
220
|
+
borderTopColor="borderSubtle"
|
|
221
|
+
flexDirection="row"
|
|
222
|
+
justifyContent="flex-end"
|
|
223
|
+
>
|
|
224
|
+
<Button
|
|
225
|
+
title={effectiveDoneText}
|
|
226
|
+
variant="ghost"
|
|
227
|
+
size="md"
|
|
228
|
+
onPress={handleConfirm}
|
|
229
|
+
testID={testID ? `${testID}-done` : undefined}
|
|
230
|
+
/>
|
|
231
|
+
</Box>
|
|
232
|
+
)}
|
|
233
|
+
</Box>
|
|
234
|
+
</Modal>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// =============================================================================
|
|
239
|
+
// EXPORTS
|
|
240
|
+
// =============================================================================
|
|
241
|
+
|
|
242
|
+
export const SelectSheet = memo(SelectSheetComponent) as <T = string>(
|
|
243
|
+
props: SelectSheetProps<T>,
|
|
244
|
+
) => React.ReactElement;
|
|
245
|
+
|
|
246
|
+
// @ts-expect-error - displayName on generic component
|
|
247
|
+
SelectSheet.displayName = "SelectSheet";
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SelectSheet Component Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the SelectSheet selection dialog component.
|
|
5
|
+
* Follows Modal pattern with consistent width behavior:
|
|
6
|
+
* - Phone: Full width minus lg (16px) horizontal padding
|
|
7
|
+
* - Tablet (≥768px): Capped at 480px max-width (phone-like experience)
|
|
8
|
+
*
|
|
9
|
+
* @see tokens/scales.ts - SELECT_SHEET_LAYOUT
|
|
10
|
+
* @see core/restyle/restyleTheme.ts - selectSheetVariants
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { IconName } from "../../content/Icon";
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// POSITION TYPES
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* SelectSheet position controlling vertical placement.
|
|
21
|
+
*
|
|
22
|
+
* - `center`: Centered vertically (default, standard dialog)
|
|
23
|
+
* - `top`: Anchored to top of screen
|
|
24
|
+
* - `bottom`: Bottom sheet anchored to screen bottom
|
|
25
|
+
*/
|
|
26
|
+
export type SelectSheetPosition = "center" | "top" | "bottom";
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// OPTION TYPES
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Individual option for the SelectSheet
|
|
34
|
+
*/
|
|
35
|
+
export interface SelectSheetOption<T = string> {
|
|
36
|
+
/** Unique value for the option */
|
|
37
|
+
value: T;
|
|
38
|
+
/** Display label */
|
|
39
|
+
label: string;
|
|
40
|
+
/** Optional icon name from MaterialIcons */
|
|
41
|
+
icon?: IconName;
|
|
42
|
+
/** Optional image URL to display */
|
|
43
|
+
imageUrl?: string;
|
|
44
|
+
/** Whether this option is disabled */
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Position of the selection indicator (checkbox/radio button)
|
|
50
|
+
*/
|
|
51
|
+
export type SelectSheetIndicatorPosition = "left" | "right";
|
|
52
|
+
|
|
53
|
+
// =============================================================================
|
|
54
|
+
// MODE TYPES
|
|
55
|
+
// =============================================================================
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Selection mode - single or multiple
|
|
59
|
+
*/
|
|
60
|
+
export type SelectSheetMode = "single" | "multiple";
|
|
61
|
+
|
|
62
|
+
// =============================================================================
|
|
63
|
+
// COMPONENT PROPS
|
|
64
|
+
// =============================================================================
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* SelectSheet component props.
|
|
68
|
+
*
|
|
69
|
+
* @template T - Type of option values (defaults to string)
|
|
70
|
+
*/
|
|
71
|
+
export interface SelectSheetProps<T = string> {
|
|
72
|
+
// -------------------------------------------------------------------------
|
|
73
|
+
// Required Props
|
|
74
|
+
// -------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
/** Whether the sheet is visible */
|
|
77
|
+
visible: boolean;
|
|
78
|
+
/** Callback when sheet should close */
|
|
79
|
+
onClose: () => void;
|
|
80
|
+
/** Available options */
|
|
81
|
+
options: SelectSheetOption<T>[];
|
|
82
|
+
/** Currently selected value(s) */
|
|
83
|
+
value: T | T[] | null;
|
|
84
|
+
/** Callback when selection changes */
|
|
85
|
+
onChange: (value: T | T[] | null) => void;
|
|
86
|
+
|
|
87
|
+
// -------------------------------------------------------------------------
|
|
88
|
+
// Position Props
|
|
89
|
+
// -------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Vertical position of the sheet
|
|
93
|
+
* - "center": Centered modal dialog
|
|
94
|
+
* - "top": Anchored to top of screen
|
|
95
|
+
* - "bottom": Bottom sheet anchored to screen bottom
|
|
96
|
+
* @default "center"
|
|
97
|
+
*/
|
|
98
|
+
position?: SelectSheetPosition;
|
|
99
|
+
|
|
100
|
+
// -------------------------------------------------------------------------
|
|
101
|
+
// Behavior Props
|
|
102
|
+
// -------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Selection mode
|
|
106
|
+
* @default "single"
|
|
107
|
+
*/
|
|
108
|
+
mode?: SelectSheetMode;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Whether to close immediately on single selection
|
|
112
|
+
* @default true (for single mode), false (for multiple mode)
|
|
113
|
+
*/
|
|
114
|
+
closeOnSelect?: boolean;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Position of the selection indicator (checkbox/radio button)
|
|
118
|
+
* @default "right"
|
|
119
|
+
*/
|
|
120
|
+
indicatorPosition?: SelectSheetIndicatorPosition;
|
|
121
|
+
|
|
122
|
+
// -------------------------------------------------------------------------
|
|
123
|
+
// Content Props
|
|
124
|
+
// -------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
/** Dialog/sheet title */
|
|
127
|
+
title?: string;
|
|
128
|
+
/** Done button text (multiple mode) */
|
|
129
|
+
doneText?: string;
|
|
130
|
+
|
|
131
|
+
// -------------------------------------------------------------------------
|
|
132
|
+
// Layout Props
|
|
133
|
+
// -------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Custom height of the list container.
|
|
137
|
+
* If not provided, calculates based on option count (max 70% of viewport).
|
|
138
|
+
*/
|
|
139
|
+
listHeight?: number;
|
|
140
|
+
|
|
141
|
+
/** Whether to show dividers between options */
|
|
142
|
+
showDividers?: boolean;
|
|
143
|
+
|
|
144
|
+
// -------------------------------------------------------------------------
|
|
145
|
+
// Accessibility Props
|
|
146
|
+
// -------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
/** Test ID for testing */
|
|
149
|
+
testID?: string;
|
|
150
|
+
/** Accessibility label */
|
|
151
|
+
accessibilityLabel?: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// =============================================================================
|
|
155
|
+
// INTERNAL TYPES
|
|
156
|
+
// =============================================================================
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Internal hook state for managing temporary selections.
|
|
160
|
+
*/
|
|
161
|
+
export interface SelectSheetState<T = string> {
|
|
162
|
+
/** Temporary selections before confirming (multiple mode) */
|
|
163
|
+
tempSelections: T[];
|
|
164
|
+
/** Search query for filtering options */
|
|
165
|
+
searchQuery: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Option row component props.
|
|
170
|
+
*/
|
|
171
|
+
export interface SelectSheetOptionRowProps<T = string> {
|
|
172
|
+
/** Option data */
|
|
173
|
+
option: SelectSheetOption<T>;
|
|
174
|
+
/** Whether this option is selected */
|
|
175
|
+
isSelected: boolean;
|
|
176
|
+
/** Whether multiple selection is enabled */
|
|
177
|
+
isMultiple: boolean;
|
|
178
|
+
/** Callback when option is pressed */
|
|
179
|
+
onPress: () => void;
|
|
180
|
+
/** Whether to show bottom divider */
|
|
181
|
+
showDivider: boolean;
|
|
182
|
+
/** Position of the selection indicator */
|
|
183
|
+
indicatorPosition: SelectSheetIndicatorPosition;
|
|
184
|
+
/** Pre-computed accessibility props from hook */
|
|
185
|
+
a11yProps: import("react-native").AccessibilityProps;
|
|
186
|
+
/** Test ID for testing */
|
|
187
|
+
testID?: string;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Computed layout values from hook.
|
|
192
|
+
*/
|
|
193
|
+
export interface SelectSheetLayout {
|
|
194
|
+
/** Calculated list height */
|
|
195
|
+
listHeight: number;
|
|
196
|
+
}
|