@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.
Files changed (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -272
  3. package/dist/ActionBar.d.ts +79 -0
  4. package/dist/ActionBar.d.ts.map +1 -0
  5. package/dist/ActionGroup.d.ts +74 -0
  6. package/dist/ActionGroup.d.ts.map +1 -0
  7. package/dist/Alert.d.ts +70 -0
  8. package/dist/Alert.d.ts.map +1 -0
  9. package/dist/Autocomplete.d.ts +5 -5
  10. package/dist/Autocomplete.d.ts.map +1 -1
  11. package/dist/Breadcrumbs.d.ts +27 -8
  12. package/dist/Breadcrumbs.d.ts.map +1 -1
  13. package/dist/Button.d.ts +28 -5
  14. package/dist/Button.d.ts.map +1 -1
  15. package/dist/Calendar.d.ts +51 -7
  16. package/dist/Calendar.d.ts.map +1 -1
  17. package/dist/Checkbox.d.ts +33 -8
  18. package/dist/Checkbox.d.ts.map +1 -1
  19. package/dist/Collection.d.ts +130 -0
  20. package/dist/Collection.d.ts.map +1 -0
  21. package/dist/Color.d.ts +210 -9
  22. package/dist/Color.d.ts.map +1 -1
  23. package/dist/ColorEditor.d.ts +42 -0
  24. package/dist/ColorEditor.d.ts.map +1 -0
  25. package/dist/ComboBox.d.ts +146 -16
  26. package/dist/ComboBox.d.ts.map +1 -1
  27. package/dist/ContextualHelpTrigger.d.ts +40 -0
  28. package/dist/ContextualHelpTrigger.d.ts.map +1 -0
  29. package/dist/DateField.d.ts +35 -8
  30. package/dist/DateField.d.ts.map +1 -1
  31. package/dist/DatePicker.d.ts +101 -5
  32. package/dist/DatePicker.d.ts.map +1 -1
  33. package/dist/DateRangePickerContext.d.ts +30 -0
  34. package/dist/DateRangePickerContext.d.ts.map +1 -0
  35. package/dist/Dialog.d.ts +5 -5
  36. package/dist/Dialog.d.ts.map +1 -1
  37. package/dist/Disclosure.d.ts +25 -5
  38. package/dist/Disclosure.d.ts.map +1 -1
  39. package/dist/DragAndDrop.d.ts +80 -0
  40. package/dist/DragAndDrop.d.ts.map +1 -0
  41. package/dist/DragPreview.d.ts +14 -0
  42. package/dist/DragPreview.d.ts.map +1 -0
  43. package/dist/DropZone.d.ts +27 -0
  44. package/dist/DropZone.d.ts.map +1 -0
  45. package/dist/FieldError.d.ts +27 -0
  46. package/dist/FieldError.d.ts.map +1 -0
  47. package/dist/FileTrigger.d.ts +26 -0
  48. package/dist/FileTrigger.d.ts.map +1 -0
  49. package/dist/Focusable.d.ts +27 -0
  50. package/dist/Focusable.d.ts.map +1 -0
  51. package/dist/Form.d.ts +41 -0
  52. package/dist/Form.d.ts.map +1 -0
  53. package/dist/GridList.d.ts +69 -10
  54. package/dist/GridList.d.ts.map +1 -1
  55. package/dist/HiddenDateInput.d.ts +26 -0
  56. package/dist/HiddenDateInput.d.ts.map +1 -0
  57. package/dist/HiddenTimeInput.d.ts +25 -0
  58. package/dist/HiddenTimeInput.d.ts.map +1 -0
  59. package/dist/Icon.d.ts +57 -0
  60. package/dist/Icon.d.ts.map +1 -0
  61. package/dist/Keyboard.d.ts +13 -0
  62. package/dist/Keyboard.d.ts.map +1 -0
  63. package/dist/Landmark.d.ts +3 -3
  64. package/dist/Landmark.d.ts.map +1 -1
  65. package/dist/Link.d.ts +10 -4
  66. package/dist/Link.d.ts.map +1 -1
  67. package/dist/ListBox.d.ts +73 -11
  68. package/dist/ListBox.d.ts.map +1 -1
  69. package/dist/ListDropTargetDelegate.d.ts +38 -0
  70. package/dist/ListDropTargetDelegate.d.ts.map +1 -0
  71. package/dist/Menu.d.ts +79 -10
  72. package/dist/Menu.d.ts.map +1 -1
  73. package/dist/Meter.d.ts +4 -4
  74. package/dist/Meter.d.ts.map +1 -1
  75. package/dist/Modal.d.ts +6 -4
  76. package/dist/Modal.d.ts.map +1 -1
  77. package/dist/NumberField.d.ts +10 -12
  78. package/dist/NumberField.d.ts.map +1 -1
  79. package/dist/Popover.d.ts +32 -7
  80. package/dist/Popover.d.ts.map +1 -1
  81. package/dist/Pressable.d.ts +27 -0
  82. package/dist/Pressable.d.ts.map +1 -0
  83. package/dist/ProgressBar.d.ts +6 -4
  84. package/dist/ProgressBar.d.ts.map +1 -1
  85. package/dist/RadioGroup.d.ts +43 -9
  86. package/dist/RadioGroup.d.ts.map +1 -1
  87. package/dist/RangeCalendar.d.ts +39 -7
  88. package/dist/RangeCalendar.d.ts.map +1 -1
  89. package/dist/RouterProvider.d.ts +75 -0
  90. package/dist/RouterProvider.d.ts.map +1 -0
  91. package/dist/SearchField.d.ts +23 -21
  92. package/dist/SearchField.d.ts.map +1 -1
  93. package/dist/Select.d.ts +48 -7
  94. package/dist/Select.d.ts.map +1 -1
  95. package/dist/SelectionIndicator.d.ts +30 -0
  96. package/dist/SelectionIndicator.d.ts.map +1 -0
  97. package/dist/Separator.d.ts +9 -3
  98. package/dist/Separator.d.ts.map +1 -1
  99. package/dist/SharedElementTransition.d.ts +41 -0
  100. package/dist/SharedElementTransition.d.ts.map +1 -0
  101. package/dist/Slider.d.ts +15 -8
  102. package/dist/Slider.d.ts.map +1 -1
  103. package/dist/StepList.d.ts +90 -0
  104. package/dist/StepList.d.ts.map +1 -0
  105. package/dist/Switch.d.ts +11 -5
  106. package/dist/Switch.d.ts.map +1 -1
  107. package/dist/Table.d.ts +222 -19
  108. package/dist/Table.d.ts.map +1 -1
  109. package/dist/Tabs.d.ts +47 -10
  110. package/dist/Tabs.d.ts.map +1 -1
  111. package/dist/TagGroup.d.ts +22 -10
  112. package/dist/TagGroup.d.ts.map +1 -1
  113. package/dist/Text.d.ts +10 -0
  114. package/dist/Text.d.ts.map +1 -0
  115. package/dist/TextField.d.ts +19 -11
  116. package/dist/TextField.d.ts.map +1 -1
  117. package/dist/TimeField.d.ts +32 -7
  118. package/dist/TimeField.d.ts.map +1 -1
  119. package/dist/Toast.d.ts +29 -14
  120. package/dist/Toast.d.ts.map +1 -1
  121. package/dist/ToggleButton.d.ts +36 -0
  122. package/dist/ToggleButton.d.ts.map +1 -0
  123. package/dist/ToggleButtonGroup.d.ts +33 -0
  124. package/dist/ToggleButtonGroup.d.ts.map +1 -0
  125. package/dist/Toolbar.d.ts +7 -3
  126. package/dist/Toolbar.d.ts.map +1 -1
  127. package/dist/Tooltip.d.ts +58 -7
  128. package/dist/Tooltip.d.ts.map +1 -1
  129. package/dist/Tree.d.ts +102 -11
  130. package/dist/Tree.d.ts.map +1 -1
  131. package/dist/Virtualizer.d.ts +61 -0
  132. package/dist/Virtualizer.d.ts.map +1 -0
  133. package/dist/VirtualizerLayouts.d.ts +82 -0
  134. package/dist/VirtualizerLayouts.d.ts.map +1 -0
  135. package/dist/VisuallyHidden.d.ts +4 -2
  136. package/dist/VisuallyHidden.d.ts.map +1 -1
  137. package/dist/contexts.d.ts +6 -1
  138. package/dist/contexts.d.ts.map +1 -1
  139. package/dist/index.d.ts +73 -39
  140. package/dist/index.d.ts.map +1 -1
  141. package/dist/index.js +23342 -10644
  142. package/dist/index.js.map +1 -7
  143. package/dist/index.jsx +18110 -0
  144. package/dist/index.jsx.map +1 -0
  145. package/dist/useDragAndDrop.d.ts +93 -0
  146. package/dist/useDragAndDrop.d.ts.map +1 -0
  147. package/dist/utils.d.ts +8 -2
  148. package/dist/utils.d.ts.map +1 -1
  149. package/dist/virtualizer/Layout.d.ts +79 -0
  150. package/dist/virtualizer/Layout.d.ts.map +1 -0
  151. package/package.json +33 -32
  152. package/src/ActionBar.tsx +251 -0
  153. package/src/ActionGroup.tsx +277 -0
  154. package/src/Alert.tsx +152 -0
  155. package/src/Autocomplete.tsx +39 -44
  156. package/src/Breadcrumbs.tsx +227 -72
  157. package/src/Button.tsx +315 -74
  158. package/src/Calendar.tsx +347 -141
  159. package/src/Checkbox.tsx +414 -123
  160. package/src/Collection.tsx +350 -0
  161. package/src/Color.tsx +1325 -284
  162. package/src/ColorEditor.tsx +213 -0
  163. package/src/ComboBox.tsx +644 -245
  164. package/src/ContextualHelpTrigger.tsx +195 -0
  165. package/src/DateField.tsx +274 -106
  166. package/src/DatePicker.tsx +892 -111
  167. package/src/DateRangePickerContext.tsx +44 -0
  168. package/src/Dialog.tsx +173 -104
  169. package/src/Disclosure.tsx +158 -105
  170. package/src/DragAndDrop.tsx +340 -0
  171. package/src/DragPreview.tsx +47 -0
  172. package/src/DropZone.tsx +233 -0
  173. package/src/FieldError.tsx +89 -0
  174. package/src/FileTrigger.tsx +83 -0
  175. package/src/Focusable.tsx +103 -0
  176. package/src/Form.tsx +140 -0
  177. package/src/GridList.tsx +542 -128
  178. package/src/HiddenDateInput.tsx +153 -0
  179. package/src/HiddenTimeInput.tsx +133 -0
  180. package/src/Icon.tsx +133 -0
  181. package/src/Keyboard.tsx +26 -0
  182. package/src/Landmark.tsx +37 -63
  183. package/src/Link.tsx +132 -69
  184. package/src/ListBox.tsx +656 -106
  185. package/src/ListDropTargetDelegate.ts +283 -0
  186. package/src/Menu.tsx +1234 -132
  187. package/src/Meter.tsx +44 -58
  188. package/src/Modal.tsx +262 -166
  189. package/src/NumberField.tsx +267 -151
  190. package/src/Popover.tsx +452 -343
  191. package/src/Pressable.tsx +108 -0
  192. package/src/ProgressBar.tsx +54 -59
  193. package/src/RadioGroup.tsx +533 -121
  194. package/src/RangeCalendar.tsx +249 -150
  195. package/src/RouterProvider.tsx +223 -0
  196. package/src/SearchField.tsx +460 -133
  197. package/src/Select.tsx +804 -233
  198. package/src/SelectionIndicator.tsx +108 -0
  199. package/src/Separator.tsx +47 -49
  200. package/src/SharedElementTransition.tsx +264 -0
  201. package/src/Slider.tsx +148 -98
  202. package/src/StepList.tsx +272 -0
  203. package/src/Switch.tsx +93 -46
  204. package/src/Table.tsx +1551 -225
  205. package/src/Tabs.tsx +377 -123
  206. package/src/TagGroup.tsx +233 -135
  207. package/src/Text.tsx +18 -0
  208. package/src/TextField.tsx +413 -86
  209. package/src/TimeField.tsx +232 -222
  210. package/src/Toast.tsx +306 -160
  211. package/src/ToggleButton.tsx +169 -0
  212. package/src/ToggleButtonGroup.tsx +141 -0
  213. package/src/Toolbar.tsx +61 -70
  214. package/src/Tooltip.tsx +473 -116
  215. package/src/Tree.tsx +1514 -175
  216. package/src/Virtualizer.tsx +730 -0
  217. package/src/VirtualizerLayouts.ts +280 -0
  218. package/src/VisuallyHidden.tsx +32 -38
  219. package/src/contexts.ts +29 -36
  220. package/src/index.ts +972 -620
  221. package/src/useDragAndDrop.ts +367 -0
  222. package/src/utils.tsx +69 -50
  223. package/src/virtualizer/Layout.ts +192 -0
  224. package/dist/index.ssr.js +0 -9785
  225. 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
+ }
@@ -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
- type JSX,
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 '@proyecto-viviana/solidaria';
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: 'main',
72
- navigation: 'nav',
73
- search: 'search', // HTML5.3 <search> element
74
- banner: 'header',
75
- contentinfo: 'footer',
76
- complementary: 'aside',
77
- form: 'form',
78
- region: 'section',
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
- 'class',
116
- 'style',
117
- 'slot',
118
- 'children',
119
- 'elementType',
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] ?? 'div';
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() { return ariaProps.role; },
137
- get 'aria-label'() { return ariaProps['aria-label']; },
138
- get 'aria-labelledby'() { return ariaProps['aria-labelledby']; },
139
- get id() { return ariaProps.id; },
140
- get focus() { return ariaProps.focus; },
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 === 'function') {
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 === 'function') {
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
  *