rip-lang 3.13.71 → 3.13.72

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.
@@ -48,6 +48,22 @@
48
48
  popover.rip
49
49
  accordion.rip
50
50
  grid.rip
51
+ badge.rip
52
+ skeleton.rip
53
+ spinner.rip
54
+ card.rip
55
+ label.rip
56
+ textarea.rip
57
+ native-select.rip
58
+ input-group.rip
59
+ button-group.rip
60
+ alert-dialog.rip
61
+ breadcrumb.rip
62
+ table.rip
63
+ collapsible.rip
64
+ pagination.rip
65
+ carousel.rip
66
+ resizable.rip
51
67
  " data-mount="WidgetGallery">
52
68
  </script>
53
69
  </head>
@@ -58,7 +74,26 @@ do ->
58
74
  orig = HTMLElement::focus
59
75
  HTMLElement::focus = (o) -> orig.call this, { preventScroll: true, ...o }
60
76
 
77
+ export SectionHead = component
78
+ accept _gallery
79
+ @tag := ''
80
+ @name := ''
81
+ @lines := 0
82
+
83
+ render
84
+ .section-title
85
+ = @name
86
+ if @lines > 0
87
+ span.badge @click: (=> _gallery._viewSource(@tag)), "#{@lines} lines ❐"
88
+ else
89
+ span.badge "pattern"
90
+ .section-nav
91
+ a @click: (=> _gallery._navSection(@tag, 'prev')), "‹"
92
+ a @click: (=> _gallery._navSection(@tag, 'home')), "⌂"
93
+ a @click: (=> _gallery._navSection(@tag, 'next')), "›"
94
+
61
95
  export WidgetGallery = component
96
+ offer _gallery := this
62
97
 
63
98
  # ---- State ----
64
99
  check1 := false
@@ -96,6 +131,11 @@ export WidgetGallery = component
96
131
  pickedDate := null
97
132
  dateRange := null
98
133
  darkMode := false
134
+ showAlertDialog := false
135
+ textareaVal := ''
136
+ nativeSelectVal := ''
137
+ collapsibleOpen := false
138
+ currentPage := 1
99
139
  sourceCode := null
100
140
  sourceName := ''
101
141
  sourceLines := 0
@@ -104,12 +144,13 @@ export WidgetGallery = component
104
144
  tocGroups := [
105
145
  { label: 'Selection', ids: ['select', 'combobox', 'multi-select', 'autocomplete'] }
106
146
  { label: 'Toggle', ids: ['checkbox', 'toggle', 'toggle-group', 'radio-group', 'checkbox-group'] }
107
- { label: 'Input', ids: ['input', 'number-field', 'slider', 'otp-field', 'date-picker', 'editable-value'] }
108
- { label: 'Navigation', ids: ['tabs', 'menu', 'context-menu', 'menubar', 'nav-menu', 'toolbar'] }
109
- { label: 'Overlay', ids: ['dialog', 'drawer', 'popover', 'tooltip', 'preview-card', 'toast'] }
110
- { label: 'Display', ids: ['button', 'separator', 'progress', 'meter', 'avatar', 'scroll-area'] }
111
- { label: 'Form', ids: ['field', 'fieldset', 'form'] }
112
- { label: 'Data', ids: ['grid', 'accordion'] }
147
+ { label: 'Input', ids: ['input', 'textarea', 'native-select', 'number-field', 'slider', 'otp-field', 'date-picker', 'editable-value', 'input-group'] }
148
+ { label: 'Navigation', ids: ['tabs', 'menu', 'context-menu', 'menubar', 'nav-menu', 'toolbar', 'breadcrumb'] }
149
+ { label: 'Overlay', ids: ['dialog', 'alert-dialog', 'drawer', 'popover', 'tooltip', 'preview-card', 'toast'] }
150
+ { label: 'Display', ids: ['button', 'badge', 'card', 'separator', 'progress', 'meter', 'spinner', 'skeleton', 'avatar', 'label', 'scroll-area'] }
151
+ { label: 'Form', ids: ['field', 'fieldset', 'form', 'button-group'] }
152
+ { label: 'Data', ids: ['grid', 'accordion', 'table', 'collapsible'] }
153
+ { label: 'Interactive', ids: ['pagination', 'carousel', 'resizable'] }
113
154
  ]
114
155
  _nameFor: (id) -> (tocData.find((c) -> c.id is id))?.name or id
115
156
  _viewSource: (id) ->
@@ -128,7 +169,11 @@ export WidgetGallery = component
128
169
  document.head.appendChild(link)
129
170
  script = document.createElement('script')
130
171
  script.src = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js'
131
- script.onload = cb
172
+ script.onload = =>
173
+ fetch('hljs-rip.js').then((r) -> r.text()).then (src) =>
174
+ fn = new Function('return ' + src.replace(/[\s\S]*export default /, ''))()
175
+ hljs.registerLanguage 'rip', fn
176
+ cb()
132
177
  document.head.appendChild(script)
133
178
  _onFilter: (e) -> tocFilter = e.target.value
134
179
  _getVisibleLinks: -> Array.from(document.querySelectorAll('.toc-nav .toc-group a:not([data-hidden])') or [])
@@ -235,44 +280,60 @@ export WidgetGallery = component
235
280
  if visible.length is 1
236
281
  tocActive = visible[0].getAttribute('href')?.slice(1)
237
282
  tocData := [
238
- { id: 'select', name: 'Select', lines: 184, desc: 'Dropdown with typeahead, keyboard nav, ARIA listbox.', props: ['@value', '@placeholder', '@disabled'], events: ['change'], keys: ['↕', 'Enter', 'Space', 'Esc', 'Home', 'End', 'type-ahead'], attrs: ['$open', '$placeholder', '$disabled', '$value', '$highlighted', '$selected'] }
239
- { id: 'combobox', name: 'Combobox', lines: 153, desc: 'Filterable input + listbox for search-as-you-type.', props: ['@query', '@items', '@placeholder', '@disabled', '@autoHighlight'], events: ['filter', 'select'], keys: ['↕', 'Enter', 'Esc', 'Tab'], attrs: ['$open', '$disabled', '$clear', '$value', '$highlighted', '$empty'] }
240
- { id: 'multi-select', name: 'MultiSelect', lines: 158, desc: 'Multi-select with chips, filtering, and keyboard nav.', props: ['@value', '@items', '@placeholder', '@disabled'], events: ['change'], keys: ['↕', 'Enter', 'Esc', 'Backspace', 'Tab'], attrs: ['$open', '$disabled', '$chips', '$chip', '$remove', '$clear', '$highlighted', '$selected'] }
241
- { id: 'autocomplete', name: 'Autocomplete', lines: 141, desc: 'Suggestion input — type to filter, select to fill.', props: ['@value', '@items', '@placeholder', '@disabled'], events: ['select'], keys: ['↕', 'Enter', 'Esc', 'Tab'], attrs: ['$open', '$disabled', '$clear'] }
242
- { id: 'checkbox', name: 'Checkbox', lines: 33, desc: 'Toggle with checkbox or switch semantics.', props: ['@checked', '@disabled', '@indeterminate', '@switch'], events: ['change'], keys: [], attrs: ['$checked', '$indeterminate', '$disabled'] }
243
- { id: 'toggle', name: 'Toggle', lines: 24, desc: 'Two-state toggle button with pressed state.', props: ['@pressed', '@disabled'], events: ['change'], keys: [], attrs: ['$pressed', '$disabled'] }
244
- { id: 'toggle-group', name: 'ToggleGroup', lines: 78, desc: 'Single or multi-select toggle buttons.', props: ['@value', '@disabled', '@multiple', '@orientation'], events: ['change'], keys: ['← →', '↕', 'Home', 'End'], attrs: ['$orientation', '$disabled', '$pressed', '$value'] }
245
- { id: 'radio-group', name: 'RadioGroup', lines: 67, desc: 'Exactly one option selected with arrow key nav.', props: ['@value', '@disabled', '@orientation', '@name'], events: ['change'], keys: ['← →', '↕', 'Home', 'End'], attrs: ['$orientation', '$disabled', '$checked', '$value'] }
246
- { id: 'checkbox-group', name: 'CheckboxGroup', lines: 65, desc: 'Multiple options checked independently.', props: ['@value', '@disabled', '@orientation', '@label'], events: ['change'], keys: ['← →', '↕'], attrs: ['$orientation', '$disabled', '$checked', '$value'] }
247
- { id: 'input', name: 'Input', lines: 35, desc: 'Headless input tracking focus, touch, and validation.', props: ['@value', '@placeholder', '@type', '@disabled', '@required'], events: [], keys: [], attrs: ['$disabled', '$focused', '$touched'] }
248
- { id: 'number-field', name: 'NumberField', lines: 162, desc: 'Number input with stepper buttons and hold-to-repeat.', props: ['@value', '@min', '@max', '@step', '@disabled', '@readOnly'], events: ['input', 'change'], keys: ['↕', 'PgUp', 'PgDn', 'Home', 'End'], attrs: ['$disabled', '$readonly', '$decrement', '$increment'] }
249
- { id: 'slider', name: 'Slider', lines: 165, desc: 'Draggable range input with pointer capture and keyboard.', props: ['@value', '@min', '@max', '@step', '@orientation', '@disabled'], events: ['input', 'change'], keys: ['← →', '↕', 'PgUp', 'PgDn', 'Home', 'End'], attrs: ['$orientation', '$disabled', '$dragging', '$track', '$indicator', '$thumb', '$active'] }
250
- { id: 'otp-field', name: 'OTPField', lines: 89, desc: 'Multi-digit code input with auto-advance and paste.', props: ['@length', '@value', '@disabled', '@mask'], events: ['input', 'complete'], keys: ['← →', 'Backspace', 'Home', 'End', 'paste'], attrs: ['$disabled', '$complete', '$filled'] }
251
- { id: 'date-picker', name: 'DatePicker', lines: 214, desc: 'Calendar dropdown for single date or range selection.', props: ['@value', '@placeholder', '@disabled', '@range', '@firstDayOfWeek'], events: ['change'], keys: ['Esc', 'Enter', 'Space'], attrs: ['$open', '$disabled', '$range', '$calendar', '$selected', '$today', '$in-range'] }
252
- { id: 'editable-value', name: 'EditableValue', lines: 80, desc: 'Click-to-edit inline value with popover form.', props: ['@disabled'], events: ['save'], keys: ['Esc', 'Enter'], attrs: ['$editing', '$disabled', '$saving', '$edit-trigger'] }
253
- { id: 'tabs', name: 'Tabs', lines: 124, desc: 'Tab panel with roving tabindex and arrow key nav.', props: ['@active', '@orientation', '@activation'], events: ['change'], keys: ['← →', '↕', 'Home', 'End', 'Enter', 'Space'], attrs: ['$tab', '$panel', '$active', '$disabled'] }
254
- { id: 'menu', name: 'Menu', lines: 162, desc: 'Dropdown action menu with keyboard navigation.', props: ['@disabled'], events: ['select'], keys: ['↕', 'Home', 'End', 'Enter', 'Space', 'Esc', 'Tab', 'type-ahead'], attrs: ['$open', '$disabled', '$highlighted', '$value'] }
255
- { id: 'context-menu', name: 'ContextMenu', lines: 98, desc: 'Right-click context menu with keyboard navigation.', props: ['@disabled'], events: ['select'], keys: ['↕', 'Home', 'End', 'Enter', 'Space', 'Esc', 'Tab'], attrs: ['$open', '$highlighted', '$disabled', '$value'] }
256
- { id: 'menubar', name: 'Menubar', lines: 155, desc: 'Horizontal menu bar with dropdown menus.', props: ['@disabled'], events: ['select'], keys: ['← →', '↕', 'Enter', 'Space', 'Esc', 'Tab'], attrs: ['$disabled', '$open', '$highlighted', '$value'] }
257
- { id: 'nav-menu', name: 'NavMenu', lines: 132, desc: 'Site navigation with hover/click dropdown panels.', props: ['@orientation', '@hoverDelay', '@hoverCloseDelay'], events: [], keys: ['← →', '↓', 'Esc'], attrs: ['$orientation', '$open'] }
258
- { id: 'toolbar', name: 'Toolbar', lines: 46, desc: 'Groups controls with roving tabindex keyboard nav.', props: ['@orientation', '@label'], events: [], keys: ['← →', '↕', 'Home', 'End'], attrs: ['$orientation'] }
259
- { id: 'dialog', name: 'Dialog', lines: 107, desc: 'Modal with focus trap, scroll lock, escape dismiss.', props: ['@open', '@dismissable', '@initialFocus'], events: ['close'], keys: ['Esc', 'Tab'], attrs: ['$open'] }
260
- { id: 'drawer', name: 'Drawer', lines: 79, desc: 'Slide-out panel with focus trap and scroll lock.', props: ['@open', '@side', '@dismissable'], events: ['close'], keys: ['Esc', 'Tab'], attrs: ['$open', '$side'] }
261
- { id: 'popover', name: 'Popover', lines: 143, desc: 'Anchored floating content with flip/shift positioning.', props: ['@placement', '@offset', '@disabled', '@openOnHover', '@hoverDelay', '@hoverCloseDelay'], events: [], keys: ['Esc', 'Enter', 'Space', '↓'], attrs: ['$open', '$placement'] }
262
- { id: 'tooltip', name: 'Tooltip', lines: 115, desc: 'Hover/focus tooltip with delay and positioning.', props: ['@text', '@placement', '@delay', '@offset', '@hoverable'], events: [], keys: [], attrs: ['$open', '$entering', '$exiting', '$placement'] }
263
- { id: 'preview-card', name: 'PreviewCard', lines: 73, desc: 'Hover/focus preview card with delay.', props: ['@delay', '@closeDelay'], events: [], keys: [], attrs: ['$open'] }
264
- { id: 'toast', name: 'Toast', lines: 88, desc: 'Auto-dismiss notification with ARIA live region.', props: ['@toasts', '@toast', '@placement'], events: ['dismiss'], keys: [], attrs: ['$placement', '$type', '$leaving'] }
265
- { id: 'button', name: 'Button', lines: 23, desc: 'Accessible button with disabled-but-focusable pattern.', props: ['@disabled'], events: ['press'], keys: [], attrs: ['$disabled'] }
266
- { id: 'separator', name: 'Separator', lines: 17, desc: 'Decorative or semantic divider between sections.', props: ['@orientation', '@decorative'], events: [], keys: [], attrs: ['$orientation'] }
267
- { id: 'progress', name: 'Progress', lines: 25, desc: 'Progress bar with CSS custom property for value.', props: ['@value', '@max', '@label'], events: [], keys: [], attrs: ['$complete'] }
268
- { id: 'meter', name: 'Meter', lines: 36, desc: 'Gauge for known-range measurements with thresholds.', props: ['@value', '@min', '@max', '@low', '@high', '@optimum', '@label'], events: [], keys: [], attrs: ['$level'] }
269
- { id: 'avatar', name: 'Avatar', lines: 37, desc: 'Image with fallback to initials or placeholder.', props: ['@src', '@alt', '@fallback'], events: [], keys: [], attrs: ['$status', '$initials', '$placeholder'] }
270
- { id: 'scroll-area', name: 'ScrollArea', lines: 145, desc: 'Custom scrollbar with draggable thumb and auto-hide.', props: ['@orientation'], events: [], keys: [], attrs: ['$orientation', '$hovering', '$scrolling', '$dragging', '$viewport', '$scrollbar', '$thumb'] }
271
- { id: 'field', name: 'Field', lines: 53, desc: 'Form field wrapper with label, description, and error.', props: ['@label', '@description', '@error', '@disabled', '@required'], events: [], keys: [], attrs: ['$disabled', '$invalid', '$label', '$required', '$description', '$error'] }
272
- { id: 'fieldset', name: 'Fieldset', lines: 22, desc: 'Grouped fields with legend and cascading disable.', props: ['@legend', '@disabled'], events: [], keys: [], attrs: ['$disabled', '$legend'] }
273
- { id: 'form', name: 'Form', lines: 39, desc: 'Form wrapper with submit handling and validation state.', props: ['@disabled'], events: ['submit'], keys: [], attrs: ['$disabled', '$submitting', '$submitted'] }
274
- { id: 'grid', name: 'Grid', lines: 901, desc: 'Virtual-scrolling data grid — 100K+ rows at 60fps.', props: ['@data', '@columns', '@rowHeight', '@overscan', '@striped', '@beforeEdit', '@afterEdit'], events: [], keys: ['Arrows', 'Tab', 'Enter', 'F2', 'Esc', 'Home', 'End', 'PgUp', 'PgDn', 'Ctrl+A', 'Ctrl+C', 'Ctrl+V', 'Ctrl+X'], attrs: ['$editing', '$selecting', '$sorted'] }
275
- { id: 'accordion', name: 'Accordion', lines: 113, desc: 'Expand/collapse sections, single or multiple mode.', props: ['@multiple'], events: ['change'], keys: ['Enter', 'Space', '↕', 'Home', 'End'], attrs: ['$item', '$trigger', '$content', '$open', '$disabled'] }
283
+ { id: 'select', name: 'Select', lines: 147, desc: 'Dropdown with typeahead, keyboard nav, ARIA listbox.', props: ['@value', '@placeholder', '@disabled'], events: ['change'], keys: ['↕', 'Enter', 'Space', 'Esc', 'Home', 'End', 'type-ahead'], attrs: ['$open', '$placeholder', '$disabled', '$value', '$highlighted', '$selected'] }
284
+ { id: 'combobox', name: 'Combobox', lines: 124, desc: 'Filterable input + listbox for search-as-you-type.', props: ['@query', '@items', '@placeholder', '@disabled', '@autoHighlight'], events: ['filter', 'select'], keys: ['↕', 'Enter', 'Esc', 'Tab'], attrs: ['$open', '$disabled', '$clear', '$value', '$highlighted', '$empty'] }
285
+ { id: 'multi-select', name: 'MultiSelect', lines: 130, desc: 'Multi-select with chips, filtering, and keyboard nav.', props: ['@value', '@items', '@placeholder', '@disabled'], events: ['change'], keys: ['↕', 'Enter', 'Esc', 'Backspace', 'Tab'], attrs: ['$open', '$disabled', '$chips', '$chip', '$remove', '$clear', '$highlighted', '$selected'] }
286
+ { id: 'autocomplete', name: 'Autocomplete', lines: 115, desc: 'Suggestion input — type to filter, select to fill.', props: ['@value', '@items', '@placeholder', '@disabled'], events: ['select'], keys: ['↕', 'Enter', 'Esc', 'Tab'], attrs: ['$open', '$disabled', '$clear'] }
287
+ { id: 'checkbox', name: 'Checkbox', lines: 18, desc: 'Toggle with checkbox or switch semantics.', props: ['@checked', '@disabled', '@indeterminate', '@switch'], events: ['change'], keys: [], attrs: ['$checked', '$indeterminate', '$disabled'] }
288
+ { id: 'toggle', name: 'Toggle', lines: 13, desc: 'Two-state toggle button with pressed state.', props: ['@pressed', '@disabled'], events: ['change'], keys: [], attrs: ['$pressed', '$disabled'] }
289
+ { id: 'toggle-group', name: 'ToggleGroup', lines: 59, desc: 'Single or multi-select toggle buttons.', props: ['@value', '@disabled', '@multiple', '@orientation'], events: ['change'], keys: ['← →', '↕', 'Home', 'End'], attrs: ['$orientation', '$disabled', '$pressed', '$value'] }
290
+ { id: 'radio-group', name: 'RadioGroup', lines: 50, desc: 'Exactly one option selected with arrow key nav.', props: ['@value', '@disabled', '@orientation', '@name'], events: ['change'], keys: ['← →', '↕', 'Home', 'End'], attrs: ['$orientation', '$disabled', '$checked', '$value'] }
291
+ { id: 'checkbox-group', name: 'CheckboxGroup', lines: 46, desc: 'Multiple options checked independently.', props: ['@value', '@disabled', '@orientation', '@label'], events: ['change'], keys: ['← →', '↕'], attrs: ['$orientation', '$disabled', '$checked', '$value'] }
292
+ { id: 'input', name: 'Input', lines: 24, desc: 'Headless input tracking focus, touch, and validation.', props: ['@value', '@placeholder', '@type', '@disabled', '@required'], events: [], keys: [], attrs: ['$disabled', '$focused', '$touched'] }
293
+ { id: 'number-field', name: 'NumberField', lines: 132, desc: 'Number input with stepper buttons and hold-to-repeat.', props: ['@value', '@min', '@max', '@step', '@disabled', '@readOnly'], events: ['input', 'change'], keys: ['↕', 'PgUp', 'PgDn', 'Home', 'End'], attrs: ['$disabled', '$readonly', '$decrement', '$increment'] }
294
+ { id: 'slider', name: 'Slider', lines: 130, desc: 'Draggable range input with pointer capture and keyboard.', props: ['@value', '@min', '@max', '@step', '@orientation', '@disabled'], events: ['input', 'change'], keys: ['← →', '↕', 'PgUp', 'PgDn', 'Home', 'End'], attrs: ['$orientation', '$disabled', '$dragging', '$track', '$indicator', '$thumb', '$active'] }
295
+ { id: 'otp-field', name: 'OTPField', lines: 72, desc: 'Multi-digit code input with auto-advance and paste.', props: ['@length', '@value', '@disabled', '@mask'], events: ['input', 'complete'], keys: ['← →', 'Backspace', 'Home', 'End', 'paste'], attrs: ['$disabled', '$complete', '$filled'] }
296
+ { id: 'date-picker', name: 'DatePicker', lines: 172, desc: 'Calendar dropdown for single date or range selection.', props: ['@value', '@placeholder', '@disabled', '@range', '@firstDayOfWeek'], events: ['change'], keys: ['Esc', 'Enter', 'Space'], attrs: ['$open', '$disabled', '$range', '$calendar', '$selected', '$today', '$in-range'] }
297
+ { id: 'editable-value', name: 'EditableValue', lines: 58, desc: 'Click-to-edit inline value with popover form.', props: ['@disabled'], events: ['save'], keys: ['Esc', 'Enter'], attrs: ['$editing', '$disabled', '$saving', '$edit-trigger'] }
298
+ { id: 'tabs', name: 'Tabs', lines: 91, desc: 'Tab panel with roving tabindex and arrow key nav.', props: ['@active', '@orientation', '@activation'], events: ['change'], keys: ['← →', '↕', 'Home', 'End', 'Enter', 'Space'], attrs: ['$tab', '$panel', '$active', '$disabled'] }
299
+ { id: 'menu', name: 'Menu', lines: 133, desc: 'Dropdown action menu with keyboard navigation.', props: ['@disabled'], events: ['select'], keys: ['↕', 'Home', 'End', 'Enter', 'Space', 'Esc', 'Tab', 'type-ahead'], attrs: ['$open', '$disabled', '$highlighted', '$value'] }
300
+ { id: 'context-menu', name: 'ContextMenu', lines: 80, desc: 'Right-click context menu with keyboard navigation.', props: ['@disabled'], events: ['select'], keys: ['↕', 'Home', 'End', 'Enter', 'Space', 'Esc', 'Tab'], attrs: ['$open', '$highlighted', '$disabled', '$value'] }
301
+ { id: 'menubar', name: 'Menubar', lines: 125, desc: 'Horizontal menu bar with dropdown menus.', props: ['@disabled'], events: ['select'], keys: ['← →', '↕', 'Enter', 'Space', 'Esc', 'Tab'], attrs: ['$disabled', '$open', '$highlighted', '$value'] }
302
+ { id: 'nav-menu', name: 'NavMenu', lines: 99, desc: 'Site navigation with hover/click dropdown panels.', props: ['@orientation', '@hoverDelay', '@hoverCloseDelay'], events: [], keys: ['← →', '↓', 'Esc'], attrs: ['$orientation', '$open'] }
303
+ { id: 'toolbar', name: 'Toolbar', lines: 31, desc: 'Groups controls with roving tabindex keyboard nav.', props: ['@orientation', '@label'], events: [], keys: ['← →', '↕', 'Home', 'End'], attrs: ['$orientation'] }
304
+ { id: 'dialog', name: 'Dialog', lines: 83, desc: 'Modal with focus trap, scroll lock, escape dismiss.', props: ['@open', '@dismissable', '@initialFocus'], events: ['close'], keys: ['Esc', 'Tab'], attrs: ['$open'] }
305
+ { id: 'drawer', name: 'Drawer', lines: 60, desc: 'Slide-out panel with focus trap and scroll lock.', props: ['@open', '@side', '@dismissable'], events: ['close'], keys: ['Esc', 'Tab'], attrs: ['$open', '$side'] }
306
+ { id: 'popover', name: 'Popover', lines: 117, desc: 'Anchored floating content with flip/shift positioning.', props: ['@placement', '@offset', '@disabled', '@openOnHover', '@hoverDelay', '@hoverCloseDelay'], events: [], keys: ['Esc', 'Enter', 'Space', '↓'], attrs: ['$open', '$placement'] }
307
+ { id: 'tooltip', name: 'Tooltip', lines: 92, desc: 'Hover/focus tooltip with delay and positioning.', props: ['@text', '@placement', '@delay', '@offset', '@hoverable'], events: [], keys: [], attrs: ['$open', '$entering', '$exiting', '$placement'] }
308
+ { id: 'preview-card', name: 'PreviewCard', lines: 56, desc: 'Hover/focus preview card with delay.', props: ['@delay', '@closeDelay'], events: [], keys: [], attrs: ['$open'] }
309
+ { id: 'toast', name: 'Toast', lines: 56, desc: 'Auto-dismiss notification with ARIA live region.', props: ['@toasts', '@toast', '@placement'], events: ['dismiss'], keys: [], attrs: ['$placement', '$type', '$leaving'] }
310
+ { id: 'button', name: 'Button', lines: 10, desc: 'Accessible button with disabled-but-focusable pattern.', props: ['@disabled'], events: ['press'], keys: [], attrs: ['$disabled'] }
311
+ { id: 'separator', name: 'Separator', lines: 7, desc: 'Decorative or semantic divider between sections.', props: ['@orientation', '@decorative'], events: [], keys: [], attrs: ['$orientation'] }
312
+ { id: 'progress', name: 'Progress', lines: 14, desc: 'Progress bar with CSS custom property for value.', props: ['@value', '@max', '@label'], events: [], keys: [], attrs: ['$complete'] }
313
+ { id: 'meter', name: 'Meter', lines: 23, desc: 'Gauge for known-range measurements with thresholds.', props: ['@value', '@min', '@max', '@low', '@high', '@optimum', '@label'], events: [], keys: [], attrs: ['$level'] }
314
+ { id: 'avatar', name: 'Avatar', lines: 23, desc: 'Image with fallback to initials or placeholder.', props: ['@src', '@alt', '@fallback'], events: [], keys: [], attrs: ['$status', '$initials', '$placeholder'] }
315
+ { id: 'scroll-area', name: 'ScrollArea', lines: 115, desc: 'Custom scrollbar with draggable thumb and auto-hide.', props: ['@orientation'], events: [], keys: [], attrs: ['$orientation', '$hovering', '$scrolling', '$dragging', '$viewport', '$scrollbar', '$thumb'] }
316
+ { id: 'field', name: 'Field', lines: 39, desc: 'Form field wrapper with label, description, and error.', props: ['@label', '@description', '@error', '@disabled', '@required'], events: [], keys: [], attrs: ['$disabled', '$invalid', '$label', '$required', '$description', '$error'] }
317
+ { id: 'fieldset', name: 'Fieldset', lines: 9, desc: 'Grouped fields with legend and cascading disable.', props: ['@legend', '@disabled'], events: [], keys: [], attrs: ['$disabled', '$legend'] }
318
+ { id: 'form', name: 'Form', lines: 21, desc: 'Form wrapper with submit handling and validation state.', props: ['@disabled'], events: ['submit'], keys: [], attrs: ['$disabled', '$submitting', '$submitted'] }
319
+ { id: 'grid', name: 'Grid', lines: 799, desc: 'Virtual-scrolling data grid — 100K+ rows at 60fps.', props: ['@data', '@columns', '@rowHeight', '@overscan', '@striped', '@beforeEdit', '@afterEdit'], events: [], keys: ['Arrows', 'Tab', 'Enter', 'F2', 'Esc', 'Home', 'End', 'PgUp', 'PgDn', 'Ctrl+A', 'Ctrl+C', 'Ctrl+V', 'Ctrl+X'], attrs: ['$editing', '$selecting', '$sorted'] }
320
+ { id: 'accordion', name: 'Accordion', lines: 86, desc: 'Expand/collapse sections, single or multiple mode.', props: ['@multiple'], events: ['change'], keys: ['Enter', 'Space', '↕', 'Home', 'End'], attrs: ['$item', '$trigger', '$content', '$open', '$disabled'] }
321
+ { id: 'badge', name: 'Badge', lines: 5, desc: 'Inline label with variant (solid/outline/subtle).', props: ['@variant'], events: [], keys: [], attrs: ['$variant'] }
322
+ { id: 'skeleton', name: 'Skeleton', lines: 10, desc: 'Loading placeholder with shimmer animation.', props: ['@width', '@height', '@circle', '@label'], events: [], keys: [], attrs: ['$circle'] }
323
+ { id: 'spinner', name: 'Spinner', lines: 6, desc: 'Loading indicator with accessible status.', props: ['@label', '@size'], events: [], keys: [], attrs: [] }
324
+ { id: 'card', name: 'Card', lines: 6, desc: 'Structured container with header/content/footer.', props: ['@interactive'], events: [], keys: [], attrs: ['$interactive'] }
325
+ { id: 'label', name: 'Label', lines: 6, desc: 'Standalone accessible form label.', props: ['@for', '@required'], events: [], keys: [], attrs: ['$required'] }
326
+ { id: 'textarea', name: 'Textarea', lines: 33, desc: 'Auto-resizing text area with focus and validation tracking.', props: ['@value', '@placeholder', '@disabled', '@required', '@rows', '@autoResize'], events: [], keys: [], attrs: ['$disabled', '$focused', '$touched'] }
327
+ { id: 'native-select', name: 'NativeSelect', lines: 18, desc: 'Styled native select element with state tracking.', props: ['@value', '@disabled', '@required'], events: ['change'], keys: [], attrs: ['$disabled', '$focused'] }
328
+ { id: 'input-group', name: 'InputGroup', lines: 11, desc: 'Input with prefix/suffix addon elements.', props: ['@disabled'], events: [], keys: [], attrs: ['$disabled', '$focused'] }
329
+ { id: 'button-group', name: 'ButtonGroup', lines: 11, desc: 'Grouped buttons with ARIA group semantics.', props: ['@orientation', '@disabled', '@label'], events: [], keys: [], attrs: ['$orientation', '$disabled'] }
330
+ { id: 'alert-dialog', name: 'AlertDialog', lines: 74, desc: 'Non-dismissable modal requiring explicit user action.', props: ['@open', '@initialFocus'], events: ['close'], keys: ['Tab'], attrs: ['$open'] }
331
+ { id: 'breadcrumb', name: 'Breadcrumb', lines: 25, desc: 'Navigation trail with separator and current page.', props: ['@separator', '@label'], events: [], keys: [], attrs: ['$current'] }
332
+ { id: 'table', name: 'Table', lines: 9, desc: 'Semantic table wrapper with optional caption and striped rows.', props: ['@caption', '@striped'], events: [], keys: [], attrs: ['$striped'] }
333
+ { id: 'collapsible', name: 'Collapsible', lines: 33, desc: 'Single open/close section with animated expand.', props: ['@open', '@disabled'], events: ['change'], keys: ['Enter', 'Space'], attrs: ['$open', '$disabled'] }
334
+ { id: 'pagination', name: 'Pagination', lines: 96, desc: 'Page navigation with prev/next and ellipsis gaps.', props: ['@page', '@total', '@perPage', '@siblingCount'], events: ['change'], keys: ['← →', 'Home', 'End'], attrs: ['$active', '$disabled', '$ellipsis'] }
335
+ { id: 'carousel', name: 'Carousel', lines: 78, desc: 'Slide carousel with autoplay, loop, and keyboard nav.', props: ['@orientation', '@loop', '@autoplay', '@interval', '@label'], events: ['change'], keys: ['← →', '↕', 'Home', 'End'], attrs: ['$orientation', '$active', '$prev', '$next'] }
336
+ { id: 'resizable', name: 'Resizable', lines: 89, desc: 'Draggable resize handles between panels.', props: ['@orientation', '@minSize', '@maxSize'], events: ['resize'], keys: ['← →', '↕'], attrs: ['$orientation', '$dragging'] }
276
337
  ]
277
338
  _infoName := ''
278
339
  _infoDesc := ''
@@ -304,6 +365,7 @@ export WidgetGallery = component
304
365
  lines = text.split('\n')
305
366
  gutterEl.textContent = lines.map((_, i) -> String(i + 1)).join('\n') if gutterEl
306
367
  codeEl.textContent = text
368
+ codeEl.className = 'source-code language-rip'
307
369
  @_ensureHljs =>
308
370
  hljs.highlightElement(codeEl) if window.hljs
309
371
  , 0
@@ -350,13 +412,12 @@ export WidgetGallery = component
350
412
  gridCellRef := ''
351
413
 
352
414
  _updateGridRef: ->
353
- cell = document.querySelector('.grid-container [data-active]')
415
+ cell = document.querySelector('.grid-container td[data-active]')
354
416
  if cell
355
- row = cell.parentElement
356
- rows = Array.from(row.parentElement.children).filter (r) -> r.tagName is 'TR' and not r.classList.contains('spacer')
357
- ri = rows.indexOf(row)
358
- ci = Array.from(row.children).indexOf(cell)
359
- gridCellRef = "R#{ri + 1}:C#{ci + 1}" if ri >= 0 and ci >= 0
417
+ tr = cell.parentElement
418
+ rowNum = tr?.firstElementChild?.textContent?.trim()
419
+ ci = Array.from(tr.children).indexOf(cell)
420
+ gridCellRef = if rowNum then "R#{rowNum}:C#{ci + 1}" else ''
360
421
  else
361
422
  gridCellRef = ''
362
423
 
@@ -395,7 +456,7 @@ export WidgetGallery = component
395
456
  { _row: 12, name: 'Leo Martin', role: 'Engineer', age: 24, city: 'Boston' },
396
457
  ]
397
458
 
398
- sectionIds =! ['select', 'combobox', 'multi-select', 'autocomplete', 'checkbox', 'toggle', 'toggle-group', 'radio-group', 'checkbox-group', 'input', 'number-field', 'slider', 'otp-field', 'date-picker', 'editable-value', 'tabs', 'menu', 'context-menu', 'menubar', 'nav-menu', 'toolbar', 'dialog', 'drawer', 'popover', 'tooltip', 'preview-card', 'toast', 'button', 'separator', 'progress', 'meter', 'avatar', 'scroll-area', 'field', 'form', 'grid', 'accordion']
459
+ sectionIds =! ['select', 'combobox', 'multi-select', 'autocomplete', 'checkbox', 'toggle', 'toggle-group', 'radio-group', 'checkbox-group', 'input', 'textarea', 'native-select', 'number-field', 'slider', 'otp-field', 'date-picker', 'editable-value', 'input-group', 'tabs', 'menu', 'context-menu', 'menubar', 'nav-menu', 'toolbar', 'breadcrumb', 'dialog', 'alert-dialog', 'drawer', 'popover', 'tooltip', 'preview-card', 'toast', 'button', 'badge', 'card', 'separator', 'progress', 'meter', 'spinner', 'skeleton', 'avatar', 'label', 'scroll-area', 'field', 'form', 'button-group', 'grid', 'accordion', 'table', 'collapsible', 'pagination', 'carousel', 'resizable']
399
460
 
400
461
  _fadeTo: (el) ->
401
462
  return unless el
@@ -438,7 +499,7 @@ export WidgetGallery = component
438
499
  h1 "Rip UI"
439
500
  button.theme-toggle @click: (=> @_toggleDark())
440
501
  if darkMode then "☀" else "☾"
441
- p "38 headless, accessible components. Zero CSS, zero dependencies."
502
+ p "54 headless, accessible components. Zero CSS, zero dependencies."
442
503
 
443
504
  .toc
444
505
  .toc-nav @keydown: @_onTocKeydown
@@ -459,11 +520,14 @@ export WidgetGallery = component
459
520
  .toc-group
460
521
  .toc-label "Input"
461
522
  a @mouseenter: (=> tocActive = 'input'), @click: (=> tocActive = 'input'), href: "#input", $active: (tocActive is 'input')?!, "Input"
523
+ a @mouseenter: (=> tocActive = 'textarea'), @click: (=> tocActive = 'textarea'), href: "#textarea", $active: (tocActive is 'textarea')?!, "Textarea"
524
+ a @mouseenter: (=> tocActive = 'native-select'), @click: (=> tocActive = 'native-select'), href: "#native-select", $active: (tocActive is 'native-select')?!, "NativeSelect"
462
525
  a @mouseenter: (=> tocActive = 'number-field'), @click: (=> tocActive = 'number-field'), href: "#number-field", $active: (tocActive is 'number-field')?!, "NumberField"
463
526
  a @mouseenter: (=> tocActive = 'slider'), @click: (=> tocActive = 'slider'), href: "#slider", $active: (tocActive is 'slider')?!, "Slider"
464
527
  a @mouseenter: (=> tocActive = 'otp-field'), @click: (=> tocActive = 'otp-field'), href: "#otp-field", $active: (tocActive is 'otp-field')?!, "OTPField"
465
528
  a @mouseenter: (=> tocActive = 'date-picker'), @click: (=> tocActive = 'date-picker'), href: "#date-picker", $active: (tocActive is 'date-picker')?!, "DatePicker"
466
529
  a @mouseenter: (=> tocActive = 'editable-value'), @click: (=> tocActive = 'editable-value'), href: "#editable-value", $active: (tocActive is 'editable-value')?!, "EditableValue"
530
+ a @mouseenter: (=> tocActive = 'input-group'), @click: (=> tocActive = 'input-group'), href: "#input-group", $active: (tocActive is 'input-group')?!, "InputGroup"
467
531
  .toc-group
468
532
  .toc-label "Navigation"
469
533
  a @mouseenter: (=> tocActive = 'tabs'), @click: (=> tocActive = 'tabs'), href: "#tabs", $active: (tocActive is 'tabs')?!, "Tabs"
@@ -472,9 +536,11 @@ export WidgetGallery = component
472
536
  a @mouseenter: (=> tocActive = 'menubar'), @click: (=> tocActive = 'menubar'), href: "#menubar", $active: (tocActive is 'menubar')?!, "Menubar"
473
537
  a @mouseenter: (=> tocActive = 'nav-menu'), @click: (=> tocActive = 'nav-menu'), href: "#nav-menu", $active: (tocActive is 'nav-menu')?!, "NavMenu"
474
538
  a @mouseenter: (=> tocActive = 'toolbar'), @click: (=> tocActive = 'toolbar'), href: "#toolbar", $active: (tocActive is 'toolbar')?!, "Toolbar"
539
+ a @mouseenter: (=> tocActive = 'breadcrumb'), @click: (=> tocActive = 'breadcrumb'), href: "#breadcrumb", $active: (tocActive is 'breadcrumb')?!, "Breadcrumb"
475
540
  .toc-group
476
541
  .toc-label "Overlay"
477
542
  a @mouseenter: (=> tocActive = 'dialog'), @click: (=> tocActive = 'dialog'), href: "#dialog", $active: (tocActive is 'dialog')?!, "Dialog"
543
+ a @mouseenter: (=> tocActive = 'alert-dialog'), @click: (=> tocActive = 'alert-dialog'), href: "#alert-dialog", $active: (tocActive is 'alert-dialog')?!, "AlertDialog"
478
544
  a @mouseenter: (=> tocActive = 'drawer'), @click: (=> tocActive = 'drawer'), href: "#drawer", $active: (tocActive is 'drawer')?!, "Drawer"
479
545
  a @mouseenter: (=> tocActive = 'popover'), @click: (=> tocActive = 'popover'), href: "#popover", $active: (tocActive is 'popover')?!, "Popover"
480
546
  a @mouseenter: (=> tocActive = 'tooltip'), @click: (=> tocActive = 'tooltip'), href: "#tooltip", $active: (tocActive is 'tooltip')?!, "Tooltip"
@@ -483,20 +549,33 @@ export WidgetGallery = component
483
549
  .toc-group
484
550
  .toc-label "Display"
485
551
  a @mouseenter: (=> tocActive = 'button'), @click: (=> tocActive = 'button'), href: "#button", $active: (tocActive is 'button')?!, "Button"
552
+ a @mouseenter: (=> tocActive = 'badge'), @click: (=> tocActive = 'badge'), href: "#badge", $active: (tocActive is 'badge')?!, "Badge"
553
+ a @mouseenter: (=> tocActive = 'card'), @click: (=> tocActive = 'card'), href: "#card", $active: (tocActive is 'card')?!, "Card"
486
554
  a @mouseenter: (=> tocActive = 'separator'), @click: (=> tocActive = 'separator'), href: "#separator", $active: (tocActive is 'separator')?!, "Separator"
487
555
  a @mouseenter: (=> tocActive = 'progress'), @click: (=> tocActive = 'progress'), href: "#progress", $active: (tocActive is 'progress')?!, "Progress"
488
556
  a @mouseenter: (=> tocActive = 'meter'), @click: (=> tocActive = 'meter'), href: "#meter", $active: (tocActive is 'meter')?!, "Meter"
557
+ a @mouseenter: (=> tocActive = 'spinner'), @click: (=> tocActive = 'spinner'), href: "#spinner", $active: (tocActive is 'spinner')?!, "Spinner"
558
+ a @mouseenter: (=> tocActive = 'skeleton'), @click: (=> tocActive = 'skeleton'), href: "#skeleton", $active: (tocActive is 'skeleton')?!, "Skeleton"
489
559
  a @mouseenter: (=> tocActive = 'avatar'), @click: (=> tocActive = 'avatar'), href: "#avatar", $active: (tocActive is 'avatar')?!, "Avatar"
560
+ a @mouseenter: (=> tocActive = 'label'), @click: (=> tocActive = 'label'), href: "#label", $active: (tocActive is 'label')?!, "Label"
490
561
  a @mouseenter: (=> tocActive = 'scroll-area'), @click: (=> tocActive = 'scroll-area'), href: "#scroll-area", $active: (tocActive is 'scroll-area')?!, "ScrollArea"
491
562
  .toc-group
492
563
  .toc-label "Form"
493
564
  a @mouseenter: (=> tocActive = 'field'), @click: (=> tocActive = 'field'), href: "#field", $active: (tocActive is 'field')?!, "Field"
494
565
  a @mouseenter: (=> tocActive = 'fieldset'), @click: (=> tocActive = 'fieldset'), href: "#fieldset", $active: (tocActive is 'fieldset')?!, "Fieldset"
495
566
  a @mouseenter: (=> tocActive = 'form'), @click: (=> tocActive = 'form'), href: "#form", $active: (tocActive is 'form')?!, "Form"
567
+ a @mouseenter: (=> tocActive = 'button-group'), @click: (=> tocActive = 'button-group'), href: "#button-group", $active: (tocActive is 'button-group')?!, "ButtonGroup"
496
568
  .toc-group
497
569
  .toc-label "Data"
498
570
  a @mouseenter: (=> tocActive = 'grid'), @click: (=> tocActive = 'grid'), href: "#grid", $active: (tocActive is 'grid')?!, "Grid"
499
571
  a @mouseenter: (=> tocActive = 'accordion'), @click: (=> tocActive = 'accordion'), href: "#accordion", $active: (tocActive is 'accordion')?!, "Accordion"
572
+ a @mouseenter: (=> tocActive = 'table'), @click: (=> tocActive = 'table'), href: "#table", $active: (tocActive is 'table')?!, "Table"
573
+ a @mouseenter: (=> tocActive = 'collapsible'), @click: (=> tocActive = 'collapsible'), href: "#collapsible", $active: (tocActive is 'collapsible')?!, "Collapsible"
574
+ .toc-group
575
+ .toc-label "Interactive"
576
+ a @mouseenter: (=> tocActive = 'pagination'), @click: (=> tocActive = 'pagination'), href: "#pagination", $active: (tocActive is 'pagination')?!, "Pagination"
577
+ a @mouseenter: (=> tocActive = 'carousel'), @click: (=> tocActive = 'carousel'), href: "#carousel", $active: (tocActive is 'carousel')?!, "Carousel"
578
+ a @mouseenter: (=> tocActive = 'resizable'), @click: (=> tocActive = 'resizable'), href: "#resizable", $active: (tocActive is 'resizable')?!, "Resizable"
500
579
  .toc-detail
501
580
  if tocActive
502
581
  .
@@ -529,16 +608,11 @@ export WidgetGallery = component
529
608
  else
530
609
  .toc-empty "← Hover a component to see its API"
531
610
 
532
- # ================================================================
611
+ # ========================================================================
533
612
  # SELECT
534
- # ================================================================
613
+ # ========================================================================
535
614
  .section id: "select"
536
- .section-title "Select"
537
- span.badge @click: (=> @_viewSource('select')), "184 lines ❐"
538
- .section-nav
539
- a @click: (=> @_navSection('select', 'prev')), "‹"
540
- a @click: (=> @_navSection('select', 'home')), "⌂"
541
- a @click: (=> @_navSection('select', 'next')), "›"
615
+ SectionHead tag: "select", name: "Select", lines: 147
542
616
  .section-desc "Dropdown with typeahead, keyboard nav, ARIA listbox."
543
617
  .demo-row
544
618
  .demo-label "Basic select"
@@ -582,16 +656,11 @@ export WidgetGallery = component
582
656
  code "$highlighted"
583
657
  code "$selected"
584
658
 
585
- # ================================================================
659
+ # ========================================================================
586
660
  # COMBOBOX
587
- # ================================================================
661
+ # ========================================================================
588
662
  .section id: "combobox"
589
- .section-title "Combobox"
590
- span.badge @click: (=> @_viewSource('combobox')), "153 lines ❐"
591
- .section-nav
592
- a @click: (=> @_navSection('combobox', 'prev')), "‹"
593
- a @click: (=> @_navSection('combobox', 'home')), "⌂"
594
- a @click: (=> @_navSection('combobox', 'next')), "›"
663
+ SectionHead tag: "combobox", name: "Combobox", lines: 124
595
664
  .section-desc "Filterable input + listbox for search-as-you-type."
596
665
  .demo-row
597
666
  .demo-label "Search fruits"
@@ -627,16 +696,11 @@ export WidgetGallery = component
627
696
  code "$highlighted"
628
697
  code "$empty"
629
698
 
630
- # ================================================================
699
+ # ========================================================================
631
700
  # MULTI-SELECT
632
- # ================================================================
701
+ # ========================================================================
633
702
  .section id: "multi-select"
634
- .section-title "MultiSelect"
635
- span.badge @click: (=> @_viewSource('multi-select')), "158 lines ❐"
636
- .section-nav
637
- a @click: (=> @_navSection('multi-select', 'prev')), "‹"
638
- a @click: (=> @_navSection('multi-select', 'home')), "⌂"
639
- a @click: (=> @_navSection('multi-select', 'next')), "›"
703
+ SectionHead tag: "multi-select", name: "MultiSelect", lines: 130
640
704
  .section-desc "Multi-select with chips, filtering, and keyboard navigation."
641
705
  .demo-row
642
706
  .demo-label "Pick colors"
@@ -671,16 +735,11 @@ export WidgetGallery = component
671
735
  code "$highlighted"
672
736
  code "$selected"
673
737
 
674
- # ================================================================
738
+ # ========================================================================
675
739
  # AUTOCOMPLETE
676
- # ================================================================
740
+ # ========================================================================
677
741
  .section id: "autocomplete"
678
- .section-title "Autocomplete"
679
- span.badge @click: (=> @_viewSource('autocomplete')), "141 lines ❐"
680
- .section-nav
681
- a @click: (=> @_navSection('autocomplete', 'prev')), "‹"
682
- a @click: (=> @_navSection('autocomplete', 'home')), "⌂"
683
- a @click: (=> @_navSection('autocomplete', 'next')), "›"
742
+ SectionHead tag: "autocomplete", name: "Autocomplete", lines: 115
684
743
  .section-desc "Suggestion input — type to filter, select to fill."
685
744
  .demo-row
686
745
  .demo-label "Search cities"
@@ -709,16 +768,11 @@ export WidgetGallery = component
709
768
  code "$disabled"
710
769
  code "$clear"
711
770
 
712
- # ================================================================
771
+ # ========================================================================
713
772
  # CHECKBOX
714
- # ================================================================
773
+ # ========================================================================
715
774
  .section id: "checkbox"
716
- .section-title "Checkbox"
717
- span.badge @click: (=> @_viewSource('checkbox')), "33 lines ❐"
718
- .section-nav
719
- a @click: (=> @_navSection('checkbox', 'prev')), "‹"
720
- a @click: (=> @_navSection('checkbox', 'home')), "⌂"
721
- a @click: (=> @_navSection('checkbox', 'next')), "›"
775
+ SectionHead tag: "checkbox", name: "Checkbox", lines: 18
722
776
  .section-desc "Toggle with checkbox or switch semantics. Supports indeterminate."
723
777
  .demo-row
724
778
  .demo-label "Checkbox (unchecked)"
@@ -752,16 +806,11 @@ export WidgetGallery = component
752
806
  code "$indeterminate"
753
807
  code "$disabled"
754
808
 
755
- # ================================================================
809
+ # ========================================================================
756
810
  # TOGGLE
757
- # ================================================================
811
+ # ========================================================================
758
812
  .section id: "toggle"
759
- .section-title "Toggle"
760
- span.badge @click: (=> @_viewSource('toggle')), "24 lines ❐"
761
- .section-nav
762
- a @click: (=> @_navSection('toggle', 'prev')), "‹"
763
- a @click: (=> @_navSection('toggle', 'home')), "⌂"
764
- a @click: (=> @_navSection('toggle', 'next')), "›"
813
+ SectionHead tag: "toggle", name: "Toggle", lines: 13
765
814
  .section-desc "Stateful toggle button with pressed state."
766
815
  .demo-row
767
816
  .demo-label "Tap to toggle"
@@ -782,16 +831,11 @@ export WidgetGallery = component
782
831
  code "$pressed"
783
832
  code "$disabled"
784
833
 
785
- # ================================================================
834
+ # ========================================================================
786
835
  # TOGGLE GROUP
787
- # ================================================================
836
+ # ========================================================================
788
837
  .section id: "toggle-group"
789
- .section-title "ToggleGroup"
790
- span.badge @click: (=> @_viewSource('toggle-group')), "78 lines ❐"
791
- .section-nav
792
- a @click: (=> @_navSection('toggle-group', 'prev')), "‹"
793
- a @click: (=> @_navSection('toggle-group', 'home')), "⌂"
794
- a @click: (=> @_navSection('toggle-group', 'next')), "›"
838
+ SectionHead tag: "toggle-group", name: "ToggleGroup", lines: 59
795
839
  .section-desc "Single or multi-select toggle buttons."
796
840
  .demo-row
797
841
  .demo-label "Text alignment"
@@ -831,16 +875,11 @@ export WidgetGallery = component
831
875
  code "$pressed"
832
876
  code "$value"
833
877
 
834
- # ================================================================
878
+ # ========================================================================
835
879
  # RADIO GROUP
836
- # ================================================================
880
+ # ========================================================================
837
881
  .section id: "radio-group"
838
- .section-title "RadioGroup"
839
- span.badge @click: (=> @_viewSource('radio-group')), "67 lines ❐"
840
- .section-nav
841
- a @click: (=> @_navSection('radio-group', 'prev')), "‹"
842
- a @click: (=> @_navSection('radio-group', 'home')), "⌂"
843
- a @click: (=> @_navSection('radio-group', 'next')), "›"
882
+ SectionHead tag: "radio-group", name: "RadioGroup", lines: 50
844
883
  .section-desc "Exactly one option selected. Arrow keys move focus and selection."
845
884
  .demo-row
846
885
  .demo-label "Font size"
@@ -873,16 +912,11 @@ export WidgetGallery = component
873
912
  code "$checked"
874
913
  code "$value"
875
914
 
876
- # ================================================================
915
+ # ========================================================================
877
916
  # CHECKBOX GROUP
878
- # ================================================================
917
+ # ========================================================================
879
918
  .section id: "checkbox-group"
880
- .section-title "CheckboxGroup"
881
- span.badge @click: (=> @_viewSource('checkbox-group')), "65 lines ❐"
882
- .section-nav
883
- a @click: (=> @_navSection('checkbox-group', 'prev')), "‹"
884
- a @click: (=> @_navSection('checkbox-group', 'home')), "⌂"
885
- a @click: (=> @_navSection('checkbox-group', 'next')), "›"
919
+ SectionHead tag: "checkbox-group", name: "CheckboxGroup", lines: 46
886
920
  .section-desc "Multiple options checked independently."
887
921
  .demo-row
888
922
  .demo-label "Toppings"
@@ -914,16 +948,11 @@ export WidgetGallery = component
914
948
  code "$checked"
915
949
  code "$value"
916
950
 
917
- # ================================================================
951
+ # ========================================================================
918
952
  # INPUT
919
- # ================================================================
953
+ # ========================================================================
920
954
  .section id: "input"
921
- .section-title "Input"
922
- span.badge @click: (=> @_viewSource('input')), "35 lines ❐"
923
- .section-nav
924
- a @click: (=> @_navSection('input', 'prev')), "‹"
925
- a @click: (=> @_navSection('input', 'home')), "⌂"
926
- a @click: (=> @_navSection('input', 'next')), "›"
955
+ SectionHead tag: "input", name: "Input", lines: 24
927
956
  .section-desc "Headless input tracking focus, touch, and validation state."
928
957
  .demo-row
929
958
  .demo-label "Text input"
@@ -944,16 +973,11 @@ export WidgetGallery = component
944
973
  code "$focused"
945
974
  code "$touched"
946
975
 
947
- # ================================================================
976
+ # ========================================================================
948
977
  # NUMBER FIELD
949
- # ================================================================
978
+ # ========================================================================
950
979
  .section id: "number-field"
951
- .section-title "NumberField"
952
- span.badge @click: (=> @_viewSource('number-field')), "162 lines ❐"
953
- .section-nav
954
- a @click: (=> @_navSection('number-field', 'prev')), "‹"
955
- a @click: (=> @_navSection('number-field', 'home')), "⌂"
956
- a @click: (=> @_navSection('number-field', 'next')), "›"
980
+ SectionHead tag: "number-field", name: "NumberField", lines: 132
957
981
  .section-desc "Number input with increment/decrement buttons, hold-to-repeat, and keyboard stepping."
958
982
  .demo-row
959
983
  .demo-label "Quantity (1-99)"
@@ -993,16 +1017,11 @@ export WidgetGallery = component
993
1017
  code "$decrement"
994
1018
  code "$increment"
995
1019
 
996
- # ================================================================
1020
+ # ========================================================================
997
1021
  # SLIDER
998
- # ================================================================
1022
+ # ========================================================================
999
1023
  .section id: "slider"
1000
- .section-title "Slider"
1001
- span.badge @click: (=> @_viewSource('slider')), "165 lines ❐"
1002
- .section-nav
1003
- a @click: (=> @_navSection('slider', 'prev')), "‹"
1004
- a @click: (=> @_navSection('slider', 'home')), "⌂"
1005
- a @click: (=> @_navSection('slider', 'next')), "›"
1024
+ SectionHead tag: "slider", name: "Slider", lines: 130
1006
1025
  .section-desc "Draggable range input with pointer capture and keyboard stepping."
1007
1026
  .demo-row
1008
1027
  .demo-label "Single thumb"
@@ -1046,16 +1065,11 @@ export WidgetGallery = component
1046
1065
  code "$thumb"
1047
1066
  code "$active"
1048
1067
 
1049
- # ================================================================
1068
+ # ========================================================================
1050
1069
  # OTP FIELD
1051
- # ================================================================
1070
+ # ========================================================================
1052
1071
  .section id: "otp-field"
1053
- .section-title "OTPField"
1054
- span.badge @click: (=> @_viewSource('otp-field')), "89 lines ❐"
1055
- .section-nav
1056
- a @click: (=> @_navSection('otp-field', 'prev')), "‹"
1057
- a @click: (=> @_navSection('otp-field', 'home')), "⌂"
1058
- a @click: (=> @_navSection('otp-field', 'next')), "›"
1072
+ SectionHead tag: "otp-field", name: "OTPField", lines: 72
1059
1073
  .section-desc "Multi-digit code input with auto-advance, backspace nav, and paste."
1060
1074
  .demo-row
1061
1075
  .demo-label "6-digit code"
@@ -1089,16 +1103,11 @@ export WidgetGallery = component
1089
1103
  code "$complete"
1090
1104
  code "$filled"
1091
1105
 
1092
- # ================================================================
1106
+ # ========================================================================
1093
1107
  # DATE PICKER
1094
- # ================================================================
1108
+ # ========================================================================
1095
1109
  .section id: "date-picker"
1096
- .section-title "DatePicker"
1097
- span.badge @click: (=> @_viewSource('date-picker')), "214 lines ❐"
1098
- .section-nav
1099
- a @click: (=> @_navSection('date-picker', 'prev')), "‹"
1100
- a @click: (=> @_navSection('date-picker', 'home')), "⌂"
1101
- a @click: (=> @_navSection('date-picker', 'next')), "›"
1110
+ SectionHead tag: "date-picker", name: "DatePicker", lines: 172
1102
1111
  .section-desc "Calendar dropdown for single date or range selection."
1103
1112
  .demo-row
1104
1113
  .demo-label "Single date"
@@ -1135,16 +1144,11 @@ export WidgetGallery = component
1135
1144
  code "$today"
1136
1145
  code "$in-range"
1137
1146
 
1138
- # ================================================================
1147
+ # ========================================================================
1139
1148
  # EDITABLE VALUE
1140
- # ================================================================
1149
+ # ========================================================================
1141
1150
  .section id: "editable-value"
1142
- .section-title "EditableValue"
1143
- span.badge @click: (=> @_viewSource('editable-value')), "80 lines ❐"
1144
- .section-nav
1145
- a @click: (=> @_navSection('editable-value', 'prev')), "‹"
1146
- a @click: (=> @_navSection('editable-value', 'home')), "⌂"
1147
- a @click: (=> @_navSection('editable-value', 'next')), "›"
1151
+ SectionHead tag: "editable-value", name: "EditableValue", lines: 58
1148
1152
  .section-desc "Click the edit icon to modify the value inline."
1149
1153
  .demo-row
1150
1154
  EditableValue @save: (=> editName = editName)
@@ -1172,16 +1176,92 @@ export WidgetGallery = component
1172
1176
  code "$saving"
1173
1177
  code "$edit-trigger"
1174
1178
 
1175
- # ================================================================
1179
+ # ========================================================================
1180
+ # TEXTAREA
1181
+ # ========================================================================
1182
+ .section id: "textarea"
1183
+ SectionHead tag: "textarea", name: "Textarea", lines: 33
1184
+ .section-desc "Auto-resizing text area with focus and validation tracking."
1185
+ .demo-row
1186
+ .demo-label "Auto-resize textarea"
1187
+ Textarea value <=> textareaVal, placeholder: "Type something...", autoResize: true
1188
+ .status "length: #{textareaVal.length}"
1189
+ .api
1190
+ dl
1191
+ dt "Props"
1192
+ dd
1193
+ code "@value"
1194
+ code "@placeholder"
1195
+ code "@disabled"
1196
+ code "@required"
1197
+ code "@rows"
1198
+ code "@autoResize"
1199
+ dt "Data"
1200
+ dd
1201
+ code "$disabled"
1202
+ code "$focused"
1203
+ code "$touched"
1204
+
1205
+ # ========================================================================
1206
+ # NATIVE SELECT
1207
+ # ========================================================================
1208
+ .section id: "native-select"
1209
+ SectionHead tag: "native-select", name: "NativeSelect", lines: 18
1210
+ .section-desc "Native browser select element with state tracking."
1211
+ .demo-row.native-select-demo
1212
+ .demo-label "Choose a color"
1213
+ NativeSelect value <=> nativeSelectVal
1214
+ option value: "", "Pick one..."
1215
+ option value: "red", "Red"
1216
+ option value: "green", "Green"
1217
+ option value: "blue", "Blue"
1218
+ .status "value: '#{nativeSelectVal}'"
1219
+ .api
1220
+ dl
1221
+ dt "Props"
1222
+ dd
1223
+ code "@value"
1224
+ code "@disabled"
1225
+ code "@required"
1226
+ dt "Events"
1227
+ dd
1228
+ code "change"
1229
+ dt "Data"
1230
+ dd
1231
+ code "$disabled"
1232
+ code "$focused"
1233
+
1234
+ # ========================================================================
1235
+ # INPUT GROUP
1236
+ # ========================================================================
1237
+ .section id: "input-group"
1238
+ SectionHead tag: "input-group", name: "InputGroup", lines: 11
1239
+ .section-desc "Input with prefix/suffix addon elements."
1240
+ .demo-row.input-group-demo
1241
+ .demo-label "With prefix"
1242
+ InputGroup
1243
+ span $prefix: true, "$"
1244
+ input type: "text", placeholder: "Amount"
1245
+ .demo-row.input-group-demo
1246
+ .demo-label "With suffix"
1247
+ InputGroup
1248
+ input type: "text", placeholder: "Search..."
1249
+ button $suffix: true, "Go"
1250
+ .api
1251
+ dl
1252
+ dt "Props"
1253
+ dd
1254
+ code "@disabled"
1255
+ dt "Data"
1256
+ dd
1257
+ code "$disabled"
1258
+ code "$focused"
1259
+
1260
+ # ========================================================================
1176
1261
  # TABS
1177
- # ================================================================
1262
+ # ========================================================================
1178
1263
  .section id: "tabs"
1179
- .section-title "Tabs"
1180
- span.badge @click: (=> @_viewSource('tabs')), "124 lines ❐"
1181
- .section-nav
1182
- a @click: (=> @_navSection('tabs', 'prev')), "‹"
1183
- a @click: (=> @_navSection('tabs', 'home')), "⌂"
1184
- a @click: (=> @_navSection('tabs', 'next')), "›"
1264
+ SectionHead tag: "tabs", name: "Tabs", lines: 91
1185
1265
  .section-desc "Tab panel with roving tabindex and arrow key navigation."
1186
1266
  .demo-row
1187
1267
  .demo-label "Basic tabs"
@@ -1221,16 +1301,11 @@ export WidgetGallery = component
1221
1301
  code "$active"
1222
1302
  code "$disabled"
1223
1303
 
1224
- # ================================================================
1304
+ # ========================================================================
1225
1305
  # MENU
1226
- # ================================================================
1306
+ # ========================================================================
1227
1307
  .section id: "menu"
1228
- .section-title "Menu"
1229
- span.badge @click: (=> @_viewSource('menu')), "162 lines ❐"
1230
- .section-nav
1231
- a @click: (=> @_navSection('menu', 'prev')), "‹"
1232
- a @click: (=> @_navSection('menu', 'home')), "⌂"
1233
- a @click: (=> @_navSection('menu', 'next')), "›"
1308
+ SectionHead tag: "menu", name: "Menu", lines: 133
1234
1309
  .section-desc "Dropdown action menu with keyboard navigation."
1235
1310
  .demo-row
1236
1311
  .demo-label "Click to open menu"
@@ -1266,16 +1341,11 @@ export WidgetGallery = component
1266
1341
  code "$highlighted"
1267
1342
  code "$value"
1268
1343
 
1269
- # ================================================================
1344
+ # ========================================================================
1270
1345
  # CONTEXT MENU
1271
- # ================================================================
1346
+ # ========================================================================
1272
1347
  .section id: "context-menu"
1273
- .section-title "ContextMenu"
1274
- span.badge @click: (=> @_viewSource('context-menu')), "98 lines ❐"
1275
- .section-nav
1276
- a @click: (=> @_navSection('context-menu', 'prev')), "‹"
1277
- a @click: (=> @_navSection('context-menu', 'home')), "⌂"
1278
- a @click: (=> @_navSection('context-menu', 'next')), "›"
1348
+ SectionHead tag: "context-menu", name: "ContextMenu", lines: 80
1279
1349
  .section-desc "Right-click the dashed area to open a context menu."
1280
1350
  ContextMenu
1281
1351
  div $trigger: true
@@ -1308,16 +1378,11 @@ export WidgetGallery = component
1308
1378
  code "$disabled"
1309
1379
  code "$value"
1310
1380
 
1311
- # ================================================================
1381
+ # ========================================================================
1312
1382
  # MENUBAR
1313
- # ================================================================
1383
+ # ========================================================================
1314
1384
  .section id: "menubar"
1315
- .section-title "Menubar"
1316
- span.badge @click: (=> @_viewSource('menubar')), "155 lines ❐"
1317
- .section-nav
1318
- a @click: (=> @_navSection('menubar', 'prev')), "‹"
1319
- a @click: (=> @_navSection('menubar', 'home')), "⌂"
1320
- a @click: (=> @_navSection('menubar', 'next')), "›"
1385
+ SectionHead tag: "menubar", name: "Menubar", lines: 125
1321
1386
  .section-desc "Horizontal menu bar with dropdown menus."
1322
1387
  .demo-row
1323
1388
  Menubar
@@ -1355,16 +1420,11 @@ export WidgetGallery = component
1355
1420
  code "$highlighted"
1356
1421
  code "$value"
1357
1422
 
1358
- # ================================================================
1423
+ # ========================================================================
1359
1424
  # NAVIGATION MENU
1360
- # ================================================================
1425
+ # ========================================================================
1361
1426
  .section id: "nav-menu"
1362
- .section-title "NavigationMenu"
1363
- span.badge @click: (=> @_viewSource('nav-menu')), "132 lines ❐"
1364
- .section-nav
1365
- a @click: (=> @_navSection('nav-menu', 'prev')), "‹"
1366
- a @click: (=> @_navSection('nav-menu', 'home')), "⌂"
1367
- a @click: (=> @_navSection('nav-menu', 'next')), "›"
1427
+ SectionHead tag: "nav-menu", name: "NavigationMenu", lines: 99
1368
1428
  .section-desc "Site navigation with dropdown panels."
1369
1429
  .demo-row
1370
1430
  NavigationMenu
@@ -1399,16 +1459,11 @@ export WidgetGallery = component
1399
1459
  code "$orientation"
1400
1460
  code "$open"
1401
1461
 
1402
- # ================================================================
1462
+ # ========================================================================
1403
1463
  # TOOLBAR
1404
- # ================================================================
1464
+ # ========================================================================
1405
1465
  .section id: "toolbar"
1406
- .section-title "Toolbar"
1407
- span.badge @click: (=> @_viewSource('toolbar')), "46 lines ❐"
1408
- .section-nav
1409
- a @click: (=> @_navSection('toolbar', 'prev')), "‹"
1410
- a @click: (=> @_navSection('toolbar', 'home')), "⌂"
1411
- a @click: (=> @_navSection('toolbar', 'next')), "›"
1466
+ SectionHead tag: "toolbar", name: "Toolbar", lines: 31
1412
1467
  .section-desc "Groups controls with roving tabindex keyboard navigation."
1413
1468
  .demo-row
1414
1469
  .demo-label "Text"
@@ -1462,17 +1517,35 @@ export WidgetGallery = component
1462
1517
  dd
1463
1518
  code "$orientation"
1464
1519
 
1465
- # ================================================================
1520
+ # ========================================================================
1521
+ # BREADCRUMB
1522
+ # ========================================================================
1523
+ .section id: "breadcrumb"
1524
+ SectionHead tag: "breadcrumb", name: "Breadcrumb", lines: 25
1525
+ .section-desc "Navigation trail with separator and current page."
1526
+ .demo-row
1527
+ .demo-label "Basic breadcrumb"
1528
+ Breadcrumb
1529
+ a $item: true, href: "#breadcrumb", "Home"
1530
+ a $item: true, href: "#breadcrumb", "Products"
1531
+ a $item: true, href: "#breadcrumb", "Widgets"
1532
+ span $item: true, "Widget Pro"
1533
+ .api
1534
+ dl
1535
+ dt "Props"
1536
+ dd
1537
+ code "@separator"
1538
+ code "@label"
1539
+ dt "Data"
1540
+ dd
1541
+ code "$current"
1542
+
1543
+ # ========================================================================
1466
1544
  # DIALOG
1467
- # ================================================================
1545
+ # ========================================================================
1468
1546
  .section id: "dialog"
1469
- .section-title "Dialog"
1470
- span.badge @click: (=> @_viewSource('dialog')), "107 lines "
1471
- .section-nav
1472
- a @click: (=> @_navSection('dialog', 'prev')), "‹"
1473
- a @click: (=> @_navSection('dialog', 'home')), "⌂"
1474
- a @click: (=> @_navSection('dialog', 'next')), "›"
1475
- .section-desc "Modal with focus trap, scroll lock, escape/click-outside dismiss."
1547
+ SectionHead tag: "dialog", name: "Dialog", lines: 83
1548
+ .section-desc "Modal with focus trap and scroll lock. Press Escape or click outside to dismiss."
1476
1549
  .demo-row
1477
1550
  .demo-label "Click to open"
1478
1551
  button.demo-btn @click: (=> showDialog = true)
@@ -1505,16 +1578,47 @@ export WidgetGallery = component
1505
1578
  dd
1506
1579
  code "$open"
1507
1580
 
1508
- # ================================================================
1581
+ # ========================================================================
1582
+ # ALERT DIALOG
1583
+ # ========================================================================
1584
+ .section id: "alert-dialog"
1585
+ SectionHead tag: "alert-dialog", name: "AlertDialog", lines: 74
1586
+ .section-desc "Non-dismissable modal — Escape and click-outside are blocked. User must choose an action."
1587
+ .demo-row
1588
+ .demo-label "Requires explicit action"
1589
+ button.demo-btn @click: (=> showAlertDialog = true)
1590
+ "Delete Account"
1591
+ .status "open: #{showAlertDialog}"
1592
+ AlertDialog open <=> showAlertDialog
1593
+ .dialog-panel
1594
+ h2 style: "font-size:18px;font-weight:600;margin-bottom:8px", "Delete Account?"
1595
+ p.dialog-desc "This action is permanent and cannot be undone."
1596
+ . style: "display:flex;gap:8px;justify-content:flex-end"
1597
+ button.demo-btn @click: (=> showAlertDialog = false)
1598
+ "Cancel"
1599
+ button style: "padding:6px 16px;border:none;border-radius:6px;background:#dc2626;color:white;font-weight:600", @click: (=> showAlertDialog = false)
1600
+ "Delete"
1601
+ .api
1602
+ dl
1603
+ dt "Props"
1604
+ dd
1605
+ code "@open"
1606
+ code "@initialFocus"
1607
+ dt "Events"
1608
+ dd
1609
+ code "close"
1610
+ dt "Keyboard"
1611
+ dd
1612
+ kbd "Tab"
1613
+ dt "Data"
1614
+ dd
1615
+ code "$open"
1616
+
1617
+ # ========================================================================
1509
1618
  # DRAWER
1510
- # ================================================================
1619
+ # ========================================================================
1511
1620
  .section id: "drawer"
1512
- .section-title "Drawer"
1513
- span.badge @click: (=> @_viewSource('drawer')), "79 lines ❐"
1514
- .section-nav
1515
- a @click: (=> @_navSection('drawer', 'prev')), "‹"
1516
- a @click: (=> @_navSection('drawer', 'home')), "⌂"
1517
- a @click: (=> @_navSection('drawer', 'next')), "›"
1621
+ SectionHead tag: "drawer", name: "Drawer", lines: 60
1518
1622
  .section-desc "Slide-out panel from edge of screen with focus trap and scroll lock."
1519
1623
  .demo-row
1520
1624
  .demo-label "Open from right"
@@ -1548,16 +1652,11 @@ export WidgetGallery = component
1548
1652
  code "$open"
1549
1653
  code "$side"
1550
1654
 
1551
- # ================================================================
1655
+ # ========================================================================
1552
1656
  # POPOVER
1553
- # ================================================================
1657
+ # ========================================================================
1554
1658
  .section id: "popover"
1555
- .section-title "Popover"
1556
- span.badge @click: (=> @_viewSource('popover')), "143 lines ❐"
1557
- .section-nav
1558
- a @click: (=> @_navSection('popover', 'prev')), "‹"
1559
- a @click: (=> @_navSection('popover', 'home')), "⌂"
1560
- a @click: (=> @_navSection('popover', 'next')), "›"
1659
+ SectionHead tag: "popover", name: "Popover", lines: 117
1561
1660
  .section-desc "Anchored floating content with flip/shift positioning."
1562
1661
  .demo-row
1563
1662
  .demo-label "Click to toggle"
@@ -1588,16 +1687,11 @@ export WidgetGallery = component
1588
1687
  code "$open"
1589
1688
  code "$placement"
1590
1689
 
1591
- # ================================================================
1690
+ # ========================================================================
1592
1691
  # TOOLTIP
1593
- # ================================================================
1692
+ # ========================================================================
1594
1693
  .section id: "tooltip"
1595
- .section-title "Tooltip"
1596
- span.badge @click: (=> @_viewSource('tooltip')), "115 lines ❐"
1597
- .section-nav
1598
- a @click: (=> @_navSection('tooltip', 'prev')), "‹"
1599
- a @click: (=> @_navSection('tooltip', 'home')), "⌂"
1600
- a @click: (=> @_navSection('tooltip', 'next')), "›"
1694
+ SectionHead tag: "tooltip", name: "Tooltip", lines: 92
1601
1695
  .section-desc "Hover/focus tooltip with delay and positioning."
1602
1696
  .demo-row
1603
1697
  .demo-label "Hover the buttons"
@@ -1624,16 +1718,11 @@ export WidgetGallery = component
1624
1718
  code "$exiting"
1625
1719
  code "$placement"
1626
1720
 
1627
- # ================================================================
1721
+ # ========================================================================
1628
1722
  # PREVIEW CARD
1629
- # ================================================================
1723
+ # ========================================================================
1630
1724
  .section id: "preview-card"
1631
- .section-title "PreviewCard"
1632
- span.badge @click: (=> @_viewSource('preview-card')), "73 lines ❐"
1633
- .section-nav
1634
- a @click: (=> @_navSection('preview-card', 'prev')), "‹"
1635
- a @click: (=> @_navSection('preview-card', 'home')), "⌂"
1636
- a @click: (=> @_navSection('preview-card', 'next')), "›"
1725
+ SectionHead tag: "preview-card", name: "PreviewCard", lines: 56
1637
1726
  .section-desc "Hover or focus the link to see a preview card."
1638
1727
  .demo-row
1639
1728
  PreviewCard delay: 300
@@ -1653,16 +1742,11 @@ export WidgetGallery = component
1653
1742
  dd
1654
1743
  code "$open"
1655
1744
 
1656
- # ================================================================
1745
+ # ========================================================================
1657
1746
  # TOAST
1658
- # ================================================================
1747
+ # ========================================================================
1659
1748
  .section id: "toast"
1660
- .section-title "Toast"
1661
- span.badge @click: (=> @_viewSource('toast')), "88 lines ❐"
1662
- .section-nav
1663
- a @click: (=> @_navSection('toast', 'prev')), "‹"
1664
- a @click: (=> @_navSection('toast', 'home')), "⌂"
1665
- a @click: (=> @_navSection('toast', 'next')), "›"
1749
+ SectionHead tag: "toast", name: "Toast", lines: 56
1666
1750
  .section-desc "Auto-dismiss notification with ARIA live region."
1667
1751
  .demo-row
1668
1752
  .demo-label "Click to show toasts"
@@ -1688,16 +1772,11 @@ export WidgetGallery = component
1688
1772
  code "$type"
1689
1773
  code "$leaving"
1690
1774
 
1691
- # ================================================================
1775
+ # ========================================================================
1692
1776
  # BUTTON
1693
- # ================================================================
1777
+ # ========================================================================
1694
1778
  .section id: "button"
1695
- .section-title "Button"
1696
- span.badge @click: (=> @_viewSource('button')), "23 lines ❐"
1697
- .section-nav
1698
- a @click: (=> @_navSection('button', 'prev')), "‹"
1699
- a @click: (=> @_navSection('button', 'home')), "⌂"
1700
- a @click: (=> @_navSection('button', 'next')), "›"
1779
+ SectionHead tag: "button", name: "Button", lines: 10
1701
1780
  .section-desc "Headless button with disabled-but-focusable pattern."
1702
1781
  .demo-row.btn-demo
1703
1782
  .demo-label "Normal, primary, and disabled"
@@ -1720,16 +1799,55 @@ export WidgetGallery = component
1720
1799
  dd
1721
1800
  code "$disabled"
1722
1801
 
1723
- # ================================================================
1802
+ # ========================================================================
1803
+ # BADGE
1804
+ # ========================================================================
1805
+ .section id: "badge"
1806
+ SectionHead tag: "badge", name: "Badge", lines: 5
1807
+ .section-desc "Inline label with variant styles."
1808
+ .demo-row
1809
+ .demo-label "Variants"
1810
+ . style: "display:flex;gap:8px;align-items:center"
1811
+ Badge "Solid"
1812
+ Badge variant: "outline", "Outline"
1813
+ Badge variant: "subtle", "Subtle"
1814
+ .api
1815
+ dl
1816
+ dt "Props"
1817
+ dd
1818
+ code "@variant"
1819
+ dt "Data"
1820
+ dd
1821
+ code "$variant"
1822
+
1823
+ # ========================================================================
1824
+ # CARD
1825
+ # ========================================================================
1826
+ .section id: "card"
1827
+ SectionHead tag: "card", name: "Card", lines: 6
1828
+ .section-desc "Structured container with header, content, and footer."
1829
+ .demo-row.card-demo
1830
+ Card
1831
+ div $header: true
1832
+ h3 "Card Title"
1833
+ div $content: true
1834
+ "This is the card body. Cards can contain any content."
1835
+ div $footer: true
1836
+ button.demo-btn @click: (=> toasts = [...toasts, { message: 'Card action clicked!', type: 'success' }]), "Action"
1837
+ .api
1838
+ dl
1839
+ dt "Props"
1840
+ dd
1841
+ code "@interactive"
1842
+ dt "Data"
1843
+ dd
1844
+ code "$interactive"
1845
+
1846
+ # ========================================================================
1724
1847
  # SEPARATOR
1725
- # ================================================================
1848
+ # ========================================================================
1726
1849
  .section id: "separator"
1727
- .section-title "Separator"
1728
- span.badge @click: (=> @_viewSource('separator')), "17 lines ❐"
1729
- .section-nav
1730
- a @click: (=> @_navSection('separator', 'prev')), "‹"
1731
- a @click: (=> @_navSection('separator', 'home')), "⌂"
1732
- a @click: (=> @_navSection('separator', 'next')), "›"
1850
+ SectionHead tag: "separator", name: "Separator", lines: 7
1733
1851
  .section-desc "Decorative or semantic divider between sections."
1734
1852
  .demo-row
1735
1853
  .demo-label "Horizontal (default)"
@@ -1753,16 +1871,11 @@ export WidgetGallery = component
1753
1871
  dd
1754
1872
  code "$orientation"
1755
1873
 
1756
- # ================================================================
1874
+ # ========================================================================
1757
1875
  # PROGRESS
1758
- # ================================================================
1876
+ # ========================================================================
1759
1877
  .section id: "progress"
1760
- .section-title "Progress"
1761
- span.badge @click: (=> @_viewSource('progress')), "25 lines ❐"
1762
- .section-nav
1763
- a @click: (=> @_navSection('progress', 'prev')), "‹"
1764
- a @click: (=> @_navSection('progress', 'home')), "⌂"
1765
- a @click: (=> @_navSection('progress', 'next')), "›"
1878
+ SectionHead tag: "progress", name: "Progress", lines: 14
1766
1879
  .section-desc "Progress bar with value exposed as CSS custom property."
1767
1880
  .demo-row
1768
1881
  .demo-label "65% complete"
@@ -1780,16 +1893,11 @@ export WidgetGallery = component
1780
1893
  dd
1781
1894
  code "$complete"
1782
1895
 
1783
- # ================================================================
1896
+ # ========================================================================
1784
1897
  # METER
1785
- # ================================================================
1898
+ # ========================================================================
1786
1899
  .section id: "meter"
1787
- .section-title "Meter"
1788
- span.badge @click: (=> @_viewSource('meter')), "36 lines ❐"
1789
- .section-nav
1790
- a @click: (=> @_navSection('meter', 'prev')), "‹"
1791
- a @click: (=> @_navSection('meter', 'home')), "⌂"
1792
- a @click: (=> @_navSection('meter', 'next')), "›"
1900
+ SectionHead tag: "meter", name: "Meter", lines: 23
1793
1901
  .section-desc "Gauge for known-range measurements with low/high/optimum thresholds."
1794
1902
  .demo-row
1795
1903
  .demo-label "Score: 72/100"
@@ -1809,16 +1917,55 @@ export WidgetGallery = component
1809
1917
  dd
1810
1918
  code "$level"
1811
1919
 
1812
- # ================================================================
1920
+ # ========================================================================
1921
+ # SPINNER
1922
+ # ========================================================================
1923
+ .section id: "spinner"
1924
+ SectionHead tag: "spinner", name: "Spinner", lines: 6
1925
+ .section-desc "Loading indicator with accessible status."
1926
+ .demo-row.spinner-demo
1927
+ .demo-label "Default"
1928
+ . style: "display:flex;gap:16px;align-items:center"
1929
+ Spinner
1930
+ Spinner size: "32px"
1931
+ Spinner label: "Saving...", size: "20px"
1932
+ .api
1933
+ dl
1934
+ dt "Props"
1935
+ dd
1936
+ code "@label"
1937
+ code "@size"
1938
+
1939
+ # ========================================================================
1940
+ # SKELETON
1941
+ # ========================================================================
1942
+ .section id: "skeleton"
1943
+ SectionHead tag: "skeleton", name: "Skeleton", lines: 10
1944
+ .section-desc "Loading placeholder with shimmer animation."
1945
+ .demo-row
1946
+ .demo-label "Shapes"
1947
+ . style: "display:flex;gap:12px;align-items:center"
1948
+ Skeleton circle: true, width: "40px", height: "40px"
1949
+ . style: "display:flex;flex-direction:column;gap:6px"
1950
+ Skeleton width: "200px", height: "14px"
1951
+ Skeleton width: "150px", height: "14px"
1952
+ .api
1953
+ dl
1954
+ dt "Props"
1955
+ dd
1956
+ code "@width"
1957
+ code "@height"
1958
+ code "@circle"
1959
+ code "@label"
1960
+ dt "Data"
1961
+ dd
1962
+ code "$circle"
1963
+
1964
+ # ========================================================================
1813
1965
  # AVATAR
1814
- # ================================================================
1966
+ # ========================================================================
1815
1967
  .section id: "avatar"
1816
- .section-title "Avatar"
1817
- span.badge @click: (=> @_viewSource('avatar')), "37 lines ❐"
1818
- .section-nav
1819
- a @click: (=> @_navSection('avatar', 'prev')), "‹"
1820
- a @click: (=> @_navSection('avatar', 'home')), "⌂"
1821
- a @click: (=> @_navSection('avatar', 'next')), "›"
1968
+ SectionHead tag: "avatar", name: "Avatar", lines: 23
1822
1969
  .section-desc "Image with fallback to initials or placeholder."
1823
1970
  .demo-row
1824
1971
  .demo-label "With image"
@@ -1842,16 +1989,31 @@ export WidgetGallery = component
1842
1989
  code "$initials"
1843
1990
  code "$placeholder"
1844
1991
 
1845
- # ================================================================
1992
+ # ========================================================================
1993
+ # LABEL
1994
+ # ========================================================================
1995
+ .section id: "label"
1996
+ SectionHead tag: "label", name: "Label", lines: 6
1997
+ .section-desc "Standalone accessible form label."
1998
+ .demo-row
1999
+ .demo-label "With required indicator"
2000
+ Label required: true
2001
+ "Email address"
2002
+ .api
2003
+ dl
2004
+ dt "Props"
2005
+ dd
2006
+ code "@for"
2007
+ code "@required"
2008
+ dt "Data"
2009
+ dd
2010
+ code "$required"
2011
+
2012
+ # ========================================================================
1846
2013
  # SCROLL AREA
1847
- # ================================================================
2014
+ # ========================================================================
1848
2015
  .section id: "scroll-area"
1849
- .section-title "ScrollArea"
1850
- span.badge @click: (=> @_viewSource('scroll-area')), "145 lines ❐"
1851
- .section-nav
1852
- a @click: (=> @_navSection('scroll-area', 'prev')), "‹"
1853
- a @click: (=> @_navSection('scroll-area', 'home')), "⌂"
1854
- a @click: (=> @_navSection('scroll-area', 'next')), "›"
2016
+ SectionHead tag: "scroll-area", name: "ScrollArea", lines: 115
1855
2017
  .section-desc "Custom scrollbar with draggable thumb and auto-hide."
1856
2018
  .demo-row
1857
2019
  .demo-label "Scrollable content"
@@ -1888,16 +2050,11 @@ export WidgetGallery = component
1888
2050
  code "$scrollbar"
1889
2051
  code "$thumb"
1890
2052
 
1891
- # ================================================================
2053
+ # ========================================================================
1892
2054
  # FIELD + FIELDSET
1893
- # ================================================================
2055
+ # ========================================================================
1894
2056
  .section id: "field"
1895
- .section-title "Field + Fieldset"
1896
- span.badge @click: (=> @_viewSource('field')), "75 lines ❐"
1897
- .section-nav
1898
- a @click: (=> @_navSection('field', 'prev')), "‹"
1899
- a @click: (=> @_navSection('field', 'home')), "⌂"
1900
- a @click: (=> @_navSection('field', 'next')), "›"
2057
+ SectionHead tag: "field", name: "Field + Fieldset", lines: 48
1901
2058
  .section-desc "Form field wrapper with label, description, and error."
1902
2059
  .demo-row
1903
2060
  Field label: "Email", description: "We'll never share your email.", required: true, error: fieldError
@@ -1934,16 +2091,11 @@ export WidgetGallery = component
1934
2091
  code "$error"
1935
2092
  code "$legend"
1936
2093
 
1937
- # ================================================================
2094
+ # ========================================================================
1938
2095
  # FORM
1939
- # ================================================================
2096
+ # ========================================================================
1940
2097
  .section id: "form"
1941
- .section-title "Form"
1942
- span.badge @click: (=> @_viewSource('form')), "39 lines ❐"
1943
- .section-nav
1944
- a @click: (=> @_navSection('form', 'prev')), "‹"
1945
- a @click: (=> @_navSection('form', 'home')), "⌂"
1946
- a @click: (=> @_navSection('form', 'next')), "›"
2098
+ SectionHead tag: "form", name: "Form", lines: 21
1947
2099
  .section-desc "Form wrapper with submit handling and validation state."
1948
2100
  .demo-row
1949
2101
  Form
@@ -1967,16 +2119,35 @@ export WidgetGallery = component
1967
2119
  code "$submitting"
1968
2120
  code "$submitted"
1969
2121
 
1970
- # ================================================================
2122
+ # ========================================================================
2123
+ # BUTTON GROUP
2124
+ # ========================================================================
2125
+ .section id: "button-group"
2126
+ SectionHead tag: "button-group", name: "ButtonGroup", lines: 11
2127
+ .section-desc "Groups related buttons with ARIA group semantics."
2128
+ .demo-row
2129
+ .demo-label "Horizontal"
2130
+ ButtonGroup label: "Actions"
2131
+ button "Cut"
2132
+ button "Copy"
2133
+ button "Paste"
2134
+ .api
2135
+ dl
2136
+ dt "Props"
2137
+ dd
2138
+ code "@orientation"
2139
+ code "@disabled"
2140
+ code "@label"
2141
+ dt "Data"
2142
+ dd
2143
+ code "$orientation"
2144
+ code "$disabled"
2145
+
2146
+ # ========================================================================
1971
2147
  # GRID
1972
- # ================================================================
2148
+ # ========================================================================
1973
2149
  .section id: "grid"
1974
- .section-title "Grid"
1975
- span.badge @click: (=> @_viewSource('grid')), "901 lines ❐"
1976
- .section-nav
1977
- a @click: (=> @_navSection('grid', 'prev')), "‹"
1978
- a @click: (=> @_navSection('grid', 'home')), "⌂"
1979
- a @click: (=> @_navSection('grid', 'next')), "›"
2150
+ SectionHead tag: "grid", name: "Grid", lines: 799
1980
2151
  .section-desc "Virtual-scrolling data grid. Click to select, arrows to navigate, double-click to edit."
1981
2152
  .demo-row
1982
2153
  . style: "display:flex;justify-content:space-between;align-items:baseline;margin-bottom:8px"
@@ -2022,16 +2193,11 @@ export WidgetGallery = component
2022
2193
  code "$selecting"
2023
2194
  code "$sorted"
2024
2195
 
2025
- # ================================================================
2196
+ # ========================================================================
2026
2197
  # ACCORDION
2027
- # ================================================================
2198
+ # ========================================================================
2028
2199
  .section id: "accordion"
2029
- .section-title "Accordion"
2030
- span.badge @click: (=> @_viewSource('accordion')), "113 lines ❐"
2031
- .section-nav
2032
- a @click: (=> @_navSection('accordion', 'prev')), "‹"
2033
- a @click: (=> @_navSection('accordion', 'home')), "⌂"
2034
- a @click: (=> @_navSection('accordion', 'next')), "›"
2200
+ SectionHead tag: "accordion", name: "Accordion", lines: 86
2035
2201
  .section-desc "Expand/collapse sections, single or multiple mode."
2036
2202
  .demo-row
2037
2203
  .demo-label "Single mode"
@@ -2074,17 +2240,187 @@ export WidgetGallery = component
2074
2240
  code "$open"
2075
2241
  code "$disabled"
2076
2242
 
2077
- # ================================================================
2243
+ # ========================================================================
2244
+ # TABLE
2245
+ # ========================================================================
2246
+ .section id: "table"
2247
+ SectionHead tag: "table", name: "Table", lines: 9
2248
+ .section-desc "Semantic table wrapper. For data-heavy tables, use Grid."
2249
+ .demo-row.table-demo
2250
+ Table caption: "Team", striped: true
2251
+ thead
2252
+ tr
2253
+ th "Name"
2254
+ th "Role"
2255
+ th "City"
2256
+ tbody
2257
+ tr
2258
+ td "Alice"
2259
+ td "Engineer"
2260
+ td "Seattle"
2261
+ tr
2262
+ td "Bob"
2263
+ td "Designer"
2264
+ td "Portland"
2265
+ tr
2266
+ td "Carol"
2267
+ td "Manager"
2268
+ td "Denver"
2269
+ .api
2270
+ dl
2271
+ dt "Props"
2272
+ dd
2273
+ code "@caption"
2274
+ code "@striped"
2275
+ dt "Data"
2276
+ dd
2277
+ code "$striped"
2278
+
2279
+ # ========================================================================
2280
+ # COLLAPSIBLE
2281
+ # ========================================================================
2282
+ .section id: "collapsible"
2283
+ SectionHead tag: "collapsible", name: "Collapsible", lines: 33
2284
+ .section-desc "Single open/close section. Simpler than Accordion."
2285
+ .demo-row.collapsible-demo
2286
+ Collapsible open <=> collapsibleOpen
2287
+ button $trigger: true
2288
+ span style: "font-size:10px;margin-right:6px"
2289
+ if collapsibleOpen then "▼" else "▶"
2290
+ "Show details"
2291
+ div $content: true
2292
+ p "These are the collapsible details. Click the trigger or press Enter/Space to toggle."
2293
+ .status "open: #{collapsibleOpen}"
2294
+ .api
2295
+ dl
2296
+ dt "Props"
2297
+ dd
2298
+ code "@open"
2299
+ code "@disabled"
2300
+ dt "Events"
2301
+ dd
2302
+ code "change"
2303
+ dt "Keyboard"
2304
+ dd
2305
+ kbd "Enter"
2306
+ kbd "Space"
2307
+ dt "Data"
2308
+ dd
2309
+ code "$open"
2310
+ code "$disabled"
2311
+
2312
+ # ========================================================================
2313
+ # PAGINATION
2314
+ # ========================================================================
2315
+ .section id: "pagination"
2316
+ SectionHead tag: "pagination", name: "Pagination", lines: 96
2317
+ .section-desc "Page navigation with prev/next buttons and ellipsis gaps."
2318
+ .demo-row
2319
+ .demo-label "10 pages"
2320
+ Pagination page <=> currentPage, total: 100, perPage: 10
2321
+ .status "page: #{currentPage}"
2322
+ .api
2323
+ dl
2324
+ dt "Props"
2325
+ dd
2326
+ code "@page"
2327
+ code "@total"
2328
+ code "@perPage"
2329
+ code "@siblingCount"
2330
+ dt "Events"
2331
+ dd
2332
+ code "change"
2333
+ dt "Keyboard"
2334
+ dd
2335
+ kbd "← →"
2336
+ kbd "Home"
2337
+ kbd "End"
2338
+ dt "Data"
2339
+ dd
2340
+ code "$active"
2341
+ code "$disabled"
2342
+ code "$ellipsis"
2343
+
2344
+ # ========================================================================
2345
+ # CAROUSEL
2346
+ # ========================================================================
2347
+ .section id: "carousel"
2348
+ SectionHead tag: "carousel", name: "Carousel", lines: 78
2349
+ .section-desc "Slide carousel with loop and keyboard navigation."
2350
+ .demo-row
2351
+ Carousel loop: true
2352
+ div $slide: true
2353
+ "🍎 Slide 1 — Apples"
2354
+ div $slide: true
2355
+ "🍌 Slide 2 — Bananas"
2356
+ div $slide: true
2357
+ "🍒 Slide 3 — Cherries"
2358
+ .api
2359
+ dl
2360
+ dt "Props"
2361
+ dd
2362
+ code "@orientation"
2363
+ code "@loop"
2364
+ code "@autoplay"
2365
+ code "@interval"
2366
+ code "@label"
2367
+ dt "Events"
2368
+ dd
2369
+ code "change"
2370
+ dt "Keyboard"
2371
+ dd
2372
+ kbd "← →"
2373
+ kbd "↕"
2374
+ kbd "Home"
2375
+ kbd "End"
2376
+ dt "Data"
2377
+ dd
2378
+ code "$orientation"
2379
+ code "$active"
2380
+ code "$prev"
2381
+ code "$next"
2382
+
2383
+ # ========================================================================
2384
+ # RESIZABLE
2385
+ # ========================================================================
2386
+ .section id: "resizable"
2387
+ SectionHead tag: "resizable", name: "Resizable", lines: 89
2388
+ .section-desc "Draggable resize handles between panels."
2389
+ .demo-row.resizable-demo
2390
+ Resizable
2391
+ div $panel: true, "Left"
2392
+ div $handle: true
2393
+ div $panel: true, "Right"
2394
+ .api
2395
+ dl
2396
+ dt "Props"
2397
+ dd
2398
+ code "@orientation"
2399
+ code "@minSize"
2400
+ code "@maxSize"
2401
+ dt "Events"
2402
+ dd
2403
+ code "resize"
2404
+ dt "Keyboard"
2405
+ dd
2406
+ kbd "← →"
2407
+ kbd "↕"
2408
+ dt "Data"
2409
+ dd
2410
+ code "$orientation"
2411
+ code "$dragging"
2412
+
2413
+ # ========================================================================
2078
2414
  # SOURCE CODE VIEWER
2079
- # ================================================================
2415
+ # ========================================================================
2080
2416
  if sourceCode
2081
- .source-overlay @click: @_closeSource
2417
+ .source-overlay @click: (=> @_closeSource())
2082
2418
  .source-modal @click: (e => e.stopPropagation())
2083
2419
  .source-header
2084
2420
  .source-title
2085
2421
  span.source-name sourceName
2086
2422
  span.source-badge "#{sourceLines} lines"
2087
- button.source-close @click: @_closeSource
2423
+ button.source-close @click: (=> @_closeSource())
2088
2424
  "×"
2089
2425
  .source-body
2090
2426
  pre.source-pre