@proyecto-viviana/ui 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/components.css +1077 -1077
  2. package/dist/index.js +236 -249
  3. package/dist/index.js.map +3 -3
  4. package/dist/index.ssr.js +78 -81
  5. package/dist/index.ssr.js.map +3 -3
  6. package/dist/radio/index.d.ts +12 -27
  7. package/dist/radio/index.d.ts.map +1 -1
  8. package/dist/test-utils/index.d.ts +2 -2
  9. package/dist/test-utils/index.d.ts.map +1 -1
  10. package/package.json +13 -12
  11. package/src/alert/index.tsx +48 -0
  12. package/src/assets/favicon.png +0 -0
  13. package/src/assets/fire.gif +0 -0
  14. package/src/autocomplete/index.tsx +313 -0
  15. package/src/avatar/index.tsx +75 -0
  16. package/src/badge/index.tsx +43 -0
  17. package/src/breadcrumbs/index.tsx +207 -0
  18. package/src/button/Button.tsx +74 -0
  19. package/src/button/index.ts +2 -0
  20. package/src/button/types.ts +24 -0
  21. package/src/calendar/DateField.tsx +200 -0
  22. package/src/calendar/DatePicker.tsx +298 -0
  23. package/src/calendar/RangeCalendar.tsx +236 -0
  24. package/src/calendar/TimeField.tsx +196 -0
  25. package/src/calendar/index.tsx +223 -0
  26. package/src/checkbox/index.tsx +257 -0
  27. package/src/color/index.tsx +687 -0
  28. package/src/combobox/index.tsx +383 -0
  29. package/src/components.css +1077 -0
  30. package/src/custom/calendar-card/index.tsx +66 -0
  31. package/src/custom/chip/index.tsx +46 -0
  32. package/src/custom/conversation/index.tsx +105 -0
  33. package/src/custom/event-card/index.tsx +132 -0
  34. package/src/custom/header/index.tsx +33 -0
  35. package/src/custom/lateral-nav/index.tsx +88 -0
  36. package/src/custom/logo/index.tsx +58 -0
  37. package/src/custom/nav-header/index.tsx +42 -0
  38. package/src/custom/page-layout/index.tsx +29 -0
  39. package/src/custom/profile-card/index.tsx +64 -0
  40. package/src/custom/project-card/index.tsx +59 -0
  41. package/src/custom/timeline-item/index.tsx +105 -0
  42. package/src/dialog/Dialog.tsx +260 -0
  43. package/src/dialog/index.tsx +3 -0
  44. package/src/disclosure/index.tsx +307 -0
  45. package/src/gridlist/index.tsx +403 -0
  46. package/src/icon/icons/GitHubIcon.tsx +20 -0
  47. package/src/icon/index.tsx +48 -0
  48. package/src/index.ts +322 -0
  49. package/src/landmark/index.tsx +231 -0
  50. package/src/link/index.tsx +130 -0
  51. package/src/listbox/index.tsx +231 -0
  52. package/src/menu/index.tsx +297 -0
  53. package/src/meter/index.tsx +163 -0
  54. package/src/numberfield/index.tsx +482 -0
  55. package/src/popover/index.tsx +260 -0
  56. package/src/progress-bar/index.tsx +169 -0
  57. package/src/radio/index.tsx +173 -0
  58. package/src/searchfield/index.tsx +453 -0
  59. package/src/select/index.tsx +349 -0
  60. package/src/separator/index.tsx +141 -0
  61. package/src/slider/index.tsx +382 -0
  62. package/src/styles.css +450 -0
  63. package/src/switch/ToggleSwitch.tsx +112 -0
  64. package/src/switch/index.tsx +90 -0
  65. package/src/table/index.tsx +531 -0
  66. package/src/tabs/index.tsx +273 -0
  67. package/src/tag-group/index.tsx +240 -0
  68. package/src/test-utils/index.ts +40 -0
  69. package/src/textfield/index.tsx +211 -0
  70. package/src/theme.css +101 -0
  71. package/src/toast/index.tsx +324 -0
  72. package/src/toolbar/index.tsx +108 -0
  73. package/src/tooltip/index.tsx +197 -0
  74. package/src/tree/index.tsx +494 -0
  75. package/dist/index.jsx +0 -6658
  76. package/dist/index.jsx.map +0 -7
@@ -0,0 +1,20 @@
1
+ import type { JSX } from 'solid-js';
2
+
3
+ export interface GitHubIconProps {
4
+ size?: number;
5
+ color?: string;
6
+ }
7
+
8
+ export function GitHubIcon(props: GitHubIconProps): JSX.Element {
9
+ return (
10
+ <svg
11
+ viewBox="0 0 24 24"
12
+ width={props.size}
13
+ height={props.size}
14
+ fill={props.color}
15
+ aria-hidden="true"
16
+ >
17
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
18
+ </svg>
19
+ );
20
+ }
@@ -0,0 +1,48 @@
1
+ import type { Component, JSX } from 'solid-js'
2
+
3
+ export interface IconProps {
4
+ /** The icon component to render (should accept size and color props) */
5
+ icon: Component<{ size?: string | number; color?: string }>
6
+ /** Size of the icon (e.g., '24px' or 24) */
7
+ size?: string | number
8
+ /** Color of the icon */
9
+ color?: string
10
+ /** Whether to show the accent shadow effect (4px offset to bottom) */
11
+ withShadow?: boolean
12
+ /** Additional CSS class */
13
+ class?: string
14
+ /** Click handler */
15
+ onClick?: () => void
16
+ }
17
+
18
+ /**
19
+ * Icon wrapper component with optional accent shadow effect.
20
+ *
21
+ * The shadow effect creates a 4px offset accent-colored duplicate
22
+ * of the icon behind it for a stylized look.
23
+ */
24
+ export function Icon(props: IconProps): JSX.Element {
25
+ const size = () => props.size ?? 24
26
+ const color = () => props.color ?? 'var(--color-primary-500)'
27
+ const IconComponent = props.icon
28
+
29
+ return (
30
+ <div
31
+ class={`vui-icon ${props.withShadow ? 'vui-icon--with-shadow' : ''} ${props.class ?? ''}`}
32
+ onClick={props.onClick}
33
+ >
34
+ {props.withShadow && (
35
+ <div class="vui-icon__shadow">
36
+ <IconComponent size={size()} color="var(--color-accent)" />
37
+ </div>
38
+ )}
39
+ <div class="vui-icon__main">
40
+ <IconComponent size={size()} color={color()} />
41
+ </div>
42
+ </div>
43
+ )
44
+ }
45
+
46
+ // Re-export common icons
47
+ export { GitHubIcon } from './icons/GitHubIcon'
48
+ export type { GitHubIconProps } from './icons/GitHubIcon'
package/src/index.ts ADDED
@@ -0,0 +1,322 @@
1
+ // ============================================
2
+ // SPECTRUM-BASED COMPONENTS
3
+ // ============================================
4
+
5
+ // Button
6
+ export { Button } from './button';
7
+ export type { ButtonProps, ButtonVariant, ButtonStyle, StaticColor } from './button';
8
+
9
+ // Badge
10
+ export { Badge } from './badge';
11
+ export type { BadgeProps, BadgeVariant, BadgeSize } from './badge';
12
+
13
+ // Alert
14
+ export { Alert } from './alert';
15
+ export type { AlertProps, AlertVariant } from './alert';
16
+
17
+ // Avatar
18
+ export { Avatar, AvatarGroup } from './avatar';
19
+ export type { AvatarProps, AvatarGroupProps, AvatarSize } from './avatar';
20
+
21
+ // Switch
22
+ export { TabSwitch, ToggleSwitch } from './switch';
23
+ export type { TabSwitchProps, ToggleSwitchProps, SwitchSize } from './switch';
24
+
25
+ // Checkbox
26
+ export { Checkbox, CheckboxGroup } from './checkbox';
27
+ export type { CheckboxProps, CheckboxGroupProps, CheckboxSize } from './checkbox';
28
+
29
+ // Radio
30
+ export { RadioGroup, Radio } from './radio';
31
+ export type { RadioGroupProps, RadioProps, RadioGroupOrientation, RadioGroupSize } from './radio';
32
+
33
+ // Dialog
34
+ export { Dialog, DialogTrigger, DialogFooter } from './dialog';
35
+ export type { DialogProps, DialogTriggerProps, DialogFooterProps, DialogSize } from './dialog';
36
+
37
+ // Icon
38
+ export { Icon, GitHubIcon } from './icon';
39
+ export type { IconProps, GitHubIconProps } from './icon';
40
+
41
+ // Tooltip
42
+ export { Tooltip, TooltipTrigger, SimpleTooltip } from './tooltip';
43
+ export type { TooltipProps, TooltipTriggerProps, TooltipPlacement, TooltipVariant, SimpleTooltipProps } from './tooltip';
44
+
45
+ // Popover
46
+ export { Popover, PopoverTrigger, PopoverHeader, PopoverFooter } from './popover';
47
+ export type { PopoverProps, PopoverTriggerProps, PopoverHeaderProps, PopoverFooterProps, PopoverPlacement, PopoverSize, PopoverRenderProps } from './popover';
48
+
49
+ // TextField
50
+ export { TextField } from './textfield';
51
+ export type { TextFieldProps, TextFieldSize, TextFieldVariant } from './textfield';
52
+
53
+ // Link
54
+ export { Link } from './link';
55
+ export type { LinkProps, LinkVariant } from './link';
56
+
57
+ // ProgressBar
58
+ export { ProgressBar } from './progress-bar';
59
+ export type { ProgressBarProps, ProgressBarSize, ProgressBarVariant } from './progress-bar';
60
+
61
+ // Separator
62
+ export { Separator } from './separator';
63
+ export type { SeparatorProps, SeparatorVariant, SeparatorSize } from './separator';
64
+
65
+ // Toolbar
66
+ export { Toolbar } from './toolbar';
67
+ export type { ToolbarProps, ToolbarSize, ToolbarVariant } from './toolbar';
68
+
69
+ // Autocomplete
70
+ export { SearchAutocomplete } from './autocomplete';
71
+ export type { SearchAutocompleteProps, SearchAutocompleteItem, SearchAutocompleteSize } from './autocomplete';
72
+
73
+ // Select
74
+ export { Select, SelectTrigger, SelectValue, SelectListBox, SelectOption } from './select';
75
+ export type { SelectProps, SelectTriggerProps, SelectValueProps, SelectListBoxProps, SelectOptionProps, SelectSize } from './select';
76
+
77
+ // Menu
78
+ export { Menu, MenuItem, MenuTrigger, MenuButton, MenuSeparator } from './menu';
79
+ export type { MenuProps, MenuItemProps, MenuTriggerProps, MenuButtonProps, MenuSeparatorProps, MenuSize } from './menu';
80
+
81
+ // ListBox
82
+ export { ListBox, ListBoxOption } from './listbox';
83
+ export type { ListBoxProps, ListBoxOptionProps, ListBoxSize } from './listbox';
84
+
85
+ // Tabs
86
+ export { Tabs, TabList, Tab, TabPanel } from './tabs';
87
+ export type { TabsProps, TabListProps, TabProps, TabPanelProps, TabsSize, TabsVariant, TabOrientation } from './tabs';
88
+
89
+ // Breadcrumbs
90
+ export { Breadcrumbs, BreadcrumbItem } from './breadcrumbs';
91
+ export type { BreadcrumbsProps, BreadcrumbItemProps, BreadcrumbsSize, BreadcrumbsVariant } from './breadcrumbs';
92
+
93
+ // NumberField
94
+ export { NumberField } from './numberfield';
95
+ export type { NumberFieldProps, NumberFieldSize, NumberFieldVariant } from './numberfield';
96
+
97
+ // SearchField
98
+ export { SearchField } from './searchfield';
99
+ export type { SearchFieldProps, SearchFieldSize, SearchFieldVariant } from './searchfield';
100
+
101
+ // Slider
102
+ export { Slider } from './slider';
103
+ export type { SliderProps, SliderSize, SliderVariant } from './slider';
104
+
105
+ // ComboBox
106
+ export { ComboBox, ComboBoxInputGroup, ComboBoxInput, ComboBoxButton, ComboBoxListBox, ComboBoxOption, defaultContainsFilter } from './combobox';
107
+ export type { ComboBoxProps, ComboBoxInputProps, ComboBoxButtonProps, ComboBoxListBoxProps, ComboBoxOptionProps, ComboBoxSize, FilterFn, MenuTriggerAction } from './combobox';
108
+
109
+ // Toast
110
+ export {
111
+ Toast,
112
+ ToastRegion,
113
+ ToastProvider,
114
+ ToastContext,
115
+ addToast,
116
+ toastSuccess,
117
+ toastError,
118
+ toastWarning,
119
+ toastInfo,
120
+ globalToastQueue,
121
+ useToastContext,
122
+ } from './toast';
123
+ export type {
124
+ ToastProps,
125
+ ToastRegionProps,
126
+ ToastProviderProps,
127
+ ToastPlacement,
128
+ ToastVariant,
129
+ ToastContent,
130
+ ToastRenderProps,
131
+ ToastRegionRenderProps,
132
+ QueuedToast,
133
+ ToastOptions,
134
+ } from './toast';
135
+
136
+ // Disclosure
137
+ export {
138
+ Disclosure,
139
+ DisclosureGroup,
140
+ DisclosureTrigger,
141
+ DisclosurePanel,
142
+ } from './disclosure';
143
+ export type {
144
+ DisclosureProps,
145
+ DisclosureGroupProps,
146
+ DisclosureTriggerProps,
147
+ DisclosurePanelProps,
148
+ DisclosureSize,
149
+ DisclosureVariant,
150
+ } from './disclosure';
151
+
152
+ // Meter
153
+ export { Meter } from './meter';
154
+ export type { MeterProps, MeterSize, MeterVariant } from './meter';
155
+
156
+ // TagGroup
157
+ export { TagGroup } from './tag-group';
158
+ export type { TagGroupProps, TagProps, TagGroupSize, TagGroupVariant } from './tag-group';
159
+
160
+ // Calendar
161
+ export { Calendar } from './calendar';
162
+ export type { CalendarProps, CalendarSize, CalendarDate, DateValue } from './calendar';
163
+
164
+ // RangeCalendar
165
+ export { RangeCalendar } from './calendar/RangeCalendar';
166
+ export type { RangeCalendarProps, RangeCalendarSize, RangeValue } from './calendar/RangeCalendar';
167
+
168
+ // DateField
169
+ export { DateField } from './calendar/DateField';
170
+ export type { DateFieldProps, DateFieldSize } from './calendar/DateField';
171
+
172
+ // TimeField
173
+ export { TimeField } from './calendar/TimeField';
174
+ export type { TimeFieldProps, TimeFieldSize, TimeValue } from './calendar/TimeField';
175
+
176
+ // DatePicker
177
+ export { DatePicker } from './calendar/DatePicker';
178
+ export type { DatePickerProps, DatePickerSize } from './calendar/DatePicker';
179
+
180
+ // Table
181
+ export {
182
+ Table,
183
+ TableHeader,
184
+ TableColumn,
185
+ TableBody,
186
+ TableRow,
187
+ TableCell,
188
+ TableSelectionCheckbox,
189
+ TableSelectAllCheckbox,
190
+ } from './table';
191
+ export type {
192
+ TableProps,
193
+ TableHeaderProps,
194
+ TableColumnProps,
195
+ TableBodyProps,
196
+ TableRowProps,
197
+ TableCellProps,
198
+ TableSize,
199
+ TableVariant,
200
+ } from './table';
201
+
202
+ // GridList
203
+ export {
204
+ GridList,
205
+ GridListItem,
206
+ GridListSelectionCheckbox,
207
+ } from './gridlist';
208
+ export type {
209
+ GridListProps,
210
+ GridListItemProps,
211
+ GridListSize,
212
+ GridListVariant,
213
+ GridListLayout,
214
+ } from './gridlist';
215
+
216
+ // Tree
217
+ export {
218
+ Tree,
219
+ TreeItem,
220
+ TreeExpandButton,
221
+ TreeSelectionCheckbox,
222
+ } from './tree';
223
+ export type {
224
+ TreeProps,
225
+ TreeItemProps,
226
+ TreeExpandButtonProps,
227
+ TreeSize,
228
+ TreeVariant,
229
+ } from './tree';
230
+
231
+ // Color
232
+ export {
233
+ ColorSlider,
234
+ ColorSliderTrack,
235
+ ColorSliderThumb,
236
+ ColorArea,
237
+ ColorAreaGradient,
238
+ ColorAreaThumb,
239
+ ColorWheel,
240
+ ColorWheelTrack,
241
+ ColorWheelThumb,
242
+ ColorField,
243
+ ColorFieldInput,
244
+ ColorSwatch,
245
+ ColorPicker,
246
+ } from './color';
247
+ export type {
248
+ ColorSliderProps,
249
+ ColorAreaProps,
250
+ ColorWheelProps,
251
+ ColorFieldProps,
252
+ ColorSwatchProps,
253
+ ColorPickerProps,
254
+ ColorSize,
255
+ } from './color';
256
+
257
+ // Landmark
258
+ export {
259
+ Landmark,
260
+ SkipLink,
261
+ LandmarkNavigator,
262
+ useLandmarkController,
263
+ } from './landmark';
264
+ export type {
265
+ LandmarkProps,
266
+ SkipLinkProps,
267
+ LandmarkNavigatorProps,
268
+ AriaLandmarkRole,
269
+ LandmarkController,
270
+ } from './landmark';
271
+
272
+ // ============================================
273
+ // CUSTOM COMPONENTS
274
+ // ============================================
275
+
276
+ // Chip
277
+ export { Chip } from './custom/chip';
278
+ export type { ChipProps, ChipVariant } from './custom/chip';
279
+
280
+ // NavHeader
281
+ export { NavHeader } from './custom/nav-header';
282
+ export type { NavHeaderProps } from './custom/nav-header';
283
+
284
+ // Header
285
+ export { Header } from './custom/header';
286
+ export type { HeaderProps } from './custom/header';
287
+
288
+ // LateralNav
289
+ export { LateralNav, NavItem, NavLink, NavSection } from './custom/lateral-nav';
290
+ export type { LateralNavProps, NavItemProps, NavLinkProps, NavSectionProps } from './custom/lateral-nav';
291
+
292
+ // TimelineItem
293
+ export { TimelineItem } from './custom/timeline-item';
294
+ export type { TimelineItemProps, TimelineEventType } from './custom/timeline-item';
295
+
296
+ // Conversation
297
+ export { Conversation, ConversationPreview, ConversationBubble } from './custom/conversation';
298
+ export type { ConversationProps, ConversationPreviewProps, ConversationBubbleProps, Message } from './custom/conversation';
299
+
300
+ // ProfileCard
301
+ export { ProfileCard } from './custom/profile-card';
302
+ export type { ProfileCardProps } from './custom/profile-card';
303
+
304
+ // EventCard
305
+ export { EventCard, EventListItem } from './custom/event-card';
306
+ export type { EventCardProps, EventListItemProps } from './custom/event-card';
307
+
308
+ // CalendarCard
309
+ export { CalendarCard } from './custom/calendar-card';
310
+ export type { CalendarCardProps } from './custom/calendar-card';
311
+
312
+ // Logo
313
+ export { Logo } from './custom/logo';
314
+ export type { LogoProps, LogoSize } from './custom/logo';
315
+
316
+ // ProjectCard
317
+ export { ProjectCard } from './custom/project-card';
318
+ export type { ProjectCardProps, ProjectCardSize } from './custom/project-card';
319
+
320
+ // PageLayout
321
+ export { PageLayout } from './custom/page-layout';
322
+ export type { PageLayoutProps } from './custom/page-layout';
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Landmark component for proyecto-viviana-ui
3
+ *
4
+ * Styled landmark component built on top of solidaria-components.
5
+ * Landmarks help screen reader users navigate between major sections of a page.
6
+ * Press F6 to cycle through landmarks, or Shift+F6 to go backwards.
7
+ */
8
+
9
+ import { type JSX, splitProps, Show } from 'solid-js'
10
+ import {
11
+ Landmark as HeadlessLandmark,
12
+ useLandmarkController,
13
+ type LandmarkProps as HeadlessLandmarkProps,
14
+ type AriaLandmarkRole,
15
+ type LandmarkController,
16
+ } from '@proyecto-viviana/solidaria-components'
17
+
18
+ // ============================================
19
+ // TYPES
20
+ // ============================================
21
+
22
+ export interface LandmarkProps extends Omit<HeadlessLandmarkProps, 'class' | 'style'> {
23
+ /** Additional CSS class name. */
24
+ class?: string
25
+ /** Whether to show a visual indicator (for development). */
26
+ showLabel?: boolean
27
+ }
28
+
29
+ export type { AriaLandmarkRole, LandmarkController }
30
+
31
+ // ============================================
32
+ // STYLES
33
+ // ============================================
34
+
35
+ const roleLabels: Record<AriaLandmarkRole, string> = {
36
+ main: 'Main',
37
+ navigation: 'Navigation',
38
+ search: 'Search',
39
+ banner: 'Banner',
40
+ contentinfo: 'Footer',
41
+ complementary: 'Aside',
42
+ form: 'Form',
43
+ region: 'Region',
44
+ }
45
+
46
+ const roleColors: Record<AriaLandmarkRole, string> = {
47
+ main: 'bg-accent/10 border-accent-300',
48
+ navigation: 'bg-primary-500/10 border-primary-400',
49
+ search: 'bg-warning-400/10 border-warning-400',
50
+ banner: 'bg-success-400/10 border-success-400',
51
+ contentinfo: 'bg-danger-400/10 border-danger-400',
52
+ complementary: 'bg-primary-300/10 border-primary-300',
53
+ form: 'bg-accent-200/10 border-accent-200',
54
+ region: 'bg-bg-200/50 border-bg-300',
55
+ }
56
+
57
+ // ============================================
58
+ // LANDMARK COMPONENT
59
+ // ============================================
60
+
61
+ /**
62
+ * A landmark is a region of the page that helps screen reader users navigate.
63
+ * Press F6 to cycle through landmarks, or Shift+F6 to go backwards.
64
+ *
65
+ * @example
66
+ * ```tsx
67
+ * // Main content area
68
+ * <Landmark role="main" aria-label="Main content">
69
+ * <h1>Welcome</h1>
70
+ * <p>Page content here...</p>
71
+ * </Landmark>
72
+ *
73
+ * // Navigation
74
+ * <Landmark role="navigation" aria-label="Primary navigation">
75
+ * <nav>...</nav>
76
+ * </Landmark>
77
+ *
78
+ * // With development label visible
79
+ * <Landmark role="main" aria-label="Main content" showLabel>
80
+ * ...
81
+ * </Landmark>
82
+ * ```
83
+ */
84
+ export function Landmark(props: LandmarkProps): JSX.Element {
85
+ const [local, headlessProps] = splitProps(props, ['class', 'showLabel'])
86
+ const customClass = local.class ?? ''
87
+
88
+ const role = () => headlessProps.role
89
+
90
+ const getClassName = (): string => {
91
+ const base = 'relative'
92
+ const debugClass = local.showLabel
93
+ ? `border-2 border-dashed ${roleColors[role()]}`
94
+ : ''
95
+ return [base, debugClass, customClass].filter(Boolean).join(' ')
96
+ }
97
+
98
+ return (
99
+ <HeadlessLandmark {...headlessProps} class={getClassName()}>
100
+ <Show when={local.showLabel}>
101
+ <div
102
+ class={`absolute -top-3 left-2 px-2 py-0.5 text-xs font-medium rounded ${roleColors[role()]} text-primary-200`}
103
+ >
104
+ {roleLabels[role()]}
105
+ <Show when={headlessProps['aria-label']}>
106
+ <span class="text-primary-400"> - {headlessProps['aria-label']}</span>
107
+ </Show>
108
+ </div>
109
+ </Show>
110
+ {props.children}
111
+ </HeadlessLandmark>
112
+ )
113
+ }
114
+
115
+ // ============================================
116
+ // SKIP LINK COMPONENT
117
+ // ============================================
118
+
119
+ export interface SkipLinkProps {
120
+ /** The ID of the element to skip to (usually the main landmark). */
121
+ href: string
122
+ /** The text to display in the skip link. */
123
+ children?: JSX.Element
124
+ /** Additional CSS class name. */
125
+ class?: string
126
+ }
127
+
128
+ /**
129
+ * A skip link allows keyboard users to bypass repetitive navigation and jump directly to main content.
130
+ * The link is visually hidden until focused.
131
+ *
132
+ * @example
133
+ * ```tsx
134
+ * <SkipLink href="#main-content">Skip to main content</SkipLink>
135
+ *
136
+ * <Landmark role="navigation">...</Landmark>
137
+ *
138
+ * <Landmark role="main" id="main-content">
139
+ * ...
140
+ * </Landmark>
141
+ * ```
142
+ */
143
+ export function SkipLink(props: SkipLinkProps): JSX.Element {
144
+ const customClass = props.class ?? ''
145
+
146
+ const className = [
147
+ // Visually hidden by default
148
+ 'absolute left-0 top-0 -translate-y-full',
149
+ // Show when focused
150
+ 'focus:translate-y-0',
151
+ // Styling
152
+ 'z-50 px-4 py-2 bg-accent text-white font-medium rounded-br-lg',
153
+ 'transition-transform duration-200',
154
+ 'focus:outline-none focus:ring-2 focus:ring-accent-300 focus:ring-offset-2',
155
+ customClass,
156
+ ]
157
+ .filter(Boolean)
158
+ .join(' ')
159
+
160
+ return (
161
+ <a href={props.href} class={className}>
162
+ {props.children ?? 'Skip to main content'}
163
+ </a>
164
+ )
165
+ }
166
+
167
+ // ============================================
168
+ // LANDMARK NAVIGATOR COMPONENT
169
+ // ============================================
170
+
171
+ export interface LandmarkNavigatorProps {
172
+ /** Additional CSS class name. */
173
+ class?: string
174
+ /** Whether to show the navigator (for development/accessibility testing). */
175
+ isVisible?: boolean
176
+ }
177
+
178
+ /**
179
+ * A floating navigator for landmarks, useful for development and accessibility testing.
180
+ * Provides buttons to navigate between landmarks programmatically.
181
+ *
182
+ * @example
183
+ * ```tsx
184
+ * // Show in development only
185
+ * <LandmarkNavigator isVisible={import.meta.env.DEV} />
186
+ * ```
187
+ */
188
+ export function LandmarkNavigator(props: LandmarkNavigatorProps): JSX.Element {
189
+ const controller = useLandmarkController()
190
+
191
+ return (
192
+ <Show when={props.isVisible}>
193
+ <div
194
+ class={`fixed bottom-4 right-4 z-50 flex flex-col gap-2 p-3 bg-bg-400 border border-bg-300 rounded-lg shadow-lg ${props.class ?? ''}`}
195
+ >
196
+ <span class="text-xs font-medium text-primary-400 uppercase tracking-wider">
197
+ Landmarks (F6)
198
+ </span>
199
+ <div class="flex gap-1">
200
+ <button
201
+ type="button"
202
+ onClick={() => controller.focusPrevious()}
203
+ class="px-2 py-1 text-sm bg-bg-300 hover:bg-bg-200 text-primary-200 rounded transition-colors"
204
+ title="Previous landmark (Shift+F6)"
205
+ >
206
+
207
+ </button>
208
+ <button
209
+ type="button"
210
+ onClick={() => controller.focusMain()}
211
+ class="px-3 py-1 text-sm bg-accent hover:bg-accent-200 text-white rounded transition-colors"
212
+ title="Go to main content"
213
+ >
214
+ Main
215
+ </button>
216
+ <button
217
+ type="button"
218
+ onClick={() => controller.focusNext()}
219
+ class="px-2 py-1 text-sm bg-bg-300 hover:bg-bg-200 text-primary-200 rounded transition-colors"
220
+ title="Next landmark (F6)"
221
+ >
222
+
223
+ </button>
224
+ </div>
225
+ </div>
226
+ </Show>
227
+ )
228
+ }
229
+
230
+ // Export controller hook for convenience
231
+ export { useLandmarkController }