@proyecto-viviana/solidaria-components 0.2.5 → 0.3.0
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/LICENSE +21 -0
- package/README.md +39 -272
- package/dist/ActionBar.d.ts +79 -0
- package/dist/ActionBar.d.ts.map +1 -0
- package/dist/ActionGroup.d.ts +74 -0
- package/dist/ActionGroup.d.ts.map +1 -0
- package/dist/Alert.d.ts +70 -0
- package/dist/Alert.d.ts.map +1 -0
- package/dist/Autocomplete.d.ts +5 -5
- package/dist/Autocomplete.d.ts.map +1 -1
- package/dist/Breadcrumbs.d.ts +27 -8
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Button.d.ts +28 -5
- package/dist/Button.d.ts.map +1 -1
- package/dist/Calendar.d.ts +51 -7
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +33 -8
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Collection.d.ts +130 -0
- package/dist/Collection.d.ts.map +1 -0
- package/dist/Color.d.ts +210 -9
- package/dist/Color.d.ts.map +1 -1
- package/dist/ColorEditor.d.ts +42 -0
- package/dist/ColorEditor.d.ts.map +1 -0
- package/dist/ComboBox.d.ts +146 -16
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/ContextualHelpTrigger.d.ts +40 -0
- package/dist/ContextualHelpTrigger.d.ts.map +1 -0
- package/dist/DateField.d.ts +35 -8
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DatePicker.d.ts +101 -5
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/DateRangePickerContext.d.ts +30 -0
- package/dist/DateRangePickerContext.d.ts.map +1 -0
- package/dist/Dialog.d.ts +5 -5
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Disclosure.d.ts +25 -5
- package/dist/Disclosure.d.ts.map +1 -1
- package/dist/DragAndDrop.d.ts +80 -0
- package/dist/DragAndDrop.d.ts.map +1 -0
- package/dist/DragPreview.d.ts +14 -0
- package/dist/DragPreview.d.ts.map +1 -0
- package/dist/DropZone.d.ts +27 -0
- package/dist/DropZone.d.ts.map +1 -0
- package/dist/FieldError.d.ts +27 -0
- package/dist/FieldError.d.ts.map +1 -0
- package/dist/FileTrigger.d.ts +26 -0
- package/dist/FileTrigger.d.ts.map +1 -0
- package/dist/Focusable.d.ts +27 -0
- package/dist/Focusable.d.ts.map +1 -0
- package/dist/Form.d.ts +41 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/GridList.d.ts +69 -10
- package/dist/GridList.d.ts.map +1 -1
- package/dist/HiddenDateInput.d.ts +26 -0
- package/dist/HiddenDateInput.d.ts.map +1 -0
- package/dist/HiddenTimeInput.d.ts +25 -0
- package/dist/HiddenTimeInput.d.ts.map +1 -0
- package/dist/Icon.d.ts +57 -0
- package/dist/Icon.d.ts.map +1 -0
- package/dist/Keyboard.d.ts +13 -0
- package/dist/Keyboard.d.ts.map +1 -0
- package/dist/Landmark.d.ts +3 -3
- package/dist/Landmark.d.ts.map +1 -1
- package/dist/Link.d.ts +10 -4
- package/dist/Link.d.ts.map +1 -1
- package/dist/ListBox.d.ts +73 -11
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/ListDropTargetDelegate.d.ts +38 -0
- package/dist/ListDropTargetDelegate.d.ts.map +1 -0
- package/dist/Menu.d.ts +79 -10
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Meter.d.ts +4 -4
- package/dist/Meter.d.ts.map +1 -1
- package/dist/Modal.d.ts +6 -4
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts +10 -12
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts +32 -7
- package/dist/Popover.d.ts.map +1 -1
- package/dist/Pressable.d.ts +27 -0
- package/dist/Pressable.d.ts.map +1 -0
- package/dist/ProgressBar.d.ts +6 -4
- package/dist/ProgressBar.d.ts.map +1 -1
- package/dist/RadioGroup.d.ts +43 -9
- package/dist/RadioGroup.d.ts.map +1 -1
- package/dist/RangeCalendar.d.ts +39 -7
- package/dist/RangeCalendar.d.ts.map +1 -1
- package/dist/RouterProvider.d.ts +75 -0
- package/dist/RouterProvider.d.ts.map +1 -0
- package/dist/SearchField.d.ts +23 -21
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/Select.d.ts +48 -7
- package/dist/Select.d.ts.map +1 -1
- package/dist/SelectionIndicator.d.ts +30 -0
- package/dist/SelectionIndicator.d.ts.map +1 -0
- package/dist/Separator.d.ts +9 -3
- package/dist/Separator.d.ts.map +1 -1
- package/dist/SharedElementTransition.d.ts +41 -0
- package/dist/SharedElementTransition.d.ts.map +1 -0
- package/dist/Slider.d.ts +15 -8
- package/dist/Slider.d.ts.map +1 -1
- package/dist/StepList.d.ts +90 -0
- package/dist/StepList.d.ts.map +1 -0
- package/dist/Switch.d.ts +11 -5
- package/dist/Switch.d.ts.map +1 -1
- package/dist/Table.d.ts +222 -19
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +47 -10
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/TagGroup.d.ts +22 -10
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/Text.d.ts +10 -0
- package/dist/Text.d.ts.map +1 -0
- package/dist/TextField.d.ts +19 -11
- package/dist/TextField.d.ts.map +1 -1
- package/dist/TimeField.d.ts +32 -7
- package/dist/TimeField.d.ts.map +1 -1
- package/dist/Toast.d.ts +29 -14
- package/dist/Toast.d.ts.map +1 -1
- package/dist/ToggleButton.d.ts +36 -0
- package/dist/ToggleButton.d.ts.map +1 -0
- package/dist/ToggleButtonGroup.d.ts +33 -0
- package/dist/ToggleButtonGroup.d.ts.map +1 -0
- package/dist/Toolbar.d.ts +7 -3
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Tooltip.d.ts +58 -7
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tree.d.ts +102 -11
- package/dist/Tree.d.ts.map +1 -1
- package/dist/Virtualizer.d.ts +61 -0
- package/dist/Virtualizer.d.ts.map +1 -0
- package/dist/VirtualizerLayouts.d.ts +82 -0
- package/dist/VirtualizerLayouts.d.ts.map +1 -0
- package/dist/VisuallyHidden.d.ts +4 -2
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/contexts.d.ts +6 -1
- package/dist/contexts.d.ts.map +1 -1
- package/dist/index.d.ts +73 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23342 -10644
- package/dist/index.js.map +1 -7
- package/dist/index.jsx +18110 -0
- package/dist/index.jsx.map +1 -0
- package/dist/useDragAndDrop.d.ts +93 -0
- package/dist/useDragAndDrop.d.ts.map +1 -0
- package/dist/utils.d.ts +8 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/virtualizer/Layout.d.ts +79 -0
- package/dist/virtualizer/Layout.d.ts.map +1 -0
- package/package.json +33 -32
- package/src/ActionBar.tsx +251 -0
- package/src/ActionGroup.tsx +277 -0
- package/src/Alert.tsx +152 -0
- package/src/Autocomplete.tsx +39 -44
- package/src/Breadcrumbs.tsx +227 -72
- package/src/Button.tsx +315 -74
- package/src/Calendar.tsx +347 -141
- package/src/Checkbox.tsx +414 -123
- package/src/Collection.tsx +350 -0
- package/src/Color.tsx +1325 -284
- package/src/ColorEditor.tsx +213 -0
- package/src/ComboBox.tsx +644 -245
- package/src/ContextualHelpTrigger.tsx +195 -0
- package/src/DateField.tsx +274 -106
- package/src/DatePicker.tsx +892 -111
- package/src/DateRangePickerContext.tsx +44 -0
- package/src/Dialog.tsx +173 -104
- package/src/Disclosure.tsx +158 -105
- package/src/DragAndDrop.tsx +340 -0
- package/src/DragPreview.tsx +47 -0
- package/src/DropZone.tsx +233 -0
- package/src/FieldError.tsx +89 -0
- package/src/FileTrigger.tsx +83 -0
- package/src/Focusable.tsx +103 -0
- package/src/Form.tsx +140 -0
- package/src/GridList.tsx +542 -128
- package/src/HiddenDateInput.tsx +153 -0
- package/src/HiddenTimeInput.tsx +133 -0
- package/src/Icon.tsx +133 -0
- package/src/Keyboard.tsx +26 -0
- package/src/Landmark.tsx +37 -63
- package/src/Link.tsx +132 -69
- package/src/ListBox.tsx +656 -106
- package/src/ListDropTargetDelegate.ts +283 -0
- package/src/Menu.tsx +1234 -132
- package/src/Meter.tsx +44 -58
- package/src/Modal.tsx +262 -166
- package/src/NumberField.tsx +267 -151
- package/src/Popover.tsx +452 -343
- package/src/Pressable.tsx +108 -0
- package/src/ProgressBar.tsx +54 -59
- package/src/RadioGroup.tsx +533 -121
- package/src/RangeCalendar.tsx +249 -150
- package/src/RouterProvider.tsx +223 -0
- package/src/SearchField.tsx +460 -133
- package/src/Select.tsx +804 -233
- package/src/SelectionIndicator.tsx +108 -0
- package/src/Separator.tsx +47 -49
- package/src/SharedElementTransition.tsx +264 -0
- package/src/Slider.tsx +148 -98
- package/src/StepList.tsx +272 -0
- package/src/Switch.tsx +93 -46
- package/src/Table.tsx +1551 -225
- package/src/Tabs.tsx +377 -123
- package/src/TagGroup.tsx +233 -135
- package/src/Text.tsx +18 -0
- package/src/TextField.tsx +413 -86
- package/src/TimeField.tsx +232 -222
- package/src/Toast.tsx +306 -160
- package/src/ToggleButton.tsx +169 -0
- package/src/ToggleButtonGroup.tsx +141 -0
- package/src/Toolbar.tsx +61 -70
- package/src/Tooltip.tsx +473 -116
- package/src/Tree.tsx +1514 -175
- package/src/Virtualizer.tsx +730 -0
- package/src/VirtualizerLayouts.ts +280 -0
- package/src/VisuallyHidden.tsx +32 -38
- package/src/contexts.ts +29 -36
- package/src/index.ts +972 -620
- package/src/useDragAndDrop.ts +367 -0
- package/src/utils.tsx +69 -50
- package/src/virtualizer/Layout.ts +192 -0
- package/dist/index.ssr.js +0 -9785
- package/dist/index.ssr.js.map +0 -7
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HiddenDateInput component for solidaria-components
|
|
3
|
+
*
|
|
4
|
+
* A hidden native date/datetime-local input for form submission.
|
|
5
|
+
* Renders server-side with the initial value for SSR safety.
|
|
6
|
+
*/
|
|
7
|
+
import { type JSX, createEffect, createSignal } from "solid-js";
|
|
8
|
+
import { type DateValue, type FormValidationState } from "@proyecto-viviana/solid-stately";
|
|
9
|
+
import { createFormValidation } from "@proyecto-viviana/solidaria";
|
|
10
|
+
|
|
11
|
+
type MaybeAccessor<T> = T | (() => T);
|
|
12
|
+
|
|
13
|
+
export interface HiddenDateInputProps {
|
|
14
|
+
name?: string;
|
|
15
|
+
form?: string;
|
|
16
|
+
value?: MaybeAccessor<DateValue | null | undefined>;
|
|
17
|
+
autoComplete?: string;
|
|
18
|
+
isDisabled?: boolean;
|
|
19
|
+
isRequired?: boolean;
|
|
20
|
+
validationBehavior?: "aria" | "native";
|
|
21
|
+
validationState?: FormValidationState;
|
|
22
|
+
focus?: () => void;
|
|
23
|
+
minValue?: MaybeAccessor<DateValue | undefined>;
|
|
24
|
+
maxValue?: MaybeAccessor<DateValue | undefined>;
|
|
25
|
+
granularity?: "day" | "hour" | "minute" | "second";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function accessValue<T>(value: MaybeAccessor<T> | undefined): T | undefined {
|
|
29
|
+
return typeof value === "function" ? (value as () => T)() : value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatDateValue(
|
|
33
|
+
value: DateValue | undefined | null,
|
|
34
|
+
granularity: "day" | "hour" | "minute" | "second",
|
|
35
|
+
): string {
|
|
36
|
+
if (!value) return "";
|
|
37
|
+
|
|
38
|
+
if ("timeZone" in value) {
|
|
39
|
+
return String(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const dateValue = value;
|
|
43
|
+
|
|
44
|
+
const year = String(dateValue.year).padStart(4, "0");
|
|
45
|
+
const month = String(dateValue.month).padStart(2, "0");
|
|
46
|
+
const day = String(dateValue.day).padStart(2, "0");
|
|
47
|
+
|
|
48
|
+
if (granularity === "day") {
|
|
49
|
+
return `${year}-${month}-${day}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const hasHour = "hour" in dateValue;
|
|
53
|
+
const hour = hasHour ? String((dateValue as { hour: number }).hour).padStart(2, "0") : "00";
|
|
54
|
+
const hasMinute = hasHour && "minute" in dateValue;
|
|
55
|
+
const minute = hasMinute
|
|
56
|
+
? String((dateValue as { minute: number }).minute).padStart(2, "0")
|
|
57
|
+
: "00";
|
|
58
|
+
|
|
59
|
+
if (granularity === "second" && hasMinute && "second" in dateValue) {
|
|
60
|
+
const second = String((dateValue as { second: number }).second).padStart(2, "0");
|
|
61
|
+
return `${year}-${month}-${day}T${hour}:${minute}:${second}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return `${year}-${month}-${day}T${hour}:${minute}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function HiddenDateInput(props: HiddenDateInputProps): JSX.Element {
|
|
68
|
+
const granularity = () => props.granularity ?? "day";
|
|
69
|
+
const hasValidationBehavior = () => props.validationBehavior != null;
|
|
70
|
+
const validationBehavior = () => props.validationBehavior;
|
|
71
|
+
const usesNativeValidation = () => validationBehavior() === "native";
|
|
72
|
+
const value = () => accessValue(props.value);
|
|
73
|
+
const minValue = () => accessValue(props.minValue);
|
|
74
|
+
const maxValue = () => accessValue(props.maxValue);
|
|
75
|
+
const hasTimeZoneValue = () =>
|
|
76
|
+
Boolean(
|
|
77
|
+
(value() && "timeZone" in value()!) ||
|
|
78
|
+
(minValue() && "timeZone" in minValue()!) ||
|
|
79
|
+
(maxValue() && "timeZone" in maxValue()!),
|
|
80
|
+
);
|
|
81
|
+
const inputType = () =>
|
|
82
|
+
usesNativeValidation()
|
|
83
|
+
? "text"
|
|
84
|
+
: hasValidationBehavior()
|
|
85
|
+
? "hidden"
|
|
86
|
+
: hasTimeZoneValue()
|
|
87
|
+
? "hidden"
|
|
88
|
+
: granularity() === "day"
|
|
89
|
+
? "date"
|
|
90
|
+
: "datetime-local";
|
|
91
|
+
const formattedValue = () => formatDateValue(value(), granularity());
|
|
92
|
+
const formattedMin = () => formatDateValue(minValue(), granularity());
|
|
93
|
+
const formattedMax = () => formatDateValue(maxValue(), granularity());
|
|
94
|
+
|
|
95
|
+
const [inputRef, setInputRef] = createSignal<HTMLInputElement>();
|
|
96
|
+
|
|
97
|
+
if (props.validationState) {
|
|
98
|
+
createFormValidation(
|
|
99
|
+
{
|
|
100
|
+
get validationBehavior() {
|
|
101
|
+
return validationBehavior();
|
|
102
|
+
},
|
|
103
|
+
get focus() {
|
|
104
|
+
return props.focus;
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
props.validationState,
|
|
108
|
+
inputRef,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
createEffect(() => {
|
|
113
|
+
const val = formattedValue();
|
|
114
|
+
const input = inputRef();
|
|
115
|
+
if (input && input.value !== val) {
|
|
116
|
+
input.value = val;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<input
|
|
122
|
+
ref={(el) => {
|
|
123
|
+
setInputRef(el);
|
|
124
|
+
}}
|
|
125
|
+
type={inputType()}
|
|
126
|
+
name={props.name}
|
|
127
|
+
form={props.form}
|
|
128
|
+
value={formattedValue()}
|
|
129
|
+
autocomplete={props.autoComplete}
|
|
130
|
+
disabled={props.isDisabled}
|
|
131
|
+
required={usesNativeValidation() && props.isRequired ? true : undefined}
|
|
132
|
+
hidden={usesNativeValidation() || undefined}
|
|
133
|
+
min={hasValidationBehavior() ? undefined : formattedMin() || undefined}
|
|
134
|
+
max={hasValidationBehavior() ? undefined : formattedMax() || undefined}
|
|
135
|
+
onChange={usesNativeValidation() ? () => {} : undefined}
|
|
136
|
+
tabIndex={-1}
|
|
137
|
+
aria-hidden="true"
|
|
138
|
+
style={
|
|
139
|
+
{
|
|
140
|
+
position: "absolute",
|
|
141
|
+
width: "1px",
|
|
142
|
+
height: "1px",
|
|
143
|
+
padding: "0",
|
|
144
|
+
margin: "-1px",
|
|
145
|
+
overflow: "hidden",
|
|
146
|
+
clip: "rect(0, 0, 0, 0)",
|
|
147
|
+
"white-space": "nowrap",
|
|
148
|
+
"border-width": "0",
|
|
149
|
+
} as JSX.CSSProperties
|
|
150
|
+
}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HiddenTimeInput component for solidaria-components
|
|
3
|
+
*
|
|
4
|
+
* A hidden native time input for form submission.
|
|
5
|
+
*/
|
|
6
|
+
import { type JSX, createEffect, createSignal } from "solid-js";
|
|
7
|
+
import { type FormValidationState, type TimeValue } from "@proyecto-viviana/solid-stately";
|
|
8
|
+
import { createFormValidation } from "@proyecto-viviana/solidaria";
|
|
9
|
+
|
|
10
|
+
type MaybeAccessor<T> = T | (() => T);
|
|
11
|
+
|
|
12
|
+
export interface HiddenTimeInputProps {
|
|
13
|
+
name?: string;
|
|
14
|
+
form?: string;
|
|
15
|
+
value?: MaybeAccessor<TimeValue | null | undefined>;
|
|
16
|
+
autoComplete?: string;
|
|
17
|
+
isDisabled?: boolean;
|
|
18
|
+
isRequired?: boolean;
|
|
19
|
+
validationBehavior?: "aria" | "native";
|
|
20
|
+
validationState?: FormValidationState;
|
|
21
|
+
focus?: () => void;
|
|
22
|
+
minValue?: MaybeAccessor<TimeValue | undefined>;
|
|
23
|
+
maxValue?: MaybeAccessor<TimeValue | undefined>;
|
|
24
|
+
granularity?: "hour" | "minute" | "second";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function accessValue<T>(value: MaybeAccessor<T> | undefined): T | undefined {
|
|
28
|
+
return typeof value === "function" ? (value as () => T)() : value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatTimeValue(
|
|
32
|
+
value: TimeValue | undefined | null,
|
|
33
|
+
granularity: "hour" | "minute" | "second",
|
|
34
|
+
): string {
|
|
35
|
+
if (!value) return "";
|
|
36
|
+
|
|
37
|
+
const hour = String(value.hour).padStart(2, "0");
|
|
38
|
+
const minute = String(granularity === "hour" ? 0 : value.minute).padStart(2, "0");
|
|
39
|
+
|
|
40
|
+
if (granularity === "second") {
|
|
41
|
+
const second = String(value.second).padStart(2, "0");
|
|
42
|
+
return `${hour}:${minute}:${second}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return `${hour}:${minute}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function stepForGranularity(granularity: "hour" | "minute" | "second"): number {
|
|
49
|
+
switch (granularity) {
|
|
50
|
+
case "hour":
|
|
51
|
+
return 3600;
|
|
52
|
+
case "second":
|
|
53
|
+
return 1;
|
|
54
|
+
case "minute":
|
|
55
|
+
default:
|
|
56
|
+
return 60;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function HiddenTimeInput(props: HiddenTimeInputProps): JSX.Element {
|
|
61
|
+
const granularity = () => props.granularity ?? "minute";
|
|
62
|
+
const hasValidationBehavior = () => props.validationBehavior != null;
|
|
63
|
+
const validationBehavior = () => props.validationBehavior;
|
|
64
|
+
const usesNativeValidation = () => validationBehavior() === "native";
|
|
65
|
+
const value = () => accessValue(props.value);
|
|
66
|
+
const minValue = () => accessValue(props.minValue);
|
|
67
|
+
const maxValue = () => accessValue(props.maxValue);
|
|
68
|
+
const inputType = () =>
|
|
69
|
+
usesNativeValidation() ? "text" : hasValidationBehavior() ? "hidden" : "time";
|
|
70
|
+
const formattedValue = () => formatTimeValue(value(), granularity());
|
|
71
|
+
const formattedMin = () => formatTimeValue(minValue(), granularity());
|
|
72
|
+
const formattedMax = () => formatTimeValue(maxValue(), granularity());
|
|
73
|
+
|
|
74
|
+
const [inputRef, setInputRef] = createSignal<HTMLInputElement>();
|
|
75
|
+
|
|
76
|
+
if (props.validationState) {
|
|
77
|
+
createFormValidation(
|
|
78
|
+
{
|
|
79
|
+
get validationBehavior() {
|
|
80
|
+
return validationBehavior();
|
|
81
|
+
},
|
|
82
|
+
get focus() {
|
|
83
|
+
return props.focus;
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
props.validationState,
|
|
87
|
+
inputRef,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
createEffect(() => {
|
|
92
|
+
const val = formattedValue();
|
|
93
|
+
const input = inputRef();
|
|
94
|
+
if (input && input.value !== val) {
|
|
95
|
+
input.value = val;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<input
|
|
101
|
+
ref={(el) => {
|
|
102
|
+
setInputRef(el);
|
|
103
|
+
}}
|
|
104
|
+
type={inputType()}
|
|
105
|
+
name={props.name}
|
|
106
|
+
form={props.form}
|
|
107
|
+
value={formattedValue()}
|
|
108
|
+
autocomplete={props.autoComplete}
|
|
109
|
+
disabled={props.isDisabled}
|
|
110
|
+
required={usesNativeValidation() && props.isRequired ? true : undefined}
|
|
111
|
+
hidden={usesNativeValidation() || undefined}
|
|
112
|
+
min={hasValidationBehavior() ? undefined : formattedMin() || undefined}
|
|
113
|
+
max={hasValidationBehavior() ? undefined : formattedMax() || undefined}
|
|
114
|
+
step={hasValidationBehavior() ? undefined : stepForGranularity(granularity())}
|
|
115
|
+
onChange={usesNativeValidation() ? () => {} : undefined}
|
|
116
|
+
tabIndex={-1}
|
|
117
|
+
aria-hidden="true"
|
|
118
|
+
style={
|
|
119
|
+
{
|
|
120
|
+
position: "absolute",
|
|
121
|
+
width: "1px",
|
|
122
|
+
height: "1px",
|
|
123
|
+
padding: "0",
|
|
124
|
+
margin: "-1px",
|
|
125
|
+
overflow: "hidden",
|
|
126
|
+
clip: "rect(0, 0, 0, 0)",
|
|
127
|
+
"white-space": "nowrap",
|
|
128
|
+
"border-width": "0",
|
|
129
|
+
} as JSX.CSSProperties
|
|
130
|
+
}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
}
|
package/src/Icon.tsx
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon component for solidaria-components
|
|
3
|
+
*
|
|
4
|
+
* Minimal headless Icon that owns ARIA semantics:
|
|
5
|
+
* - Decorative (no label): aria-hidden="true"
|
|
6
|
+
* - Informative (aria-label): role="img" + aria-label
|
|
7
|
+
* - Interactive (onPress): wraps content in headless Button
|
|
8
|
+
*
|
|
9
|
+
* The UI layer consumes this for styling/composition only.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { type JSX, createContext, createMemo, Show, splitProps } from "solid-js";
|
|
13
|
+
import {
|
|
14
|
+
type RenderChildren,
|
|
15
|
+
type ClassNameOrFunction,
|
|
16
|
+
type StyleOrFunction,
|
|
17
|
+
type SlotProps,
|
|
18
|
+
useRenderProps,
|
|
19
|
+
filterDOMProps,
|
|
20
|
+
} from "./utils";
|
|
21
|
+
import { Button } from "./Button";
|
|
22
|
+
import type { PressEvent } from "@proyecto-viviana/solidaria";
|
|
23
|
+
|
|
24
|
+
export interface IconRenderProps {
|
|
25
|
+
/** Whether the icon is purely decorative (no label). */
|
|
26
|
+
isDecorative: boolean;
|
|
27
|
+
/** Whether the icon is interactive (has onPress). */
|
|
28
|
+
isInteractive: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface IconProps extends SlotProps {
|
|
32
|
+
/** Handler called when the icon is pressed (makes it interactive). */
|
|
33
|
+
onPress?: (e: PressEvent) => void;
|
|
34
|
+
/** Accessible label for the icon. When provided, the icon is informative (role="img"). */
|
|
35
|
+
"aria-label"?: string;
|
|
36
|
+
/** ID of an element that labels this icon. */
|
|
37
|
+
"aria-labelledby"?: string;
|
|
38
|
+
/** The children of the component. A function may be provided to receive render props. */
|
|
39
|
+
children?: RenderChildren<IconRenderProps>;
|
|
40
|
+
/** The CSS className for the element. */
|
|
41
|
+
class?: ClassNameOrFunction<IconRenderProps>;
|
|
42
|
+
/** The inline style for the element. */
|
|
43
|
+
style?: StyleOrFunction<IconRenderProps>;
|
|
44
|
+
/** The id of the element. */
|
|
45
|
+
id?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const IconContext = createContext<IconProps | null>(null);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* An icon wrapper that provides correct ARIA semantics.
|
|
52
|
+
*
|
|
53
|
+
* - **Decorative** (no `aria-label`): renders `<span aria-hidden="true">`
|
|
54
|
+
* - **Informative** (`aria-label` provided): renders `<span role="img" aria-label="...">`
|
|
55
|
+
* - **Interactive** (`onPress` provided): wraps in headless `Button`
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```tsx
|
|
59
|
+
* // Decorative
|
|
60
|
+
* <Icon><SearchSvg /></Icon>
|
|
61
|
+
*
|
|
62
|
+
* // Informative
|
|
63
|
+
* <Icon aria-label="Search"><SearchSvg /></Icon>
|
|
64
|
+
*
|
|
65
|
+
* // Interactive
|
|
66
|
+
* <Icon onPress={handleSearch} aria-label="Search"><SearchSvg /></Icon>
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function Icon(props: IconProps): JSX.Element {
|
|
70
|
+
const [local, rest] = splitProps(props, [
|
|
71
|
+
"children",
|
|
72
|
+
"class",
|
|
73
|
+
"style",
|
|
74
|
+
"slot",
|
|
75
|
+
"onPress",
|
|
76
|
+
"aria-label",
|
|
77
|
+
"aria-labelledby",
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
const hasLabel = () => !!(local["aria-label"] || local["aria-labelledby"]);
|
|
81
|
+
const isInteractive = () => !!local.onPress;
|
|
82
|
+
const isDecorative = () => !hasLabel();
|
|
83
|
+
|
|
84
|
+
const renderValues = createMemo<IconRenderProps>(() => ({
|
|
85
|
+
isDecorative: isDecorative(),
|
|
86
|
+
isInteractive: isInteractive(),
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
const renderProps = useRenderProps(
|
|
90
|
+
{
|
|
91
|
+
children: local.children,
|
|
92
|
+
class: local.class,
|
|
93
|
+
style: local.style,
|
|
94
|
+
defaultClassName: "solidaria-Icon",
|
|
95
|
+
},
|
|
96
|
+
renderValues,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const domProps = createMemo(() => filterDOMProps(rest, { global: true }));
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<Show
|
|
103
|
+
when={isInteractive()}
|
|
104
|
+
fallback={
|
|
105
|
+
<span
|
|
106
|
+
{...domProps()}
|
|
107
|
+
role={hasLabel() ? "img" : undefined}
|
|
108
|
+
aria-hidden={isDecorative() ? "true" : undefined}
|
|
109
|
+
aria-label={local["aria-label"]}
|
|
110
|
+
aria-labelledby={local["aria-labelledby"]}
|
|
111
|
+
class={renderProps.class()}
|
|
112
|
+
style={renderProps.style()}
|
|
113
|
+
data-interactive={undefined}
|
|
114
|
+
data-decorative={isDecorative() || undefined}
|
|
115
|
+
>
|
|
116
|
+
{renderProps.renderChildren()}
|
|
117
|
+
</span>
|
|
118
|
+
}
|
|
119
|
+
>
|
|
120
|
+
<Button
|
|
121
|
+
{...domProps()}
|
|
122
|
+
onPress={local.onPress}
|
|
123
|
+
aria-label={local["aria-label"]}
|
|
124
|
+
aria-labelledby={local["aria-labelledby"]}
|
|
125
|
+
class={renderProps.class()}
|
|
126
|
+
style={renderProps.style()}
|
|
127
|
+
data-interactive=""
|
|
128
|
+
>
|
|
129
|
+
{renderProps.renderChildren()}
|
|
130
|
+
</Button>
|
|
131
|
+
</Show>
|
|
132
|
+
);
|
|
133
|
+
}
|
package/src/Keyboard.tsx
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard primitive for solidaria-components.
|
|
3
|
+
*
|
|
4
|
+
* Displays keyboard key hints with semantic <kbd> markup.
|
|
5
|
+
* Port direction: react-aria-components/src/Keyboard.tsx
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type JSX, createContext, splitProps, useContext } from "solid-js";
|
|
9
|
+
|
|
10
|
+
export interface KeyboardProps extends JSX.HTMLAttributes<HTMLElement> {
|
|
11
|
+
children?: JSX.Element;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const KeyboardContext = createContext<KeyboardProps | null>(null);
|
|
15
|
+
|
|
16
|
+
export function Keyboard(props: KeyboardProps): JSX.Element {
|
|
17
|
+
const context = useContext(KeyboardContext);
|
|
18
|
+
const merged = () => ({ ...context, ...props });
|
|
19
|
+
const [local, domProps] = splitProps(merged(), ["children"]);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<kbd dir="ltr" {...domProps}>
|
|
23
|
+
{local.children}
|
|
24
|
+
</kbd>
|
|
25
|
+
);
|
|
26
|
+
}
|
package/src/Landmark.tsx
CHANGED
|
@@ -5,38 +5,23 @@
|
|
|
5
5
|
* Enables F6 keyboard navigation between major page sections.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
createContext,
|
|
11
|
-
createMemo,
|
|
12
|
-
createSignal,
|
|
13
|
-
splitProps,
|
|
14
|
-
} from 'solid-js';
|
|
15
|
-
import { Dynamic } from 'solid-js/web';
|
|
8
|
+
import { type JSX, createContext, createMemo, createSignal, splitProps } from "solid-js";
|
|
9
|
+
import { Dynamic } from "solid-js/web";
|
|
16
10
|
import {
|
|
17
11
|
createLandmark,
|
|
18
12
|
getLandmarkController,
|
|
19
13
|
type AriaLandmarkProps,
|
|
20
14
|
type AriaLandmarkRole,
|
|
21
15
|
type LandmarkController,
|
|
22
|
-
} from
|
|
23
|
-
import {
|
|
24
|
-
type SlotProps,
|
|
25
|
-
filterDOMProps,
|
|
26
|
-
} from './utils';
|
|
27
|
-
|
|
28
|
-
// ============================================
|
|
29
|
-
// TYPES
|
|
30
|
-
// ============================================
|
|
16
|
+
} from "@proyecto-viviana/solidaria";
|
|
17
|
+
import { type SlotProps, filterDOMProps } from "./utils";
|
|
31
18
|
|
|
32
19
|
export interface LandmarkRenderProps {
|
|
33
20
|
/** The ARIA landmark role. */
|
|
34
21
|
role: AriaLandmarkRole;
|
|
35
22
|
}
|
|
36
23
|
|
|
37
|
-
export interface LandmarkProps
|
|
38
|
-
extends AriaLandmarkProps,
|
|
39
|
-
SlotProps {
|
|
24
|
+
export interface LandmarkProps extends AriaLandmarkProps, SlotProps {
|
|
40
25
|
/**
|
|
41
26
|
* The HTML element type to render.
|
|
42
27
|
* @default 'div' (or semantic element based on role)
|
|
@@ -50,38 +35,25 @@ export interface LandmarkProps
|
|
|
50
35
|
children?: JSX.Element;
|
|
51
36
|
}
|
|
52
37
|
|
|
53
|
-
// Re-export types
|
|
54
38
|
export type { AriaLandmarkRole, LandmarkController };
|
|
55
39
|
|
|
56
|
-
// ============================================
|
|
57
|
-
// CONTEXT
|
|
58
|
-
// ============================================
|
|
59
|
-
|
|
60
40
|
export const LandmarkContext = createContext<LandmarkProps | null>(null);
|
|
61
41
|
|
|
62
|
-
// ============================================
|
|
63
|
-
// SEMANTIC ELEMENT MAPPING
|
|
64
|
-
// ============================================
|
|
65
|
-
|
|
66
42
|
/**
|
|
67
43
|
* Maps ARIA landmark roles to their semantic HTML elements.
|
|
68
44
|
* Using semantic elements is preferred when possible.
|
|
69
45
|
*/
|
|
70
46
|
const roleToSemanticElement: Partial<Record<AriaLandmarkRole, keyof JSX.IntrinsicElements>> = {
|
|
71
|
-
main:
|
|
72
|
-
navigation:
|
|
73
|
-
search:
|
|
74
|
-
banner:
|
|
75
|
-
contentinfo:
|
|
76
|
-
complementary:
|
|
77
|
-
form:
|
|
78
|
-
region:
|
|
47
|
+
main: "main",
|
|
48
|
+
navigation: "nav",
|
|
49
|
+
search: "search", // HTML5.3 <search> element
|
|
50
|
+
banner: "header",
|
|
51
|
+
contentinfo: "footer",
|
|
52
|
+
complementary: "aside",
|
|
53
|
+
form: "form",
|
|
54
|
+
region: "section",
|
|
79
55
|
};
|
|
80
56
|
|
|
81
|
-
// ============================================
|
|
82
|
-
// LANDMARK COMPONENT
|
|
83
|
-
// ============================================
|
|
84
|
-
|
|
85
57
|
/**
|
|
86
58
|
* A landmark is a region of the page that helps screen reader users navigate.
|
|
87
59
|
* Press F6 to cycle through landmarks, or Shift+F6 to go backwards.
|
|
@@ -112,11 +84,11 @@ const roleToSemanticElement: Partial<Record<AriaLandmarkRole, keyof JSX.Intrinsi
|
|
|
112
84
|
*/
|
|
113
85
|
export function Landmark(props: LandmarkProps): JSX.Element {
|
|
114
86
|
const [local, ariaProps] = splitProps(props, [
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
87
|
+
"class",
|
|
88
|
+
"style",
|
|
89
|
+
"slot",
|
|
90
|
+
"children",
|
|
91
|
+
"elementType",
|
|
120
92
|
]);
|
|
121
93
|
|
|
122
94
|
// Element ref
|
|
@@ -127,45 +99,51 @@ export function Landmark(props: LandmarkProps): JSX.Element {
|
|
|
127
99
|
if (local.elementType) {
|
|
128
100
|
return local.elementType;
|
|
129
101
|
}
|
|
130
|
-
return roleToSemanticElement[ariaProps.role] ??
|
|
102
|
+
return roleToSemanticElement[ariaProps.role] ?? "div";
|
|
131
103
|
});
|
|
132
104
|
|
|
133
105
|
// Create landmark aria props
|
|
134
106
|
const landmarkAria = createLandmark(
|
|
135
107
|
{
|
|
136
|
-
get role() {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
get
|
|
140
|
-
|
|
108
|
+
get role() {
|
|
109
|
+
return ariaProps.role;
|
|
110
|
+
},
|
|
111
|
+
get "aria-label"() {
|
|
112
|
+
return ariaProps["aria-label"];
|
|
113
|
+
},
|
|
114
|
+
get "aria-labelledby"() {
|
|
115
|
+
return ariaProps["aria-labelledby"];
|
|
116
|
+
},
|
|
117
|
+
get id() {
|
|
118
|
+
return ariaProps.id;
|
|
119
|
+
},
|
|
120
|
+
get focus() {
|
|
121
|
+
return ariaProps.focus;
|
|
122
|
+
},
|
|
141
123
|
},
|
|
142
|
-
ref
|
|
124
|
+
ref,
|
|
143
125
|
);
|
|
144
126
|
|
|
145
|
-
// Render props values
|
|
146
127
|
const renderValues = createMemo<LandmarkRenderProps>(() => ({
|
|
147
128
|
role: ariaProps.role,
|
|
148
129
|
}));
|
|
149
130
|
|
|
150
|
-
// Resolve class
|
|
151
131
|
const resolvedClass = createMemo(() => {
|
|
152
132
|
const cls = local.class;
|
|
153
|
-
if (typeof cls ===
|
|
133
|
+
if (typeof cls === "function") {
|
|
154
134
|
return cls(renderValues());
|
|
155
135
|
}
|
|
156
136
|
return cls ?? `solidaria-Landmark solidaria-Landmark--${ariaProps.role}`;
|
|
157
137
|
});
|
|
158
138
|
|
|
159
|
-
// Resolve style
|
|
160
139
|
const resolvedStyle = createMemo(() => {
|
|
161
140
|
const style = local.style;
|
|
162
|
-
if (typeof style ===
|
|
141
|
+
if (typeof style === "function") {
|
|
163
142
|
return style(renderValues());
|
|
164
143
|
}
|
|
165
144
|
return style;
|
|
166
145
|
});
|
|
167
146
|
|
|
168
|
-
// Filter DOM props
|
|
169
147
|
const domProps = createMemo(() => filterDOMProps(ariaProps, { global: true }));
|
|
170
148
|
|
|
171
149
|
return (
|
|
@@ -183,10 +161,6 @@ export function Landmark(props: LandmarkProps): JSX.Element {
|
|
|
183
161
|
);
|
|
184
162
|
}
|
|
185
163
|
|
|
186
|
-
// ============================================
|
|
187
|
-
// LANDMARK CONTROLLER EXPORT
|
|
188
|
-
// ============================================
|
|
189
|
-
|
|
190
164
|
/**
|
|
191
165
|
* Returns a controller for programmatic landmark navigation.
|
|
192
166
|
*
|