rip-lang 3.13.119 → 3.13.121

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 (71) hide show
  1. package/README.md +11 -2
  2. package/docs/RIP-LANG.md +4 -0
  3. package/docs/dist/rip.js +257 -27
  4. package/docs/dist/rip.min.js +183 -183
  5. package/docs/dist/rip.min.js.br +0 -0
  6. package/docs/ui/accordion.rip +103 -0
  7. package/docs/ui/alert-dialog.rip +53 -0
  8. package/docs/ui/autocomplete.rip +115 -0
  9. package/docs/ui/avatar.rip +37 -0
  10. package/docs/ui/badge.rip +15 -0
  11. package/docs/ui/breadcrumb.rip +47 -0
  12. package/docs/ui/button-group.rip +26 -0
  13. package/docs/ui/button.rip +23 -0
  14. package/docs/ui/card.rip +25 -0
  15. package/docs/ui/carousel.rip +110 -0
  16. package/docs/ui/checkbox-group.rip +61 -0
  17. package/docs/ui/checkbox.rip +33 -0
  18. package/docs/ui/collapsible.rip +50 -0
  19. package/docs/ui/combobox.rip +130 -0
  20. package/docs/ui/context-menu.rip +88 -0
  21. package/docs/ui/date-picker.rip +206 -0
  22. package/docs/ui/dialog.rip +60 -0
  23. package/docs/ui/drawer.rip +58 -0
  24. package/docs/ui/editable-value.rip +82 -0
  25. package/docs/ui/field.rip +53 -0
  26. package/docs/ui/fieldset.rip +22 -0
  27. package/docs/ui/form.rip +39 -0
  28. package/docs/ui/grid.rip +901 -0
  29. package/docs/ui/hljs-rip.js +209 -0
  30. package/docs/ui/index.css +1797 -0
  31. package/docs/ui/index.html +2385 -0
  32. package/docs/ui/input-group.rip +28 -0
  33. package/docs/ui/input.rip +36 -0
  34. package/docs/ui/label.rip +16 -0
  35. package/docs/ui/menu.rip +134 -0
  36. package/docs/ui/menubar.rip +151 -0
  37. package/docs/ui/meter.rip +36 -0
  38. package/docs/ui/multi-select.rip +203 -0
  39. package/docs/ui/native-select.rip +33 -0
  40. package/docs/ui/nav-menu.rip +126 -0
  41. package/docs/ui/number-field.rip +162 -0
  42. package/docs/ui/otp-field.rip +89 -0
  43. package/docs/ui/pagination.rip +123 -0
  44. package/docs/ui/popover.rip +93 -0
  45. package/docs/ui/preview-card.rip +75 -0
  46. package/docs/ui/progress.rip +25 -0
  47. package/docs/ui/radio-group.rip +57 -0
  48. package/docs/ui/resizable.rip +123 -0
  49. package/docs/ui/scroll-area.rip +145 -0
  50. package/docs/ui/select.rip +151 -0
  51. package/docs/ui/separator.rip +17 -0
  52. package/docs/ui/skeleton.rip +22 -0
  53. package/docs/ui/slider.rip +165 -0
  54. package/docs/ui/spinner.rip +17 -0
  55. package/docs/ui/table.rip +27 -0
  56. package/docs/ui/tabs.rip +113 -0
  57. package/docs/ui/textarea.rip +48 -0
  58. package/docs/ui/toast.rip +87 -0
  59. package/docs/ui/toggle-group.rip +71 -0
  60. package/docs/ui/toggle.rip +24 -0
  61. package/docs/ui/toolbar.rip +38 -0
  62. package/docs/ui/tooltip.rip +85 -0
  63. package/package.json +1 -1
  64. package/src/compiler.js +24 -12
  65. package/src/components.js +43 -6
  66. package/src/grammar/grammar.rip +2 -2
  67. package/src/lexer.js +26 -0
  68. package/src/parser.js +2 -2
  69. package/src/sourcemap-utils.js +91 -0
  70. package/src/typecheck.js +33 -8
  71. package/src/ui.rip +118 -2
@@ -0,0 +1,2385 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Rip UI</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
10
+ <link rel="stylesheet" href="index.css">
11
+ <style>body { opacity: 0; } body.ready { opacity: 1; transition: opacity 200ms ease-in; }</style>
12
+ <script defer src="../dist/rip.min.js" data-src="bundle" data-mount="WidgetGallery" data-reload>
13
+ </script>
14
+ </head>
15
+ <body>
16
+
17
+ <script type="text/rip">
18
+ do ->
19
+ orig = HTMLElement::focus
20
+ HTMLElement::focus = (o) -> orig.call this, { preventScroll: true, ...o }
21
+
22
+ export SectionHead = component
23
+ accept _gallery
24
+ @tag := ''
25
+ @name := ''
26
+ @lines := 0
27
+
28
+ render
29
+ .section-title
30
+ = @name
31
+ if @lines > 0
32
+ span.badge @click: (=> _gallery._viewSource(@tag)), "#{@lines} lines ❐"
33
+ else
34
+ span.badge "pattern"
35
+ .section-nav
36
+ a @click: (=> _gallery._navSection(@tag, 'prev')), "‹"
37
+ a @click: (=> _gallery._navSection(@tag, 'home')), "⌂"
38
+ a @click: (=> _gallery._navSection(@tag, 'next')), "›"
39
+
40
+ export WidgetGallery = component
41
+ offer _gallery := this
42
+
43
+ # ---- State ----
44
+ check1 := false
45
+ check2 := true
46
+ switch1 := false
47
+ toasts := []
48
+ showDialog := false
49
+ fruit := null
50
+ role := null
51
+ searchQuery := ''
52
+ allFruits := ['Apple', 'Apricot', 'Avocado', 'Banana', 'Blueberry', 'Cherry', 'Cranberry', 'Date', 'Fig', 'Grape', 'Kiwi', 'Lemon', 'Lime', 'Mango', 'Orange', 'Papaya', 'Peach', 'Pear', 'Plum', 'Raspberry', 'Strawberry']
53
+ filteredFruits := allFruits
54
+ currentTab := 'overview'
55
+ isBold := false
56
+ isItalic := false
57
+ userName := ''
58
+ progressVal := 0.65
59
+ meterVal := 72
60
+ sliderVal := 40
61
+ rangeVal := [20, 70]
62
+ showDrawer := false
63
+ citySearch := ''
64
+ cities := ['Albuquerque', 'Anchorage', 'Arlington', 'Atlanta', 'Aurora', 'Austin', 'Anaheim', 'Asheville', 'Baltimore', 'Baton Rouge', 'Birmingham', 'Boise', 'Boston', 'Buffalo', 'Bakersfield', 'Bridgeport', 'Charlotte', 'Chicago', 'Dallas', 'Denver', 'Detroit', 'El Paso', 'Houston', 'Miami', 'New York', 'Phoenix', 'Portland', 'San Diego', 'San Francisco', 'Seattle']
65
+ quantity := 1
66
+ price := 9.99
67
+ alignment := 'left'
68
+ fontSize := 'md'
69
+ toppings := ['Cheese']
70
+ cbToppings := ['Cheese']
71
+ fieldEmail := ''
72
+ fieldError := ''
73
+ otpCode := ''
74
+ selectedColors := ['Red']
75
+ editName := 'John Doe'
76
+ pickedDate := null
77
+ dateRange := null
78
+ darkMode := false
79
+ showAlertDialog := false
80
+ textareaVal := ''
81
+ nativeSelectVal := ''
82
+ collapsibleOpen := false
83
+ currentPage := 1
84
+ sourceCode := null
85
+ sourceName := ''
86
+ sourceLines := 0
87
+ tocActive := null
88
+ tocFilter := ''
89
+ tocGroups := [
90
+ { label: 'Selection', ids: ['select', 'combobox', 'multi-select', 'autocomplete'] }
91
+ { label: 'Toggle', ids: ['checkbox', 'toggle', 'toggle-group', 'radio-group', 'checkbox-group'] }
92
+ { label: 'Input', ids: ['input', 'textarea', 'native-select', 'number-field', 'slider', 'otp-field', 'date-picker', 'editable-value', 'input-group'] }
93
+ { label: 'Navigation', ids: ['tabs', 'menu', 'context-menu', 'menubar', 'nav-menu', 'toolbar', 'breadcrumb'] }
94
+ { label: 'Overlay', ids: ['dialog', 'alert-dialog', 'drawer', 'popover', 'tooltip', 'preview-card', 'toast'] }
95
+ { label: 'Display', ids: ['button', 'badge', 'card', 'separator', 'progress', 'meter', 'spinner', 'skeleton', 'avatar', 'label', 'scroll-area'] }
96
+ { label: 'Form', ids: ['field', 'fieldset', 'form', 'button-group'] }
97
+ { label: 'Data', ids: ['grid', 'accordion', 'table', 'collapsible'] }
98
+ { label: 'Interactive', ids: ['pagination', 'carousel', 'resizable'] }
99
+ ]
100
+ _nameFor: (id) -> (tocData.find((c) -> c.id is id))?.name or id
101
+ _viewSource: (id) ->
102
+ entry = tocData.find((c) -> c.id is id)
103
+ return unless entry
104
+ sourceName = entry.name
105
+ sourceLines = entry.lines
106
+ resp = fetch! "components/#{id}.rip"
107
+ sourceCode = resp.text!
108
+ _closeSource: -> sourceCode = null
109
+ _stopProp: (e) -> e.stopPropagation()
110
+ _ensureHljs: (cb) ->
111
+ if window.hljs then return cb()
112
+ link = document.createElement('link')
113
+ link.rel = 'stylesheet'
114
+ link.href = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/github.min.css'
115
+ document.head.appendChild(link)
116
+ script = document.createElement('script')
117
+ script.src = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js'
118
+ script.onload = =>
119
+ fetch('hljs-rip.js').then((r) -> r.text()).then (src) =>
120
+ fn = new Function('return ' + src.replace(/[\s\S]*export default /, ''))()
121
+ hljs.registerLanguage 'rip', fn
122
+ cb()
123
+ document.head.appendChild(script)
124
+ _onFilter: (e) -> tocFilter = e.target.value
125
+ _getVisibleLinks: -> Array.from(document.querySelectorAll('.toc-nav .toc-group a:not([data-hidden])') or [])
126
+
127
+ _onFilterKey: (e) ->
128
+ if e.key is '/'
129
+ e.preventDefault()
130
+ @_navSection('', 'home')
131
+ tocFilter = ''
132
+ e.target.value = ''
133
+ document.addEventListener 'keyup', (=> e.target.focus()), { once: true }
134
+ return
135
+ if e.key is 'Escape'
136
+ tocFilter = ''
137
+ e.target.value = ''
138
+ else if e.key is 'Enter'
139
+ links = @_getVisibleLinks()
140
+ if links.length is 1
141
+ e.preventDefault()
142
+ tocActive = links[0].getAttribute('href')?.slice(1)
143
+ @_fadeTo(document.getElementById(tocActive))
144
+ tocFilter = ''
145
+ e.target.value = ''
146
+ else if e.key is 'Tab' and not e.shiftKey
147
+ links = @_getVisibleLinks()
148
+ if links.length
149
+ e.preventDefault()
150
+ tocActive = links[0].getAttribute('href')?.slice(1)
151
+ links[0].focus()
152
+
153
+ _getTocGrid: ->
154
+ groups = Array.from(document.querySelectorAll('.toc-nav .toc-group') or [])
155
+ grid = []
156
+ for grp in groups
157
+ col = Array.from(grp.querySelectorAll('a:not([data-hidden])') or [])
158
+ grid.push(col) if col.length
159
+ grid
160
+
161
+ _focusTocLink: (link) ->
162
+ tocActive = link.getAttribute('href')?.slice(1)
163
+ link.focus()
164
+
165
+ _onTocKeydown: (e) ->
166
+ grid = @_getTocGrid()
167
+ return unless grid.length
168
+ focused = document.activeElement
169
+ col = -1
170
+ row = -1
171
+ for c, ci in grid
172
+ ri = c.indexOf(focused)
173
+ if ri >= 0
174
+ col = ci
175
+ row = ri
176
+ break
177
+ return if col < 0
178
+ switch e.key
179
+ when 'ArrowDown'
180
+ e.preventDefault()
181
+ if row < grid[col].length - 1
182
+ @_focusTocLink(grid[col][row + 1])
183
+ else if col < grid.length - 1
184
+ @_focusTocLink(grid[col + 1][0])
185
+ when 'ArrowUp'
186
+ e.preventDefault()
187
+ if row > 0
188
+ @_focusTocLink(grid[col][row - 1])
189
+ else if col > 0
190
+ prev = grid[col - 1]
191
+ @_focusTocLink(prev[prev.length - 1])
192
+ else
193
+ @_root?.querySelector('.toc-search')?.focus()
194
+ when 'ArrowRight'
195
+ e.preventDefault()
196
+ if col < grid.length - 1
197
+ target = grid[col + 1]
198
+ @_focusTocLink(target[Math.min(row, target.length - 1)])
199
+ when 'ArrowLeft'
200
+ e.preventDefault()
201
+ if col > 0
202
+ target = grid[col - 1]
203
+ @_focusTocLink(target[Math.min(row, target.length - 1)])
204
+ when 'Home'
205
+ e.preventDefault()
206
+ @_focusTocLink(grid[0][0])
207
+ when 'End'
208
+ e.preventDefault()
209
+ last = grid[grid.length - 1]
210
+ @_focusTocLink(last[last.length - 1])
211
+ when 'Enter', ' '
212
+ e.preventDefault()
213
+ @_fadeTo(document.getElementById(tocActive))
214
+ when 'Escape'
215
+ @_root?.querySelector('.toc-search')?.focus()
216
+
217
+ ~>
218
+ q = tocFilter.toLowerCase()
219
+ visible = []
220
+ for el in Array.from(document.querySelectorAll('.toc-nav a') or [])
221
+ if q and not el.textContent.toLowerCase().includes(q)
222
+ el.setAttribute('data-hidden', '')
223
+ else
224
+ el.removeAttribute('data-hidden')
225
+ visible.push(el) if q
226
+ if visible.length is 1
227
+ tocActive = visible[0].getAttribute('href')?.slice(1)
228
+ tocData := [
229
+ { id: 'select', name: 'Select', lines: 135, 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'] }
230
+ { id: 'combobox', name: 'Combobox', lines: 118, 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'] }
231
+ { id: 'multi-select', name: 'MultiSelect', lines: 146, 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'] }
232
+ { id: 'autocomplete', name: 'Autocomplete', lines: 113, desc: 'Suggestion input — type to filter, select to fill.', props: ['@value', '@items', '@placeholder', '@disabled'], events: ['select'], keys: ['↕', 'Enter', 'Esc', 'Tab'], attrs: ['$open', '$disabled', '$clear'] }
233
+ { 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'] }
234
+ { id: 'toggle', name: 'Toggle', lines: 14, desc: 'Two-state toggle button with pressed state.', props: ['@pressed', '@disabled'], events: ['change'], keys: [], attrs: ['$pressed', '$disabled'] }
235
+ { id: 'toggle-group', name: 'ToggleGroup', lines: 55, desc: 'Single or multi-select toggle buttons.', props: ['@value', '@disabled', '@multiple', '@orientation'], events: ['change'], keys: ['← →', '↕', 'Home', 'End'], attrs: ['$orientation', '$disabled', '$pressed', '$value'] }
236
+ { id: 'radio-group', name: 'RadioGroup', lines: 42, desc: 'Exactly one option selected with arrow key nav.', props: ['@value', '@disabled', '@orientation', '@name'], events: ['change'], keys: ['← →', '↕', 'Home', 'End'], attrs: ['$orientation', '$disabled', '$checked', '$value'] }
237
+ { id: 'checkbox-group', name: 'CheckboxGroup', lines: 45, desc: 'Multiple options checked independently.', props: ['@value', '@disabled', '@orientation', '@label'], events: ['change'], keys: ['← →', '↕'], attrs: ['$orientation', '$disabled', '$checked', '$value'] }
238
+ { id: 'input', name: 'Input', lines: 25, desc: 'Headless input tracking focus, touch, and validation.', props: ['@value', '@placeholder', '@type', '@disabled', '@required'], events: [], keys: [], attrs: ['$disabled', '$focused', '$touched'] }
239
+ { id: 'number-field', name: 'NumberField', lines: 151, 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'] }
240
+ { id: 'slider', name: 'Slider', lines: 152, 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'] }
241
+ { id: 'otp-field', name: 'OTPField', lines: 79, 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'] }
242
+ { id: 'date-picker', name: 'DatePicker', lines: 194, 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'] }
243
+ { id: 'editable-value', name: 'EditableValue', lines: 70, desc: 'Click-to-edit inline value with popover form.', props: ['@disabled'], events: ['save'], keys: ['Esc', 'Enter'], attrs: ['$editing', '$disabled', '$saving', '$edit-trigger'] }
244
+ { id: 'tabs', name: 'Tabs', lines: 92, 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'] }
245
+ { id: 'menu', name: 'Menu', lines: 124, 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'] }
246
+ { id: 'context-menu', name: 'ContextMenu', lines: 73, desc: 'Right-click context menu with keyboard navigation.', props: ['@disabled'], events: ['select'], keys: ['↕', 'Home', 'End', 'Enter', 'Space', 'Esc', 'Tab'], attrs: ['$open', '$highlighted', '$disabled', '$value'] }
247
+ { id: 'menubar', name: 'Menubar', lines: 137, desc: 'Horizontal menu bar with dropdown menus.', props: ['@disabled'], events: ['select'], keys: ['← →', '↕', 'Enter', 'Space', 'Esc', 'Tab'], attrs: ['$disabled', '$open', '$highlighted', '$value'] }
248
+ { id: 'nav-menu', name: 'NavMenu', lines: 114, desc: 'Site navigation with hover/click dropdown panels.', props: ['@orientation', '@hoverDelay', '@hoverCloseDelay'], events: [], keys: ['← →', '↓', 'Esc'], attrs: ['$orientation', '$open'] }
249
+ { id: 'toolbar', name: 'Toolbar', lines: 26, desc: 'Groups controls with roving tabindex keyboard nav.', props: ['@orientation', '@label'], events: [], keys: ['← →', '↕', 'Home', 'End'], attrs: ['$orientation'] }
250
+ { id: 'dialog', name: 'Dialog', lines: 94, desc: 'Modal with focus trap, scroll lock, escape dismiss.', props: ['@open', '@dismissable', '@initialFocus'], events: ['close'], keys: ['Esc', 'Tab'], attrs: ['$open'] }
251
+ { id: 'drawer', name: 'Drawer', lines: 68, desc: 'Slide-out panel with focus trap and scroll lock.', props: ['@open', '@side', '@dismissable'], events: ['close'], keys: ['Esc', 'Tab'], attrs: ['$open', '$side'] }
252
+ { id: 'popover', name: 'Popover', lines: 135, desc: 'Anchored floating content with flip/shift positioning.', props: ['@placement', '@offset', '@disabled', '@openOnHover', '@hoverDelay', '@hoverCloseDelay'], events: [], keys: ['Esc', 'Enter', 'Space', '↓'], attrs: ['$open', '$placement'] }
253
+ { id: 'tooltip', name: 'Tooltip', lines: 103, desc: 'Hover/focus tooltip with delay and positioning.', props: ['@text', '@placement', '@delay', '@offset', '@hoverable'], events: [], keys: [], attrs: ['$open', '$entering', '$exiting', '$placement'] }
254
+ { id: 'preview-card', name: 'PreviewCard', lines: 63, desc: 'Hover/focus preview card with delay.', props: ['@delay', '@closeDelay'], events: [], keys: [], attrs: ['$open'] }
255
+ { id: 'toast', name: 'Toast', lines: 66, desc: 'Auto-dismiss notification with ARIA live region.', props: ['@toasts', '@toast', '@placement'], events: ['dismiss'], keys: [], attrs: ['$placement', '$type', '$leaving'] }
256
+ { id: 'button', name: 'Button', lines: 12, desc: 'Accessible button with disabled-but-focusable pattern.', props: ['@disabled'], events: ['press'], keys: [], attrs: ['$disabled'] }
257
+ { id: 'separator', name: 'Separator', lines: 8, desc: 'Decorative or semantic divider between sections.', props: ['@orientation', '@decorative'], events: [], keys: [], attrs: ['$orientation'] }
258
+ { id: 'progress', name: 'Progress', lines: 16, desc: 'Progress bar with CSS custom property for value.', props: ['@value', '@max', '@label'], events: [], keys: [], attrs: ['$complete'] }
259
+ { id: 'meter', name: 'Meter', lines: 26, desc: 'Gauge for known-range measurements with thresholds.', props: ['@value', '@min', '@max', '@low', '@high', '@optimum', '@label'], events: [], keys: [], attrs: ['$level'] }
260
+ { id: 'avatar', name: 'Avatar', lines: 27, desc: 'Image with fallback to initials or placeholder.', props: ['@src', '@alt', '@fallback'], events: [], keys: [], attrs: ['$status', '$initials', '$placeholder'] }
261
+ { id: 'scroll-area', name: 'ScrollArea', lines: 133, desc: 'Custom scrollbar with draggable thumb and auto-hide.', props: ['@orientation'], events: [], keys: [], attrs: ['$orientation', '$hovering', '$scrolling', '$dragging', '$viewport', '$scrollbar', '$thumb'] }
262
+ { id: 'field', name: 'Field', lines: 44, desc: 'Form field wrapper with label, description, and error.', props: ['@label', '@description', '@error', '@disabled', '@required'], events: [], keys: [], attrs: ['$disabled', '$invalid', '$label', '$required', '$description', '$error'] }
263
+ { id: 'fieldset', name: 'Fieldset', lines: 11, desc: 'Grouped fields with legend and cascading disable.', props: ['@legend', '@disabled'], events: [], keys: [], attrs: ['$disabled', '$legend'] }
264
+ { id: 'form', name: 'Form', lines: 24, desc: 'Form wrapper with submit handling and validation state.', props: ['@disabled'], events: ['submit'], keys: [], attrs: ['$disabled', '$submitting', '$submitted'] }
265
+ { id: 'grid', name: 'Grid', lines: 857, 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'] }
266
+ { id: 'accordion', name: 'Accordion', lines: 87, desc: 'Expand/collapse sections, single or multiple mode.', props: ['@multiple'], events: ['change'], keys: ['Enter', 'Space', '↕', 'Home', 'End'], attrs: ['$item', '$trigger', '$content', '$open', '$disabled'] }
267
+ { id: 'badge', name: 'Badge', lines: 7, desc: 'Inline label with variant (solid/outline/subtle).', props: ['@variant'], events: [], keys: [], attrs: ['$variant'] }
268
+ { id: 'skeleton', name: 'Skeleton', lines: 11, desc: 'Loading placeholder with shimmer animation.', props: ['@width', '@height', '@circle', '@label'], events: [], keys: [], attrs: ['$circle'] }
269
+ { id: 'spinner', name: 'Spinner', lines: 8, desc: 'Loading indicator with accessible status.', props: ['@label', '@size'], events: [], keys: [], attrs: [] }
270
+ { id: 'card', name: 'Card', lines: 7, desc: 'Structured container with header/content/footer.', props: ['@interactive'], events: [], keys: [], attrs: ['$interactive'] }
271
+ { id: 'label', name: 'Label', lines: 8, desc: 'Standalone accessible form label.', props: ['@for', '@required'], events: [], keys: [], attrs: ['$required'] }
272
+ { id: 'textarea', name: 'Textarea', lines: 37, desc: 'Auto-resizing text area with focus and validation tracking.', props: ['@value', '@placeholder', '@disabled', '@required', '@rows', '@autoResize'], events: [], keys: [], attrs: ['$disabled', '$focused', '$touched'] }
273
+ { id: 'native-select', name: 'NativeSelect', lines: 21, desc: 'Styled native select element with state tracking.', props: ['@value', '@disabled', '@required'], events: ['change'], keys: [], attrs: ['$disabled', '$focused'] }
274
+ { id: 'input-group', name: 'InputGroup', lines: 15, desc: 'Input with prefix/suffix addon elements.', props: ['@disabled'], events: [], keys: [], attrs: ['$disabled', '$focused'] }
275
+ { id: 'button-group', name: 'ButtonGroup', lines: 11, desc: 'Grouped buttons with ARIA group semantics.', props: ['@orientation', '@disabled', '@label'], events: [], keys: [], attrs: ['$orientation', '$disabled'] }
276
+ { id: 'alert-dialog', name: 'AlertDialog', lines: 83, desc: 'Non-dismissable modal requiring explicit user action.', props: ['@open', '@initialFocus'], events: ['close'], keys: ['Tab'], attrs: ['$open'] }
277
+ { id: 'breadcrumb', name: 'Breadcrumb', lines: 32, desc: 'Navigation trail with separator and current page.', props: ['@separator', '@label'], events: [], keys: [], attrs: ['$current'] }
278
+ { id: 'table', name: 'Table', lines: 11, desc: 'Semantic table wrapper with optional caption and striped rows.', props: ['@caption', '@striped'], events: [], keys: [], attrs: ['$striped'] }
279
+ { id: 'collapsible', name: 'Collapsible', lines: 39, desc: 'Single open/close section with animated expand.', props: ['@open', '@disabled'], events: ['change'], keys: ['Enter', 'Space'], attrs: ['$open', '$disabled'] }
280
+ { id: 'pagination', name: 'Pagination', lines: 113, desc: 'Page navigation with prev/next and ellipsis gaps.', props: ['@page', '@total', '@perPage', '@siblingCount'], events: ['change'], keys: ['← →', 'Home', 'End'], attrs: ['$active', '$disabled', '$ellipsis'] }
281
+ { id: 'carousel', name: 'Carousel', lines: 89, 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'] }
282
+ { id: 'resizable', name: 'Resizable', lines: 104, desc: 'Draggable resize handles between panels.', props: ['@orientation', '@minSize', '@maxSize'], events: ['resize'], keys: ['← →', '↕'], attrs: ['$orientation', '$dragging'] }
283
+ ]
284
+ _infoName := ''
285
+ _infoDesc := ''
286
+ _infoLines := 0
287
+ _infoProps := []
288
+ _infoEvents := []
289
+ _infoKeys := []
290
+ _infoAttrs := []
291
+
292
+ ~>
293
+ info = tocData.find (c) -> c.id is tocActive
294
+ if info
295
+ _infoName = info.name
296
+ _infoDesc = info.desc
297
+ _infoLines = info.lines
298
+ _infoProps = info.props
299
+ _infoEvents = info.events
300
+ _infoKeys = info.keys
301
+ _infoAttrs = info.attrs
302
+
303
+ ~>
304
+ raw = sourceCode
305
+ return unless raw
306
+ setTimeout =>
307
+ codeEl = document.querySelector('.source-code')
308
+ gutterEl = document.querySelector('.source-gutter')
309
+ return unless codeEl
310
+ text = raw.replace(/^\n+/, '').replace(/\n+$/, '')
311
+ lines = text.split('\n')
312
+ gutterEl.textContent = lines.map((_, i) -> String(i + 1)).join('\n') if gutterEl
313
+ codeEl.textContent = text
314
+ codeEl.className = 'source-code language-rip'
315
+ @_ensureHljs =>
316
+ hljs.highlightElement(codeEl) if window.hljs
317
+ , 0
318
+
319
+ ~>
320
+ if sourceCode
321
+ onKey = (e) => @_closeSource() if e.key is 'Escape'
322
+ document.addEventListener 'keydown', onKey
323
+ return -> document.removeEventListener 'keydown', onKey
324
+
325
+ _toggleDark: ->
326
+ darkMode = not darkMode
327
+ document.documentElement.classList.add('no-transition')
328
+ document.documentElement.dataset.theme = if darkMode then 'dark' else ''
329
+ localStorage.setItem('rip-ui-theme', if darkMode then 'dark' else 'light')
330
+ requestAnimationFrame -> requestAnimationFrame -> document.documentElement.classList.remove('no-transition')
331
+
332
+ mounted: ->
333
+ if localStorage.getItem('rip-ui-theme') is 'dark'
334
+ darkMode = true
335
+ document.documentElement.dataset.theme = 'dark'
336
+ document.addEventListener 'keydown', (e) =>
337
+ if e.key is '/' and not (e.target.tagName in ['INPUT', 'TEXTAREA', 'SELECT']) and not e.target.isContentEditable
338
+ e.preventDefault()
339
+ @_navSection('', 'home')
340
+ tocFilter = ''
341
+ document.addEventListener 'keyup', (=>
342
+ @_root?.querySelector('.toc-search')?.focus()
343
+ ), { once: true }
344
+ gc = document.querySelector('.grid-container')
345
+ if gc
346
+ gc.addEventListener 'mousedown', => setTimeout (=> @_updateGridRef()), 0
347
+ gc.addEventListener 'keyup', => setTimeout (=> @_updateGridRef()), 0
348
+ hash = location.hash.slice(1)
349
+ if hash and document.getElementById(hash)
350
+ tocActive = hash
351
+ setTimeout =>
352
+ document.getElementById(hash)?.scrollIntoView({ behavior: 'instant', block: 'start' })
353
+ document.body.classList.add('ready')
354
+ , 50
355
+ else
356
+ document.body.classList.add('ready')
357
+
358
+ gridCellRef := ''
359
+
360
+ _updateGridRef: ->
361
+ cell = document.querySelector('.grid-container td[data-active]')
362
+ if cell
363
+ tr = cell.parentElement
364
+ rowNum = tr?.firstElementChild?.textContent?.trim()
365
+ ci = Array.from(tr.children).indexOf(cell)
366
+ gridCellRef = if rowNum then "R#{rowNum}:C#{ci + 1}" else ''
367
+ else
368
+ gridCellRef = ''
369
+
370
+ _loadGridRows: (n) ->
371
+ firstNames = ['Alice', 'Bob', 'Carol', 'Dan', 'Eve', 'Frank', 'Grace', 'Hank', 'Iris', 'Jack', 'Karen', 'Leo', 'Mia', 'Nora', 'Omar', 'Pia', 'Quinn', 'Rosa', 'Sam', 'Tina']
372
+ lastNames = ['Chen', 'Park', 'Singh', 'Nakamura', 'Torres', 'Liu', 'Kim', 'Patel', 'Wang', 'Brown', 'Lee', 'Martin', 'Garcia', 'Davis', 'Lopez', 'Clark', 'Hall', 'Young', 'King', 'Wright']
373
+ roles = ['Engineer', 'Designer', 'Manager', 'Director', 'Analyst', 'Lead', 'Intern']
374
+ cityList = ['Seattle', 'Portland', 'Denver', 'Austin', 'Chicago', 'Boston', 'Miami', 'Phoenix', 'Dallas', 'Atlanta']
375
+ gc = document.querySelector('.grid-container')
376
+ gc?.scrollTop = 0
377
+ gridData = Array.from { length: n }, (_, idx) ->
378
+ fn = firstNames[idx %% firstNames.length]
379
+ ln = lastNames[Math.floor(Math.random() * lastNames.length)]
380
+ { _row: idx + 1, name: "#{fn} #{ln}", role: roles[idx %% roles.length], age: 22 + (idx %% 40), city: cityList[idx %% cityList.length] }
381
+ gridCellRef = ''
382
+
383
+ gridColumns := [
384
+ { key: '_row', title: '#', width: 60, align: 'right' },
385
+ { key: 'name', title: 'Name', width: 180 },
386
+ { key: 'role', title: 'Role', width: 140 },
387
+ { key: 'age', title: 'Age', width: 80, align: 'right' },
388
+ { key: 'city', title: 'City', width: 140 },
389
+ ]
390
+ gridData := [
391
+ { _row: 1, name: 'Alice Chen', role: 'Engineer', age: 28, city: 'Seattle' },
392
+ { _row: 2, name: 'Bob Park', role: 'Designer', age: 34, city: 'Portland' },
393
+ { _row: 3, name: 'Carol Singh', role: 'Manager', age: 41, city: 'Denver' },
394
+ { _row: 4, name: 'Dan Nakamura', role: 'Engineer', age: 26, city: 'Austin' },
395
+ { _row: 5, name: 'Eve Torres', role: 'Director', age: 45, city: 'Chicago' },
396
+ { _row: 6, name: 'Frank Liu', role: 'Designer', age: 31, city: 'Boston' },
397
+ { _row: 7, name: 'Grace Kim', role: 'Engineer', age: 29, city: 'Seattle' },
398
+ { _row: 8, name: 'Hank Patel', role: 'Manager', age: 38, city: 'Denver' },
399
+ { _row: 9, name: 'Iris Wang', role: 'Engineer', age: 33, city: 'Austin' },
400
+ { _row: 10, name: 'Jack Brown', role: 'Designer', age: 27, city: 'Portland' },
401
+ { _row: 11, name: 'Karen Lee', role: 'Director', age: 52, city: 'Chicago' },
402
+ { _row: 12, name: 'Leo Martin', role: 'Engineer', age: 24, city: 'Boston' },
403
+ ]
404
+
405
+ 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']
406
+
407
+ _fadeTo: (el) ->
408
+ return unless el
409
+ gallery = document.querySelector('.gallery')
410
+ gallery.style.transition = 'opacity 80ms ease-out'
411
+ gallery.style.opacity = '0'
412
+ setTimeout =>
413
+ el.scrollIntoView({ behavior: 'instant', block: 'start' })
414
+ history.replaceState(null, '', '#' + el.id) if el.id
415
+ gallery.style.transition = 'opacity 120ms ease-in'
416
+ gallery.style.opacity = '1'
417
+ , 80
418
+
419
+ _jumpTo: (e) ->
420
+ e.preventDefault()
421
+ @_fadeTo(document.getElementById(tocActive))
422
+
423
+ _navSection: (currentId, dir) ->
424
+ if dir is 'home'
425
+ gallery = document.querySelector('.gallery')
426
+ gallery.style.transition = 'opacity 80ms ease-out'
427
+ gallery.style.opacity = '0'
428
+ setTimeout =>
429
+ window.scrollTo({ top: 0, behavior: 'instant' })
430
+ history.replaceState(null, '', location.pathname)
431
+ gallery.style.transition = 'opacity 120ms ease-in'
432
+ gallery.style.opacity = '1'
433
+ , 80
434
+ return
435
+ idx = sectionIds.indexOf(currentId)
436
+ return if idx < 0
437
+ nextId = sectionIds[if dir is 'prev' then idx - 1 else idx + 1]
438
+ @_fadeTo(document.getElementById(nextId)) if nextId
439
+
440
+ render
441
+ .gallery
442
+
443
+ .gallery-header
444
+ .
445
+ h1 "Rip UI"
446
+ button.theme-toggle @click: (=> @_toggleDark())
447
+ if darkMode then "☀" else "☾"
448
+ p "54 headless, accessible components. Zero CSS, zero dependencies."
449
+
450
+ .toc
451
+ .toc-nav @keydown: @_onTocKeydown
452
+ input.toc-search type: "text", placeholder: "Filter components...", @input: @_onFilter, @keydown: @_onFilterKey
453
+ .toc-group
454
+ .toc-label "Selection"
455
+ a @mouseenter: (=> tocActive = 'select'), @click: (=> tocActive = 'select'), href: "#select", $active: (tocActive is 'select')?!, "Select"
456
+ a @mouseenter: (=> tocActive = 'combobox'), @click: (=> tocActive = 'combobox'), href: "#combobox", $active: (tocActive is 'combobox')?!, "Combobox"
457
+ a @mouseenter: (=> tocActive = 'multi-select'), @click: (=> tocActive = 'multi-select'), href: "#multi-select", $active: (tocActive is 'multi-select')?!, "MultiSelect"
458
+ a @mouseenter: (=> tocActive = 'autocomplete'), @click: (=> tocActive = 'autocomplete'), href: "#autocomplete", $active: (tocActive is 'autocomplete')?!, "Autocomplete"
459
+ .toc-group
460
+ .toc-label "Toggle"
461
+ a @mouseenter: (=> tocActive = 'checkbox'), @click: (=> tocActive = 'checkbox'), href: "#checkbox", $active: (tocActive is 'checkbox')?!, "Checkbox"
462
+ a @mouseenter: (=> tocActive = 'toggle'), @click: (=> tocActive = 'toggle'), href: "#toggle", $active: (tocActive is 'toggle')?!, "Toggle"
463
+ a @mouseenter: (=> tocActive = 'toggle-group'), @click: (=> tocActive = 'toggle-group'), href: "#toggle-group", $active: (tocActive is 'toggle-group')?!, "ToggleGroup"
464
+ a @mouseenter: (=> tocActive = 'radio-group'), @click: (=> tocActive = 'radio-group'), href: "#radio-group", $active: (tocActive is 'radio-group')?!, "RadioGroup"
465
+ a @mouseenter: (=> tocActive = 'checkbox-group'), @click: (=> tocActive = 'checkbox-group'), href: "#checkbox-group", $active: (tocActive is 'checkbox-group')?!, "CheckboxGroup"
466
+ .toc-group
467
+ .toc-label "Input"
468
+ a @mouseenter: (=> tocActive = 'input'), @click: (=> tocActive = 'input'), href: "#input", $active: (tocActive is 'input')?!, "Input"
469
+ a @mouseenter: (=> tocActive = 'textarea'), @click: (=> tocActive = 'textarea'), href: "#textarea", $active: (tocActive is 'textarea')?!, "Textarea"
470
+ a @mouseenter: (=> tocActive = 'native-select'), @click: (=> tocActive = 'native-select'), href: "#native-select", $active: (tocActive is 'native-select')?!, "NativeSelect"
471
+ a @mouseenter: (=> tocActive = 'number-field'), @click: (=> tocActive = 'number-field'), href: "#number-field", $active: (tocActive is 'number-field')?!, "NumberField"
472
+ a @mouseenter: (=> tocActive = 'slider'), @click: (=> tocActive = 'slider'), href: "#slider", $active: (tocActive is 'slider')?!, "Slider"
473
+ a @mouseenter: (=> tocActive = 'otp-field'), @click: (=> tocActive = 'otp-field'), href: "#otp-field", $active: (tocActive is 'otp-field')?!, "OTPField"
474
+ a @mouseenter: (=> tocActive = 'date-picker'), @click: (=> tocActive = 'date-picker'), href: "#date-picker", $active: (tocActive is 'date-picker')?!, "DatePicker"
475
+ a @mouseenter: (=> tocActive = 'editable-value'), @click: (=> tocActive = 'editable-value'), href: "#editable-value", $active: (tocActive is 'editable-value')?!, "EditableValue"
476
+ a @mouseenter: (=> tocActive = 'input-group'), @click: (=> tocActive = 'input-group'), href: "#input-group", $active: (tocActive is 'input-group')?!, "InputGroup"
477
+ .toc-group
478
+ .toc-label "Navigation"
479
+ a @mouseenter: (=> tocActive = 'tabs'), @click: (=> tocActive = 'tabs'), href: "#tabs", $active: (tocActive is 'tabs')?!, "Tabs"
480
+ a @mouseenter: (=> tocActive = 'menu'), @click: (=> tocActive = 'menu'), href: "#menu", $active: (tocActive is 'menu')?!, "Menu"
481
+ a @mouseenter: (=> tocActive = 'context-menu'), @click: (=> tocActive = 'context-menu'), href: "#context-menu", $active: (tocActive is 'context-menu')?!, "ContextMenu"
482
+ a @mouseenter: (=> tocActive = 'menubar'), @click: (=> tocActive = 'menubar'), href: "#menubar", $active: (tocActive is 'menubar')?!, "Menubar"
483
+ a @mouseenter: (=> tocActive = 'nav-menu'), @click: (=> tocActive = 'nav-menu'), href: "#nav-menu", $active: (tocActive is 'nav-menu')?!, "NavMenu"
484
+ a @mouseenter: (=> tocActive = 'toolbar'), @click: (=> tocActive = 'toolbar'), href: "#toolbar", $active: (tocActive is 'toolbar')?!, "Toolbar"
485
+ a @mouseenter: (=> tocActive = 'breadcrumb'), @click: (=> tocActive = 'breadcrumb'), href: "#breadcrumb", $active: (tocActive is 'breadcrumb')?!, "Breadcrumb"
486
+ .toc-group
487
+ .toc-label "Overlay"
488
+ a @mouseenter: (=> tocActive = 'dialog'), @click: (=> tocActive = 'dialog'), href: "#dialog", $active: (tocActive is 'dialog')?!, "Dialog"
489
+ a @mouseenter: (=> tocActive = 'alert-dialog'), @click: (=> tocActive = 'alert-dialog'), href: "#alert-dialog", $active: (tocActive is 'alert-dialog')?!, "AlertDialog"
490
+ a @mouseenter: (=> tocActive = 'drawer'), @click: (=> tocActive = 'drawer'), href: "#drawer", $active: (tocActive is 'drawer')?!, "Drawer"
491
+ a @mouseenter: (=> tocActive = 'popover'), @click: (=> tocActive = 'popover'), href: "#popover", $active: (tocActive is 'popover')?!, "Popover"
492
+ a @mouseenter: (=> tocActive = 'tooltip'), @click: (=> tocActive = 'tooltip'), href: "#tooltip", $active: (tocActive is 'tooltip')?!, "Tooltip"
493
+ a @mouseenter: (=> tocActive = 'preview-card'), @click: (=> tocActive = 'preview-card'), href: "#preview-card", $active: (tocActive is 'preview-card')?!, "PreviewCard"
494
+ a @mouseenter: (=> tocActive = 'toast'), @click: (=> tocActive = 'toast'), href: "#toast", $active: (tocActive is 'toast')?!, "Toast"
495
+ .toc-group
496
+ .toc-label "Display"
497
+ a @mouseenter: (=> tocActive = 'button'), @click: (=> tocActive = 'button'), href: "#button", $active: (tocActive is 'button')?!, "Button"
498
+ a @mouseenter: (=> tocActive = 'badge'), @click: (=> tocActive = 'badge'), href: "#badge", $active: (tocActive is 'badge')?!, "Badge"
499
+ a @mouseenter: (=> tocActive = 'card'), @click: (=> tocActive = 'card'), href: "#card", $active: (tocActive is 'card')?!, "Card"
500
+ a @mouseenter: (=> tocActive = 'separator'), @click: (=> tocActive = 'separator'), href: "#separator", $active: (tocActive is 'separator')?!, "Separator"
501
+ a @mouseenter: (=> tocActive = 'progress'), @click: (=> tocActive = 'progress'), href: "#progress", $active: (tocActive is 'progress')?!, "Progress"
502
+ a @mouseenter: (=> tocActive = 'meter'), @click: (=> tocActive = 'meter'), href: "#meter", $active: (tocActive is 'meter')?!, "Meter"
503
+ a @mouseenter: (=> tocActive = 'spinner'), @click: (=> tocActive = 'spinner'), href: "#spinner", $active: (tocActive is 'spinner')?!, "Spinner"
504
+ a @mouseenter: (=> tocActive = 'skeleton'), @click: (=> tocActive = 'skeleton'), href: "#skeleton", $active: (tocActive is 'skeleton')?!, "Skeleton"
505
+ a @mouseenter: (=> tocActive = 'avatar'), @click: (=> tocActive = 'avatar'), href: "#avatar", $active: (tocActive is 'avatar')?!, "Avatar"
506
+ a @mouseenter: (=> tocActive = 'label'), @click: (=> tocActive = 'label'), href: "#label", $active: (tocActive is 'label')?!, "Label"
507
+ a @mouseenter: (=> tocActive = 'scroll-area'), @click: (=> tocActive = 'scroll-area'), href: "#scroll-area", $active: (tocActive is 'scroll-area')?!, "ScrollArea"
508
+ .toc-group
509
+ .toc-label "Form"
510
+ a @mouseenter: (=> tocActive = 'field'), @click: (=> tocActive = 'field'), href: "#field", $active: (tocActive is 'field')?!, "Field"
511
+ a @mouseenter: (=> tocActive = 'fieldset'), @click: (=> tocActive = 'fieldset'), href: "#fieldset", $active: (tocActive is 'fieldset')?!, "Fieldset"
512
+ a @mouseenter: (=> tocActive = 'form'), @click: (=> tocActive = 'form'), href: "#form", $active: (tocActive is 'form')?!, "Form"
513
+ a @mouseenter: (=> tocActive = 'button-group'), @click: (=> tocActive = 'button-group'), href: "#button-group", $active: (tocActive is 'button-group')?!, "ButtonGroup"
514
+ .toc-group
515
+ .toc-label "Data"
516
+ a @mouseenter: (=> tocActive = 'grid'), @click: (=> tocActive = 'grid'), href: "#grid", $active: (tocActive is 'grid')?!, "Grid"
517
+ a @mouseenter: (=> tocActive = 'accordion'), @click: (=> tocActive = 'accordion'), href: "#accordion", $active: (tocActive is 'accordion')?!, "Accordion"
518
+ a @mouseenter: (=> tocActive = 'table'), @click: (=> tocActive = 'table'), href: "#table", $active: (tocActive is 'table')?!, "Table"
519
+ a @mouseenter: (=> tocActive = 'collapsible'), @click: (=> tocActive = 'collapsible'), href: "#collapsible", $active: (tocActive is 'collapsible')?!, "Collapsible"
520
+ .toc-group
521
+ .toc-label "Interactive"
522
+ a @mouseenter: (=> tocActive = 'pagination'), @click: (=> tocActive = 'pagination'), href: "#pagination", $active: (tocActive is 'pagination')?!, "Pagination"
523
+ a @mouseenter: (=> tocActive = 'carousel'), @click: (=> tocActive = 'carousel'), href: "#carousel", $active: (tocActive is 'carousel')?!, "Carousel"
524
+ a @mouseenter: (=> tocActive = 'resizable'), @click: (=> tocActive = 'resizable'), href: "#resizable", $active: (tocActive is 'resizable')?!, "Resizable"
525
+ .toc-detail
526
+ if tocActive
527
+ .
528
+ h3
529
+ _infoName
530
+ span.badge @click: (=> @_viewSource(tocActive)), "#{_infoLines} lines ❐"
531
+ p.toc-desc _infoDesc
532
+ .api
533
+ dl
534
+ dt "Props"
535
+ dd
536
+ for item in _infoProps
537
+ code item
538
+ if _infoEvents.length
539
+ dt "Events"
540
+ dd
541
+ for item in _infoEvents
542
+ code item
543
+ if _infoKeys.length
544
+ dt "Keyboard"
545
+ dd
546
+ for item in _infoKeys
547
+ kbd item
548
+ dt "Data"
549
+ dd
550
+ for item in _infoAttrs
551
+ code item
552
+ a.jump href: "##{tocActive}", @click: ((e) => @_jumpTo(e))
553
+ "Jump to demo ↓"
554
+ else
555
+ .toc-empty "← Hover a component to see its API"
556
+
557
+ # ========================================================================
558
+ # SELECT
559
+ # ========================================================================
560
+ .section id: "select"
561
+ SectionHead tag: "select", name: "Select", lines: 135
562
+ .section-desc "Dropdown with typeahead, keyboard nav, ARIA listbox."
563
+ .demo-row
564
+ .demo-label "Basic select"
565
+ Select value <=> fruit, @change: (=> null)
566
+ for f in allFruits
567
+ option value: f.toLowerCase(), f
568
+ .status "selected: #{fruit ?? 'none'}"
569
+ .demo-row
570
+ .demo-label "With placeholder"
571
+ Select value <=> role, placeholder: "Choose a role..."
572
+ option value: "eng", "Engineer"
573
+ option value: "des", "Designer"
574
+ option value: "mgr", "Manager"
575
+ option value: "dir", "Director"
576
+ .status "selected: #{role ?? 'none'}"
577
+ .api
578
+ dl
579
+ dt "Props"
580
+ dd
581
+ code "@value"
582
+ code "@placeholder"
583
+ code "@disabled"
584
+ dt "Events"
585
+ dd
586
+ code "change"
587
+ dt "Keyboard"
588
+ dd
589
+ kbd "↕"
590
+ kbd "Enter"
591
+ kbd "Space"
592
+ kbd "Esc"
593
+ kbd "Home"
594
+ kbd "End"
595
+ kbd "type-ahead"
596
+ dt "Data"
597
+ dd
598
+ code "$open"
599
+ code "$placeholder"
600
+ code "$disabled"
601
+ code "$value"
602
+ code "$highlighted"
603
+ code "$selected"
604
+
605
+ # ========================================================================
606
+ # COMBOBOX
607
+ # ========================================================================
608
+ .section id: "combobox"
609
+ SectionHead tag: "combobox", name: "Combobox", lines: 118
610
+ .section-desc "Filterable input + listbox for search-as-you-type."
611
+ .demo-row
612
+ .demo-label "Search fruits"
613
+ Combobox query <=> searchQuery, items: filteredFruits, placeholder: "Type to search..."
614
+ @select: (e) => (searchQuery = e.detail if e.detail?; filteredFruits = allFruits)
615
+ @filter: (=> filteredFruits = allFruits.filter (f) -> f.toLowerCase().includes(searchQuery.toLowerCase()))
616
+ .status "query: '#{searchQuery}'"
617
+ .api
618
+ dl
619
+ dt "Props"
620
+ dd
621
+ code "@query"
622
+ code "@items"
623
+ code "@placeholder"
624
+ code "@disabled"
625
+ code "@autoHighlight"
626
+ dt "Events"
627
+ dd
628
+ code "filter"
629
+ code "select"
630
+ dt "Keyboard"
631
+ dd
632
+ kbd "↕"
633
+ kbd "Enter"
634
+ kbd "Esc"
635
+ kbd "Tab"
636
+ dt "Data"
637
+ dd
638
+ code "$open"
639
+ code "$disabled"
640
+ code "$clear"
641
+ code "$value"
642
+ code "$highlighted"
643
+ code "$empty"
644
+
645
+ # ========================================================================
646
+ # MULTI-SELECT
647
+ # ========================================================================
648
+ .section id: "multi-select"
649
+ SectionHead tag: "multi-select", name: "MultiSelect", lines: 146
650
+ .section-desc "Multi-select with chips, filtering, and keyboard navigation."
651
+ .demo-row
652
+ .demo-label "Pick colors"
653
+ MultiSelect value <=> selectedColors, items: ['Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Indigo', 'Violet'], placeholder: "Choose colors..."
654
+ .status "selected: #{selectedColors.join(', ')}"
655
+ .api
656
+ dl
657
+ dt "Props"
658
+ dd
659
+ code "@value"
660
+ code "@items"
661
+ code "@placeholder"
662
+ code "@disabled"
663
+ dt "Events"
664
+ dd
665
+ code "change"
666
+ dt "Keyboard"
667
+ dd
668
+ kbd "↕"
669
+ kbd "Enter"
670
+ kbd "Esc"
671
+ kbd "Backspace"
672
+ kbd "Tab"
673
+ dt "Data"
674
+ dd
675
+ code "$open"
676
+ code "$disabled"
677
+ code "$chips"
678
+ code "$chip"
679
+ code "$remove"
680
+ code "$clear"
681
+ code "$highlighted"
682
+ code "$selected"
683
+
684
+ # ========================================================================
685
+ # AUTOCOMPLETE
686
+ # ========================================================================
687
+ .section id: "autocomplete"
688
+ SectionHead tag: "autocomplete", name: "Autocomplete", lines: 113
689
+ .section-desc "Suggestion input — type to filter, select to fill."
690
+ .demo-row
691
+ .demo-label "Search cities"
692
+ Autocomplete value <=> citySearch, items: cities
693
+ .status "value: #{citySearch}"
694
+ .api
695
+ dl
696
+ dt "Props"
697
+ dd
698
+ code "@value"
699
+ code "@items"
700
+ code "@placeholder"
701
+ code "@disabled"
702
+ dt "Events"
703
+ dd
704
+ code "select"
705
+ dt "Keyboard"
706
+ dd
707
+ kbd "↕"
708
+ kbd "Enter"
709
+ kbd "Esc"
710
+ kbd "Tab"
711
+ dt "Data"
712
+ dd
713
+ code "$open"
714
+ code "$disabled"
715
+ code "$clear"
716
+
717
+ # ========================================================================
718
+ # CHECKBOX
719
+ # ========================================================================
720
+ .section id: "checkbox"
721
+ SectionHead tag: "checkbox", name: "Checkbox", lines: 18
722
+ .section-desc "Toggle with checkbox or switch semantics. Supports indeterminate."
723
+ .demo-row
724
+ .demo-label "Checkbox (unchecked)"
725
+ Checkbox checked <=> check1, @change: (=> p "Clicked!")
726
+ "Enable notifications"
727
+ .status "checked: #{check1}"
728
+ .demo-row
729
+ .demo-label "Checkbox (pre-checked)"
730
+ Checkbox checked <=> check2
731
+ "Accept terms"
732
+ .status "checked: #{check2}"
733
+ .demo-row
734
+ .demo-label "Switch variant"
735
+ Checkbox checked <=> switch1, switch: true
736
+ "Dark mode"
737
+ .status "on: #{switch1}"
738
+ .api
739
+ dl
740
+ dt "Props"
741
+ dd
742
+ code "@checked"
743
+ code "@disabled"
744
+ code "@indeterminate"
745
+ code "@switch"
746
+ dt "Events"
747
+ dd
748
+ code "change"
749
+ dt "Data"
750
+ dd
751
+ code "$checked"
752
+ code "$indeterminate"
753
+ code "$disabled"
754
+
755
+ # ========================================================================
756
+ # TOGGLE
757
+ # ========================================================================
758
+ .section id: "toggle"
759
+ SectionHead tag: "toggle", name: "Toggle", lines: 14
760
+ .section-desc "Stateful toggle button with pressed state."
761
+ .demo-row
762
+ .demo-label "Tap to toggle"
763
+ .toggle-heart
764
+ Toggle pressed <=> isBold
765
+ span.heart (if isBold then "♥" else "♡")
766
+ .api
767
+ dl
768
+ dt "Props"
769
+ dd
770
+ code "@pressed"
771
+ code "@disabled"
772
+ dt "Events"
773
+ dd
774
+ code "change"
775
+ dt "Data"
776
+ dd
777
+ code "$pressed"
778
+ code "$disabled"
779
+
780
+ # ========================================================================
781
+ # TOGGLE GROUP
782
+ # ========================================================================
783
+ .section id: "toggle-group"
784
+ SectionHead tag: "toggle-group", name: "ToggleGroup", lines: 55
785
+ .section-desc "Single or multi-select toggle buttons."
786
+ .demo-row
787
+ .demo-label "Text alignment"
788
+ ToggleGroup value <=> alignment
789
+ div $value: "left", "Left"
790
+ div $value: "center", "Center"
791
+ div $value: "right", "Right"
792
+ .status "alignment: #{alignment ?? 'none'}"
793
+ .demo-row
794
+ .demo-label "Multi-select"
795
+ ToggleGroup value <=> toppings, multiple: true
796
+ div $value: "Cheese", "Cheese"
797
+ div $value: "Bacon", "Bacon"
798
+ div $value: "Lettuce", "Lettuce"
799
+ .status "toppings: #{toppings.join(', ')}"
800
+ .api
801
+ dl
802
+ dt "Props"
803
+ dd
804
+ code "@value"
805
+ code "@disabled"
806
+ code "@multiple"
807
+ code "@orientation"
808
+ dt "Events"
809
+ dd
810
+ code "change"
811
+ dt "Keyboard"
812
+ dd
813
+ kbd "← →"
814
+ kbd "↕"
815
+ kbd "Home"
816
+ kbd "End"
817
+ dt "Data"
818
+ dd
819
+ code "$orientation"
820
+ code "$disabled"
821
+ code "$pressed"
822
+ code "$value"
823
+
824
+ # ========================================================================
825
+ # RADIO GROUP
826
+ # ========================================================================
827
+ .section id: "radio-group"
828
+ SectionHead tag: "radio-group", name: "RadioGroup", lines: 42
829
+ .section-desc "Exactly one option selected. Arrow keys move focus and selection."
830
+ .demo-row
831
+ .demo-label "Font size"
832
+ RadioGroup value <=> fontSize
833
+ div $value: "sm", "Small"
834
+ div $value: "md", "Medium"
835
+ div $value: "lg", "Large"
836
+ .status "fontSize: #{fontSize ?? 'none'}"
837
+ .api
838
+ dl
839
+ dt "Props"
840
+ dd
841
+ code "@value"
842
+ code "@disabled"
843
+ code "@orientation"
844
+ code "@name"
845
+ dt "Events"
846
+ dd
847
+ code "change"
848
+ dt "Keyboard"
849
+ dd
850
+ kbd "← →"
851
+ kbd "↕"
852
+ kbd "Home"
853
+ kbd "End"
854
+ dt "Data"
855
+ dd
856
+ code "$orientation"
857
+ code "$disabled"
858
+ code "$checked"
859
+ code "$value"
860
+
861
+ # ========================================================================
862
+ # CHECKBOX GROUP
863
+ # ========================================================================
864
+ .section id: "checkbox-group"
865
+ SectionHead tag: "checkbox-group", name: "CheckboxGroup", lines: 45
866
+ .section-desc "Multiple options checked independently."
867
+ .demo-row
868
+ .demo-label "Toppings"
869
+ CheckboxGroup value <=> cbToppings, label: "Pizza toppings"
870
+ div $value: "Cheese", "Cheese"
871
+ div $value: "Bacon", "Bacon"
872
+ div $value: "Lettuce", "Lettuce"
873
+ div $value: "Tomato", "Tomato"
874
+ .status "selected: #{cbToppings.join(', ')}"
875
+ .api
876
+ dl
877
+ dt "Props"
878
+ dd
879
+ code "@value"
880
+ code "@disabled"
881
+ code "@orientation"
882
+ code "@label"
883
+ dt "Events"
884
+ dd
885
+ code "change"
886
+ dt "Keyboard"
887
+ dd
888
+ kbd "← →"
889
+ kbd "↕"
890
+ dt "Data"
891
+ dd
892
+ code "$orientation"
893
+ code "$disabled"
894
+ code "$checked"
895
+ code "$value"
896
+
897
+ # ========================================================================
898
+ # INPUT
899
+ # ========================================================================
900
+ .section id: "input"
901
+ SectionHead tag: "input", name: "Input", lines: 25
902
+ .section-desc "Headless input tracking focus, touch, and validation state."
903
+ .demo-row
904
+ .demo-label "Text input"
905
+ Input value <=> userName, placeholder: "Enter your name"
906
+ .status "value: #{userName}"
907
+ .api
908
+ dl
909
+ dt "Props"
910
+ dd
911
+ code "@value"
912
+ code "@placeholder"
913
+ code "@type"
914
+ code "@disabled"
915
+ code "@required"
916
+ dt "Data"
917
+ dd
918
+ code "$disabled"
919
+ code "$focused"
920
+ code "$touched"
921
+
922
+ # ========================================================================
923
+ # NUMBER FIELD
924
+ # ========================================================================
925
+ .section id: "number-field"
926
+ SectionHead tag: "number-field", name: "NumberField", lines: 151
927
+ .section-desc "Number input with increment/decrement buttons, hold-to-repeat, and keyboard stepping."
928
+ .demo-row
929
+ .demo-label "Quantity (1-99)"
930
+ .nf-wrap
931
+ NumberField value <=> quantity, min: 1, max: 99
932
+ .status "quantity: #{quantity}"
933
+ .demo-row
934
+ .demo-label "Price (step: 0.01)"
935
+ .nf-wrap
936
+ NumberField value <=> price, min: 0, max: 999.99, step: 0.01
937
+ .status "price: #{price}"
938
+ .api
939
+ dl
940
+ dt "Props"
941
+ dd
942
+ code "@value"
943
+ code "@min"
944
+ code "@max"
945
+ code "@step"
946
+ code "@disabled"
947
+ code "@readOnly"
948
+ dt "Events"
949
+ dd
950
+ code "input"
951
+ code "change"
952
+ dt "Keyboard"
953
+ dd
954
+ kbd "↕"
955
+ kbd "PgUp"
956
+ kbd "PgDn"
957
+ kbd "Home"
958
+ kbd "End"
959
+ dt "Data"
960
+ dd
961
+ code "$disabled"
962
+ code "$readonly"
963
+ code "$decrement"
964
+ code "$increment"
965
+
966
+ # ========================================================================
967
+ # SLIDER
968
+ # ========================================================================
969
+ .section id: "slider"
970
+ SectionHead tag: "slider", name: "Slider", lines: 152
971
+ .section-desc "Draggable range input with pointer capture and keyboard stepping."
972
+ .demo-row
973
+ .demo-label "Single thumb"
974
+ .slider-wrap
975
+ Slider value <=> sliderVal
976
+ .status "value: #{sliderVal}"
977
+ .demo-row
978
+ .demo-label "Range (two thumbs)"
979
+ .slider-wrap
980
+ Slider value <=> rangeVal
981
+ .status "range: #{rangeVal}"
982
+ .api
983
+ dl
984
+ dt "Props"
985
+ dd
986
+ code "@value"
987
+ code "@min"
988
+ code "@max"
989
+ code "@step"
990
+ code "@orientation"
991
+ code "@disabled"
992
+ dt "Events"
993
+ dd
994
+ code "input"
995
+ code "change"
996
+ dt "Keyboard"
997
+ dd
998
+ kbd "← →"
999
+ kbd "↕"
1000
+ kbd "PgUp"
1001
+ kbd "PgDn"
1002
+ kbd "Home"
1003
+ kbd "End"
1004
+ dt "Data"
1005
+ dd
1006
+ code "$orientation"
1007
+ code "$disabled"
1008
+ code "$dragging"
1009
+ code "$track"
1010
+ code "$indicator"
1011
+ code "$thumb"
1012
+ code "$active"
1013
+
1014
+ # ========================================================================
1015
+ # OTP FIELD
1016
+ # ========================================================================
1017
+ .section id: "otp-field"
1018
+ SectionHead tag: "otp-field", name: "OTPField", lines: 79
1019
+ .section-desc "Multi-digit code input with auto-advance, backspace nav, and paste."
1020
+ .demo-row
1021
+ .demo-label "6-digit code"
1022
+ OTPField length: 6, value <=> otpCode
1023
+ .status "code: '#{otpCode}'"
1024
+ .demo-row
1025
+ .demo-label "4-digit masked"
1026
+ OTPField length: 4, mask: true
1027
+ .api
1028
+ dl
1029
+ dt "Props"
1030
+ dd
1031
+ code "@length"
1032
+ code "@value"
1033
+ code "@disabled"
1034
+ code "@mask"
1035
+ dt "Events"
1036
+ dd
1037
+ code "input"
1038
+ code "complete"
1039
+ dt "Keyboard"
1040
+ dd
1041
+ kbd "← →"
1042
+ kbd "Backspace"
1043
+ kbd "Home"
1044
+ kbd "End"
1045
+ kbd "paste"
1046
+ dt "Data"
1047
+ dd
1048
+ code "$disabled"
1049
+ code "$complete"
1050
+ code "$filled"
1051
+
1052
+ # ========================================================================
1053
+ # DATE PICKER
1054
+ # ========================================================================
1055
+ .section id: "date-picker"
1056
+ SectionHead tag: "date-picker", name: "DatePicker", lines: 194
1057
+ .section-desc "Calendar dropdown for single date or range selection."
1058
+ .demo-row
1059
+ .demo-label "Single date"
1060
+ DatePicker value <=> pickedDate
1061
+ .status "date: #{if pickedDate then pickedDate.toLocaleDateString() else 'none'}"
1062
+ .demo-row
1063
+ .demo-label "Date range"
1064
+ DatePicker value <=> dateRange, range: true
1065
+ .status "range: #{if dateRange and dateRange[0] then dateRange[0].toLocaleDateString() else 'none'} – #{if dateRange and dateRange[1] then dateRange[1].toLocaleDateString() else '...'}"
1066
+ .api
1067
+ dl
1068
+ dt "Props"
1069
+ dd
1070
+ code "@value"
1071
+ code "@placeholder"
1072
+ code "@disabled"
1073
+ code "@range"
1074
+ code "@firstDayOfWeek"
1075
+ dt "Events"
1076
+ dd
1077
+ code "change"
1078
+ dt "Keyboard"
1079
+ dd
1080
+ kbd "Esc"
1081
+ kbd "Enter"
1082
+ kbd "Space"
1083
+ dt "Data"
1084
+ dd
1085
+ code "$open"
1086
+ code "$disabled"
1087
+ code "$range"
1088
+ code "$calendar"
1089
+ code "$selected"
1090
+ code "$today"
1091
+ code "$in-range"
1092
+
1093
+ # ========================================================================
1094
+ # EDITABLE VALUE
1095
+ # ========================================================================
1096
+ .section id: "editable-value"
1097
+ SectionHead tag: "editable-value", name: "EditableValue", lines: 70
1098
+ .section-desc "Click the edit icon to modify the value inline."
1099
+ .demo-row
1100
+ EditableValue @save: (=> editName = editName)
1101
+ span $display: true
1102
+ "#{editName}"
1103
+ div $editor: true
1104
+ input type: "text", value: editName
1105
+ @input: (e) => editName = e.target.value
1106
+ .api
1107
+ dl
1108
+ dt "Props"
1109
+ dd
1110
+ code "@disabled"
1111
+ dt "Events"
1112
+ dd
1113
+ code "save"
1114
+ dt "Keyboard"
1115
+ dd
1116
+ kbd "Esc"
1117
+ kbd "Enter"
1118
+ dt "Data"
1119
+ dd
1120
+ code "$editing"
1121
+ code "$disabled"
1122
+ code "$saving"
1123
+ code "$edit-trigger"
1124
+
1125
+ # ========================================================================
1126
+ # TEXTAREA
1127
+ # ========================================================================
1128
+ .section id: "textarea"
1129
+ SectionHead tag: "textarea", name: "Textarea", lines: 37
1130
+ .section-desc "Auto-resizing text area with focus and validation tracking."
1131
+ .demo-row
1132
+ .demo-label "Auto-resize textarea"
1133
+ Textarea value <=> textareaVal, placeholder: "Type something...", autoResize: true
1134
+ .status "length: #{textareaVal.length}"
1135
+ .api
1136
+ dl
1137
+ dt "Props"
1138
+ dd
1139
+ code "@value"
1140
+ code "@placeholder"
1141
+ code "@disabled"
1142
+ code "@required"
1143
+ code "@rows"
1144
+ code "@autoResize"
1145
+ dt "Data"
1146
+ dd
1147
+ code "$disabled"
1148
+ code "$focused"
1149
+ code "$touched"
1150
+
1151
+ # ========================================================================
1152
+ # NATIVE SELECT
1153
+ # ========================================================================
1154
+ .section id: "native-select"
1155
+ SectionHead tag: "native-select", name: "NativeSelect", lines: 21
1156
+ .section-desc "Native browser select element with state tracking."
1157
+ .demo-row.native-select-demo
1158
+ .demo-label "Choose a color"
1159
+ NativeSelect value <=> nativeSelectVal
1160
+ option value: "", "Pick one..."
1161
+ option value: "red", "Red"
1162
+ option value: "green", "Green"
1163
+ option value: "blue", "Blue"
1164
+ .status "value: '#{nativeSelectVal}'"
1165
+ .api
1166
+ dl
1167
+ dt "Props"
1168
+ dd
1169
+ code "@value"
1170
+ code "@disabled"
1171
+ code "@required"
1172
+ dt "Events"
1173
+ dd
1174
+ code "change"
1175
+ dt "Data"
1176
+ dd
1177
+ code "$disabled"
1178
+ code "$focused"
1179
+
1180
+ # ========================================================================
1181
+ # INPUT GROUP
1182
+ # ========================================================================
1183
+ .section id: "input-group"
1184
+ SectionHead tag: "input-group", name: "InputGroup", lines: 15
1185
+ .section-desc "Input with prefix/suffix addon elements."
1186
+ .demo-row.input-group-demo
1187
+ .demo-label "With prefix"
1188
+ InputGroup
1189
+ span $prefix: true, "$"
1190
+ input type: "text", placeholder: "Amount"
1191
+ .demo-row.input-group-demo
1192
+ .demo-label "With suffix"
1193
+ InputGroup
1194
+ input type: "text", placeholder: "Search..."
1195
+ button $suffix: true, "Go"
1196
+ .api
1197
+ dl
1198
+ dt "Props"
1199
+ dd
1200
+ code "@disabled"
1201
+ dt "Data"
1202
+ dd
1203
+ code "$disabled"
1204
+ code "$focused"
1205
+
1206
+ # ========================================================================
1207
+ # TABS
1208
+ # ========================================================================
1209
+ .section id: "tabs"
1210
+ SectionHead tag: "tabs", name: "Tabs", lines: 92
1211
+ .section-desc "Tab panel with roving tabindex and arrow key navigation."
1212
+ .demo-row
1213
+ .demo-label "Basic tabs"
1214
+ Tabs active <=> currentTab
1215
+ div data-tab: "overview", "Overview"
1216
+ div data-tab: "details", "Details"
1217
+ div data-tab: "settings", "Settings"
1218
+ div data-panel: "overview"
1219
+ p "This is the overview panel."
1220
+ div data-panel: "details"
1221
+ p "These are the details."
1222
+ div data-panel: "settings"
1223
+ p "Settings go here."
1224
+ .status "active: #{currentTab}"
1225
+ .api
1226
+ dl
1227
+ dt "Props"
1228
+ dd
1229
+ code "@active"
1230
+ code "@orientation"
1231
+ code "@activation"
1232
+ dt "Events"
1233
+ dd
1234
+ code "change"
1235
+ dt "Keyboard"
1236
+ dd
1237
+ kbd "← →"
1238
+ kbd "↕"
1239
+ kbd "Home"
1240
+ kbd "End"
1241
+ kbd "Enter"
1242
+ kbd "Space"
1243
+ dt "Data"
1244
+ dd
1245
+ code "$tab"
1246
+ code "$panel"
1247
+ code "$active"
1248
+ code "$disabled"
1249
+
1250
+ # ========================================================================
1251
+ # MENU
1252
+ # ========================================================================
1253
+ .section id: "menu"
1254
+ SectionHead tag: "menu", name: "Menu", lines: 124
1255
+ .section-desc "Dropdown action menu with keyboard navigation."
1256
+ .demo-row
1257
+ .demo-label "Click to open menu"
1258
+ Menu
1259
+ @select: (e) => console.log("Selected: #{e.detail}")
1260
+ span "Actions"
1261
+ div data-item: "edit", "Edit"
1262
+ div data-item: "duplicate", "Duplicate"
1263
+ div data-item: "archive", "Archive"
1264
+ div data-item: "delete", "Delete"
1265
+ .api
1266
+ dl
1267
+ dt "Props"
1268
+ dd
1269
+ code "@disabled"
1270
+ dt "Events"
1271
+ dd
1272
+ code "select"
1273
+ dt "Keyboard"
1274
+ dd
1275
+ kbd "↕"
1276
+ kbd "Home"
1277
+ kbd "End"
1278
+ kbd "Enter"
1279
+ kbd "Space"
1280
+ kbd "Esc"
1281
+ kbd "Tab"
1282
+ kbd "type-ahead"
1283
+ dt "Data"
1284
+ dd
1285
+ code "$open"
1286
+ code "$disabled"
1287
+ code "$highlighted"
1288
+ code "$value"
1289
+
1290
+ # ========================================================================
1291
+ # CONTEXT MENU
1292
+ # ========================================================================
1293
+ .section id: "context-menu"
1294
+ SectionHead tag: "context-menu", name: "ContextMenu", lines: 73
1295
+ .section-desc "Right-click the dashed area to open a context menu."
1296
+ ContextMenu
1297
+ div $trigger: true
1298
+ .context-zone
1299
+ "Right-click here"
1300
+ div $item: "cut", "Cut"
1301
+ div $item: "copy", "Copy"
1302
+ div $item: "paste", "Paste"
1303
+ .api
1304
+ dl
1305
+ dt "Props"
1306
+ dd
1307
+ code "@disabled"
1308
+ dt "Events"
1309
+ dd
1310
+ code "select"
1311
+ dt "Keyboard"
1312
+ dd
1313
+ kbd "↕"
1314
+ kbd "Home"
1315
+ kbd "End"
1316
+ kbd "Enter"
1317
+ kbd "Space"
1318
+ kbd "Esc"
1319
+ kbd "Tab"
1320
+ dt "Data"
1321
+ dd
1322
+ code "$open"
1323
+ code "$highlighted"
1324
+ code "$disabled"
1325
+ code "$value"
1326
+
1327
+ # ========================================================================
1328
+ # MENUBAR
1329
+ # ========================================================================
1330
+ .section id: "menubar"
1331
+ SectionHead tag: "menubar", name: "Menubar", lines: 137
1332
+ .section-desc "Horizontal menu bar with dropdown menus."
1333
+ .demo-row
1334
+ Menubar
1335
+ div $menu: "File"
1336
+ div $item: "new", "New"
1337
+ div $item: "open", "Open"
1338
+ div $item: "save", "Save"
1339
+ div $menu: "Edit"
1340
+ div $item: "undo", "Undo"
1341
+ div $item: "redo", "Redo"
1342
+ div $item: "cut", "Cut"
1343
+ div $menu: "View"
1344
+ div $item: "zoom-in", "Zoom In"
1345
+ div $item: "zoom-out", "Zoom Out"
1346
+ .api
1347
+ dl
1348
+ dt "Props"
1349
+ dd
1350
+ code "@disabled"
1351
+ dt "Events"
1352
+ dd
1353
+ code "select"
1354
+ dt "Keyboard"
1355
+ dd
1356
+ kbd "← →"
1357
+ kbd "↕"
1358
+ kbd "Enter"
1359
+ kbd "Space"
1360
+ kbd "Esc"
1361
+ kbd "Tab"
1362
+ dt "Data"
1363
+ dd
1364
+ code "$disabled"
1365
+ code "$open"
1366
+ code "$highlighted"
1367
+ code "$value"
1368
+
1369
+ # ========================================================================
1370
+ # NAVIGATION MENU
1371
+ # ========================================================================
1372
+ .section id: "nav-menu"
1373
+ SectionHead tag: "nav-menu", name: "NavigationMenu", lines: 114
1374
+ .section-desc "Site navigation with dropdown panels."
1375
+ .demo-row
1376
+ NavigationMenu
1377
+ a $link: true, href: "javascript:void(0)"
1378
+ "Home"
1379
+ div $trigger: "Products"
1380
+ div $panel: true
1381
+ a href: "javascript:void(0)", "Components"
1382
+ a href: "javascript:void(0)", "Templates"
1383
+ a href: "javascript:void(0)", "Playground"
1384
+ div $trigger: "Learn"
1385
+ div $panel: true
1386
+ a href: "javascript:void(0)", "Documentation"
1387
+ a href: "javascript:void(0)", "Blog"
1388
+ a href: "javascript:void(0)", "Changelog"
1389
+ a $link: true, href: "javascript:void(0)"
1390
+ "GitHub"
1391
+ .api
1392
+ dl
1393
+ dt "Props"
1394
+ dd
1395
+ code "@orientation"
1396
+ code "@hoverDelay"
1397
+ code "@hoverCloseDelay"
1398
+ dt "Keyboard"
1399
+ dd
1400
+ kbd "← →"
1401
+ kbd "↓"
1402
+ kbd "Esc"
1403
+ dt "Data"
1404
+ dd
1405
+ code "$orientation"
1406
+ code "$open"
1407
+
1408
+ # ========================================================================
1409
+ # TOOLBAR
1410
+ # ========================================================================
1411
+ .section id: "toolbar"
1412
+ SectionHead tag: "toolbar", name: "Toolbar", lines: 26
1413
+ .section-desc "Groups controls with roving tabindex keyboard navigation."
1414
+ .demo-row
1415
+ .demo-label "Text"
1416
+ Toolbar label: "Formatting"
1417
+ Button
1418
+ "Save"
1419
+ Button
1420
+ "Undo"
1421
+ Separator orientation: "vertical"
1422
+ Toggle pressed <=> isBold
1423
+ "Bold"
1424
+ Toggle pressed <=> isItalic
1425
+ "Italic"
1426
+ .demo-row
1427
+ .demo-label "Icons + Text"
1428
+ Toolbar label: "Formatting"
1429
+ Button
1430
+ "💾 Save"
1431
+ Button
1432
+ "↩ Undo"
1433
+ Separator orientation: "vertical"
1434
+ Toggle pressed <=> isBold
1435
+ "𝐁 Bold"
1436
+ Toggle pressed <=> isItalic
1437
+ "𝐼 Italic"
1438
+ .demo-row
1439
+ .demo-label "Icons Only"
1440
+ Toolbar label: "Formatting"
1441
+ Button aria-label: "Save"
1442
+ "💾"
1443
+ Button aria-label: "Undo"
1444
+ "↩"
1445
+ Separator orientation: "vertical"
1446
+ Toggle pressed <=> isBold, aria-label: "Bold"
1447
+ "𝐁"
1448
+ Toggle pressed <=> isItalic, aria-label: "Italic"
1449
+ "𝐼"
1450
+ .api
1451
+ dl
1452
+ dt "Props"
1453
+ dd
1454
+ code "@orientation"
1455
+ code "@label"
1456
+ dt "Keyboard"
1457
+ dd
1458
+ kbd "← →"
1459
+ kbd "↕"
1460
+ kbd "Home"
1461
+ kbd "End"
1462
+ dt "Data"
1463
+ dd
1464
+ code "$orientation"
1465
+
1466
+ # ========================================================================
1467
+ # BREADCRUMB
1468
+ # ========================================================================
1469
+ .section id: "breadcrumb"
1470
+ SectionHead tag: "breadcrumb", name: "Breadcrumb", lines: 32
1471
+ .section-desc "Navigation trail with separator and current page."
1472
+ .demo-row
1473
+ .demo-label "Basic breadcrumb"
1474
+ Breadcrumb
1475
+ a $item: true, href: "#breadcrumb", "Home"
1476
+ a $item: true, href: "#breadcrumb", "Products"
1477
+ a $item: true, href: "#breadcrumb", "Widgets"
1478
+ span $item: true, "Widget Pro"
1479
+ .api
1480
+ dl
1481
+ dt "Props"
1482
+ dd
1483
+ code "@separator"
1484
+ code "@label"
1485
+ dt "Data"
1486
+ dd
1487
+ code "$current"
1488
+
1489
+ # ========================================================================
1490
+ # DIALOG
1491
+ # ========================================================================
1492
+ .section id: "dialog"
1493
+ SectionHead tag: "dialog", name: "Dialog", lines: 94
1494
+ .section-desc "Modal with focus trap and scroll lock. Press Escape or click outside to dismiss."
1495
+ .demo-row
1496
+ .demo-label "Click to open"
1497
+ button.demo-btn @click: (=> showDialog = true)
1498
+ "Open Dialog"
1499
+ .status "open: #{showDialog}"
1500
+ Dialog open <=> showDialog, @close: (=> showDialog = false)
1501
+ .dialog-panel
1502
+ h2 style: "font-size:18px;font-weight:600;margin-bottom:8px", "Confirm Action"
1503
+ p.dialog-desc "Are you sure you want to proceed?"
1504
+ Popover placement: "bottom-start"
1505
+ button.demo-btn data-trigger: true
1506
+ "More options"
1507
+ div.floating-panel data-content: true
1508
+ p.floating-title "Dialog popover"
1509
+ p.floating-desc "Nested overlay: popover stays inside active modal flow."
1510
+ . style: "display:flex;gap:8px;justify-content:flex-end"
1511
+ button.demo-btn @click: (=> showDialog = false)
1512
+ "Cancel"
1513
+ button style: "padding:6px 16px;border:none;border-radius:6px;background:#1d4ed8;color:white;font-weight:600", @click: (=> showDialog = false)
1514
+ "Confirm"
1515
+ .api
1516
+ dl
1517
+ dt "Props"
1518
+ dd
1519
+ code "@open"
1520
+ code "@dismissable"
1521
+ code "@initialFocus"
1522
+ dt "Events"
1523
+ dd
1524
+ code "close"
1525
+ dt "Keyboard"
1526
+ dd
1527
+ kbd "Esc"
1528
+ kbd "Tab"
1529
+ dt "Data"
1530
+ dd
1531
+ code "$open"
1532
+
1533
+ # ========================================================================
1534
+ # ALERT DIALOG
1535
+ # ========================================================================
1536
+ .section id: "alert-dialog"
1537
+ SectionHead tag: "alert-dialog", name: "AlertDialog", lines: 83
1538
+ .section-desc "Non-dismissable modal — Escape and click-outside are blocked. User must choose an action."
1539
+ .demo-row
1540
+ .demo-label "Requires explicit action"
1541
+ button.demo-btn @click: (=> showAlertDialog = true)
1542
+ "Delete Account"
1543
+ .status "open: #{showAlertDialog}"
1544
+ AlertDialog open <=> showAlertDialog
1545
+ .dialog-panel
1546
+ h2 style: "font-size:18px;font-weight:600;margin-bottom:8px", "Delete Account?"
1547
+ p.dialog-desc "This action is permanent and cannot be undone."
1548
+ . style: "display:flex;gap:8px;justify-content:flex-end"
1549
+ button.demo-btn @click: (=> showAlertDialog = false)
1550
+ "Cancel"
1551
+ button style: "padding:6px 16px;border:none;border-radius:6px;background:#dc2626;color:white;font-weight:600", @click: (=> showAlertDialog = false)
1552
+ "Delete"
1553
+ .api
1554
+ dl
1555
+ dt "Props"
1556
+ dd
1557
+ code "@open"
1558
+ code "@initialFocus"
1559
+ dt "Events"
1560
+ dd
1561
+ code "close"
1562
+ dt "Keyboard"
1563
+ dd
1564
+ kbd "Tab"
1565
+ dt "Data"
1566
+ dd
1567
+ code "$open"
1568
+
1569
+ # ========================================================================
1570
+ # DRAWER
1571
+ # ========================================================================
1572
+ .section id: "drawer"
1573
+ SectionHead tag: "drawer", name: "Drawer", lines: 68
1574
+ .section-desc "Slide-out panel from edge of screen with focus trap and scroll lock."
1575
+ .demo-row
1576
+ .demo-label "Open from right"
1577
+ .btn-demo
1578
+ .btn-row
1579
+ button @click: (=> showDrawer = true)
1580
+ "Open Drawer"
1581
+ Drawer open <=> showDrawer, side: "right"
1582
+ .drawer-panel
1583
+ h2 "Settings"
1584
+ p "This is a drawer panel that slides in from the right."
1585
+ p "Press Escape or click outside to close."
1586
+ button @click: (=> showDrawer = false)
1587
+ "Close"
1588
+ .api
1589
+ dl
1590
+ dt "Props"
1591
+ dd
1592
+ code "@open"
1593
+ code "@side"
1594
+ code "@dismissable"
1595
+ dt "Events"
1596
+ dd
1597
+ code "close"
1598
+ dt "Keyboard"
1599
+ dd
1600
+ kbd "Esc"
1601
+ kbd "Tab"
1602
+ dt "Data"
1603
+ dd
1604
+ code "$open"
1605
+ code "$side"
1606
+
1607
+ # ========================================================================
1608
+ # POPOVER
1609
+ # ========================================================================
1610
+ .section id: "popover"
1611
+ SectionHead tag: "popover", name: "Popover", lines: 135
1612
+ .section-desc "Anchored floating content with flip/shift positioning."
1613
+ .demo-row
1614
+ .demo-label "Click to toggle"
1615
+ Popover placement: "bottom-start"
1616
+ button.demo-btn data-trigger: true
1617
+ "Open Popover"
1618
+ div.floating-panel data-content: true
1619
+ p.floating-title "Popover content"
1620
+ p.floating-desc "Click outside or press Escape to close."
1621
+ .api
1622
+ dl
1623
+ dt "Props"
1624
+ dd
1625
+ code "@placement"
1626
+ code "@offset"
1627
+ code "@disabled"
1628
+ code "@openOnHover"
1629
+ code "@hoverDelay"
1630
+ code "@hoverCloseDelay"
1631
+ dt "Keyboard"
1632
+ dd
1633
+ kbd "Esc"
1634
+ kbd "Enter"
1635
+ kbd "Space"
1636
+ kbd "↓"
1637
+ dt "Data"
1638
+ dd
1639
+ code "$open"
1640
+ code "$placement"
1641
+
1642
+ # ========================================================================
1643
+ # TOOLTIP
1644
+ # ========================================================================
1645
+ .section id: "tooltip"
1646
+ SectionHead tag: "tooltip", name: "Tooltip", lines: 103
1647
+ .section-desc "Hover/focus tooltip with delay and positioning."
1648
+ .demo-row
1649
+ .demo-label "Hover the buttons"
1650
+ . style: "display:flex;gap:12px"
1651
+ Tooltip text: "Save your changes", placement: "top"
1652
+ button.demo-btn
1653
+ "Save (top)"
1654
+ Tooltip text: "Delete this item", placement: "bottom"
1655
+ button.demo-btn
1656
+ "Delete (bottom)"
1657
+ .api
1658
+ dl
1659
+ dt "Props"
1660
+ dd
1661
+ code "@text"
1662
+ code "@placement"
1663
+ code "@delay"
1664
+ code "@offset"
1665
+ code "@hoverable"
1666
+ dt "Data"
1667
+ dd
1668
+ code "$open"
1669
+ code "$entering"
1670
+ code "$exiting"
1671
+ code "$placement"
1672
+
1673
+ # ========================================================================
1674
+ # PREVIEW CARD
1675
+ # ========================================================================
1676
+ .section id: "preview-card"
1677
+ SectionHead tag: "preview-card", name: "PreviewCard", lines: 63
1678
+ .section-desc "Hover or focus the link to see a preview card."
1679
+ .demo-row
1680
+ PreviewCard delay: 300
1681
+ a $trigger: true, href: "#preview-card", @click: ((e) => e.preventDefault())
1682
+ "Hover me for preview"
1683
+ div $content: true, hidden: true
1684
+ .floating-panel style: "width:240px"
1685
+ p.floating-title "Preview Card"
1686
+ p.floating-desc "This content appears on hover or focus. Useful for link previews."
1687
+ .api
1688
+ dl
1689
+ dt "Props"
1690
+ dd
1691
+ code "@delay"
1692
+ code "@closeDelay"
1693
+ dt "Data"
1694
+ dd
1695
+ code "$open"
1696
+
1697
+ # ========================================================================
1698
+ # TOAST
1699
+ # ========================================================================
1700
+ .section id: "toast"
1701
+ SectionHead tag: "toast", name: "Toast", lines: 66
1702
+ .section-desc "Auto-dismiss notification with ARIA live region."
1703
+ .demo-row
1704
+ .demo-label "Click to show toasts"
1705
+ button.demo-btn @click: (=> toasts = [...toasts, { message: 'Hello!', type: 'info' }])
1706
+ "Info Toast"
1707
+ button.demo-btn style: "margin-left:8px", @click: (=> toasts = [...toasts, { message: 'Saved!', type: 'success' }])
1708
+ "Success Toast"
1709
+ .toast-wrap
1710
+ ToastViewport toasts <=> toasts
1711
+ .api
1712
+ dl
1713
+ dt "Props"
1714
+ dd
1715
+ code "@toasts"
1716
+ code "@toast"
1717
+ code "@placement"
1718
+ dt "Events"
1719
+ dd
1720
+ code "dismiss"
1721
+ dt "Data"
1722
+ dd
1723
+ code "$placement"
1724
+ code "$type"
1725
+ code "$leaving"
1726
+
1727
+ # ========================================================================
1728
+ # BUTTON
1729
+ # ========================================================================
1730
+ .section id: "button"
1731
+ SectionHead tag: "button", name: "Button", lines: 12
1732
+ .section-desc "Headless button with disabled-but-focusable pattern."
1733
+ .demo-row.btn-demo
1734
+ .demo-label "Normal, primary, and disabled"
1735
+ .btn-row
1736
+ Button @press: (=> p "Pressed!")
1737
+ "Click Me"
1738
+ Button @press: (=> p "Saved!")
1739
+ "Save"
1740
+ Button disabled: true
1741
+ "Disabled"
1742
+ .api
1743
+ dl
1744
+ dt "Props"
1745
+ dd
1746
+ code "@disabled"
1747
+ dt "Events"
1748
+ dd
1749
+ code "press"
1750
+ dt "Data"
1751
+ dd
1752
+ code "$disabled"
1753
+
1754
+ # ========================================================================
1755
+ # BADGE
1756
+ # ========================================================================
1757
+ .section id: "badge"
1758
+ SectionHead tag: "badge", name: "Badge", lines: 7
1759
+ .section-desc "Inline label with variant styles."
1760
+ .demo-row
1761
+ .demo-label "Variants"
1762
+ . style: "display:flex;gap:8px;align-items:center"
1763
+ Badge "Solid"
1764
+ Badge variant: "outline", "Outline"
1765
+ Badge variant: "subtle", "Subtle"
1766
+ .api
1767
+ dl
1768
+ dt "Props"
1769
+ dd
1770
+ code "@variant"
1771
+ dt "Data"
1772
+ dd
1773
+ code "$variant"
1774
+
1775
+ # ========================================================================
1776
+ # CARD
1777
+ # ========================================================================
1778
+ .section id: "card"
1779
+ SectionHead tag: "card", name: "Card", lines: 7
1780
+ .section-desc "Structured container with header, content, and footer."
1781
+ .demo-row.card-demo
1782
+ Card
1783
+ div $header: true
1784
+ h3 "Card Title"
1785
+ div $content: true
1786
+ "This is the card body. Cards can contain any content."
1787
+ div $footer: true
1788
+ button.demo-btn @click: (=> toasts = [...toasts, { message: 'Card action clicked!', type: 'success' }]), "Action"
1789
+ .api
1790
+ dl
1791
+ dt "Props"
1792
+ dd
1793
+ code "@interactive"
1794
+ dt "Data"
1795
+ dd
1796
+ code "$interactive"
1797
+
1798
+ # ========================================================================
1799
+ # SEPARATOR
1800
+ # ========================================================================
1801
+ .section id: "separator"
1802
+ SectionHead tag: "separator", name: "Separator", lines: 8
1803
+ .section-desc "Decorative or semantic divider between sections."
1804
+ .demo-row
1805
+ .demo-label "Horizontal (default)"
1806
+ div style: "width:100%"
1807
+ p "Above the separator"
1808
+ Separator
1809
+ p "Below the separator"
1810
+ .demo-row
1811
+ .demo-label "Vertical"
1812
+ div style: "display:flex;align-items:center;gap:12px;height:32px"
1813
+ span "Left"
1814
+ Separator orientation: "vertical"
1815
+ span "Right"
1816
+ .api
1817
+ dl
1818
+ dt "Props"
1819
+ dd
1820
+ code "@orientation"
1821
+ code "@decorative"
1822
+ dt "Data"
1823
+ dd
1824
+ code "$orientation"
1825
+
1826
+ # ========================================================================
1827
+ # PROGRESS
1828
+ # ========================================================================
1829
+ .section id: "progress"
1830
+ SectionHead tag: "progress", name: "Progress", lines: 16
1831
+ .section-desc "Progress bar with value exposed as CSS custom property."
1832
+ .demo-row
1833
+ .demo-label "65% complete"
1834
+ Progress value: progressVal
1835
+ div.progress-track
1836
+ div.progress-fill style: "width: var(--progress-percent)"
1837
+ .api
1838
+ dl
1839
+ dt "Props"
1840
+ dd
1841
+ code "@value"
1842
+ code "@max"
1843
+ code "@label"
1844
+ dt "Data"
1845
+ dd
1846
+ code "$complete"
1847
+
1848
+ # ========================================================================
1849
+ # METER
1850
+ # ========================================================================
1851
+ .section id: "meter"
1852
+ SectionHead tag: "meter", name: "Meter", lines: 26
1853
+ .section-desc "Gauge for known-range measurements with low/high/optimum thresholds."
1854
+ .demo-row
1855
+ .demo-label "Score: 72/100"
1856
+ Meter value: meterVal, min: 0, max: 100, low: 30, high: 80, optimum: 60
1857
+ .api
1858
+ dl
1859
+ dt "Props"
1860
+ dd
1861
+ code "@value"
1862
+ code "@min"
1863
+ code "@max"
1864
+ code "@low"
1865
+ code "@high"
1866
+ code "@optimum"
1867
+ code "@label"
1868
+ dt "Data"
1869
+ dd
1870
+ code "$level"
1871
+
1872
+ # ========================================================================
1873
+ # SPINNER
1874
+ # ========================================================================
1875
+ .section id: "spinner"
1876
+ SectionHead tag: "spinner", name: "Spinner", lines: 8
1877
+ .section-desc "Loading indicator with accessible status."
1878
+ .demo-row.spinner-demo
1879
+ .demo-label "Default"
1880
+ . style: "display:flex;gap:16px;align-items:center"
1881
+ Spinner
1882
+ Spinner size: "32px"
1883
+ Spinner label: "Saving...", size: "20px"
1884
+ .api
1885
+ dl
1886
+ dt "Props"
1887
+ dd
1888
+ code "@label"
1889
+ code "@size"
1890
+
1891
+ # ========================================================================
1892
+ # SKELETON
1893
+ # ========================================================================
1894
+ .section id: "skeleton"
1895
+ SectionHead tag: "skeleton", name: "Skeleton", lines: 11
1896
+ .section-desc "Loading placeholder with shimmer animation."
1897
+ .demo-row
1898
+ .demo-label "Shapes"
1899
+ . style: "display:flex;gap:12px;align-items:center"
1900
+ Skeleton circle: true, width: "40px", height: "40px"
1901
+ . style: "display:flex;flex-direction:column;gap:6px"
1902
+ Skeleton width: "200px", height: "14px"
1903
+ Skeleton width: "150px", height: "14px"
1904
+ .api
1905
+ dl
1906
+ dt "Props"
1907
+ dd
1908
+ code "@width"
1909
+ code "@height"
1910
+ code "@circle"
1911
+ code "@label"
1912
+ dt "Data"
1913
+ dd
1914
+ code "$circle"
1915
+
1916
+ # ========================================================================
1917
+ # AVATAR
1918
+ # ========================================================================
1919
+ .section id: "avatar"
1920
+ SectionHead tag: "avatar", name: "Avatar", lines: 27
1921
+ .section-desc "Image with fallback to initials or placeholder."
1922
+ .demo-row
1923
+ .demo-label "With image"
1924
+ Avatar src: "https://i.pravatar.cc/80?u=linda", alt: "Alice Chen"
1925
+ .demo-row
1926
+ .demo-label "Initials fallback"
1927
+ Avatar alt: "Bob Park", fallback: "BP"
1928
+ .demo-row
1929
+ .demo-label "Placeholder"
1930
+ Avatar
1931
+ .api
1932
+ dl
1933
+ dt "Props"
1934
+ dd
1935
+ code "@src"
1936
+ code "@alt"
1937
+ code "@fallback"
1938
+ dt "Data"
1939
+ dd
1940
+ code "$status"
1941
+ code "$initials"
1942
+ code "$placeholder"
1943
+
1944
+ # ========================================================================
1945
+ # LABEL
1946
+ # ========================================================================
1947
+ .section id: "label"
1948
+ SectionHead tag: "label", name: "Label", lines: 8
1949
+ .section-desc "Standalone accessible form label."
1950
+ .demo-row
1951
+ .demo-label "With required indicator"
1952
+ Label required: true
1953
+ "Email address"
1954
+ .api
1955
+ dl
1956
+ dt "Props"
1957
+ dd
1958
+ code "@for"
1959
+ code "@required"
1960
+ dt "Data"
1961
+ dd
1962
+ code "$required"
1963
+
1964
+ # ========================================================================
1965
+ # SCROLL AREA
1966
+ # ========================================================================
1967
+ .section id: "scroll-area"
1968
+ SectionHead tag: "scroll-area", name: "ScrollArea", lines: 133
1969
+ .section-desc "Custom scrollbar with draggable thumb and auto-hide."
1970
+ .demo-row
1971
+ .demo-label "Scrollable content"
1972
+ .scroll-demo
1973
+ ScrollArea
1974
+ div style: "padding: 4px 12px"
1975
+ p "Item 1 — Lorem ipsum dolor sit amet, consectetur adipiscing elit."
1976
+ p "Item 2 — Sed do eiusmod tempor incididunt ut labore et dolore."
1977
+ p "Item 3 — Ut enim ad minim veniam, quis nostrud exercitation."
1978
+ p "Item 4 — Duis aute irure dolor in reprehenderit in voluptate."
1979
+ p "Item 5 — Excepteur sint occaecat cupidatat non proident."
1980
+ p "Item 6 — Sunt in culpa qui officia deserunt mollit anim id."
1981
+ p "Item 7 — Lorem ipsum dolor sit amet, consectetur adipiscing."
1982
+ p "Item 8 — Sed do eiusmod tempor incididunt ut labore et dolore."
1983
+ p "Item 9 — Ut enim ad minim veniam, quis nostrud exercitation."
1984
+ p "Item 10 — Duis aute irure dolor in reprehenderit in voluptate."
1985
+ p "Item 11 — Excepteur sint occaecat cupidatat non proident."
1986
+ p "Item 12 — Sunt in culpa qui officia deserunt mollit anim id."
1987
+ p "Item 13 — Lorem ipsum dolor sit amet, consectetur adipiscing."
1988
+ p "Item 14 — Sed do eiusmod tempor incididunt ut labore et dolore."
1989
+ p "Item 15 — Ut enim ad minim veniam, quis nostrud exercitation."
1990
+ .api
1991
+ dl
1992
+ dt "Props"
1993
+ dd
1994
+ code "@orientation"
1995
+ dt "Data"
1996
+ dd
1997
+ code "$orientation"
1998
+ code "$hovering"
1999
+ code "$scrolling"
2000
+ code "$dragging"
2001
+ code "$viewport"
2002
+ code "$scrollbar"
2003
+ code "$thumb"
2004
+
2005
+ # ========================================================================
2006
+ # FIELD + FIELDSET
2007
+ # ========================================================================
2008
+ .section id: "field"
2009
+ SectionHead tag: "field", name: "Field + Fieldset", lines: 44
2010
+ .section-desc "Form field wrapper with label, description, and error."
2011
+ .demo-row
2012
+ Field label: "Email", description: "We'll never share your email.", required: true, error: fieldError
2013
+ input type: "email", placeholder: "you@example.com", value: fieldEmail
2014
+ @input: (e) =>
2015
+ fieldEmail = e.target.value
2016
+ fieldError = if fieldEmail and not fieldEmail.includes('@') then 'Invalid email address' else ''
2017
+ .demo-row
2018
+ Fieldset legend: "Account Info"
2019
+ Field label: "Username"
2020
+ input type: "text", placeholder: "username"
2021
+ Field label: "Password"
2022
+ input type: "password", placeholder: "password"
2023
+ .api
2024
+ dl
2025
+ dt "Field"
2026
+ dd
2027
+ code "@label"
2028
+ code "@description"
2029
+ code "@error"
2030
+ code "@disabled"
2031
+ code "@required"
2032
+ dt "Fieldset"
2033
+ dd
2034
+ code "@legend"
2035
+ code "@disabled"
2036
+ dt "Data"
2037
+ dd
2038
+ code "$disabled"
2039
+ code "$invalid"
2040
+ code "$label"
2041
+ code "$required"
2042
+ code "$description"
2043
+ code "$error"
2044
+ code "$legend"
2045
+
2046
+ # ========================================================================
2047
+ # FORM
2048
+ # ========================================================================
2049
+ .section id: "form"
2050
+ SectionHead tag: "form", name: "Form", lines: 24
2051
+ .section-desc "Form wrapper with submit handling and validation state."
2052
+ .demo-row
2053
+ Form
2054
+ Field label: "Name"
2055
+ input type: "text", placeholder: "Your name"
2056
+ Field label: "Email"
2057
+ input type: "email", placeholder: "you@example.com"
2058
+ button type: "submit"
2059
+ "Submit"
2060
+ .api
2061
+ dl
2062
+ dt "Props"
2063
+ dd
2064
+ code "@disabled"
2065
+ dt "Events"
2066
+ dd
2067
+ code "submit"
2068
+ dt "Data"
2069
+ dd
2070
+ code "$disabled"
2071
+ code "$submitting"
2072
+ code "$submitted"
2073
+
2074
+ # ========================================================================
2075
+ # BUTTON GROUP
2076
+ # ========================================================================
2077
+ .section id: "button-group"
2078
+ SectionHead tag: "button-group", name: "ButtonGroup", lines: 11
2079
+ .section-desc "Groups related buttons with ARIA group semantics."
2080
+ .demo-row
2081
+ .demo-label "Horizontal"
2082
+ ButtonGroup label: "Actions"
2083
+ button "Cut"
2084
+ button "Copy"
2085
+ button "Paste"
2086
+ .api
2087
+ dl
2088
+ dt "Props"
2089
+ dd
2090
+ code "@orientation"
2091
+ code "@disabled"
2092
+ code "@label"
2093
+ dt "Data"
2094
+ dd
2095
+ code "$orientation"
2096
+ code "$disabled"
2097
+
2098
+ # ========================================================================
2099
+ # GRID
2100
+ # ========================================================================
2101
+ .section id: "grid"
2102
+ SectionHead tag: "grid", name: "Grid", lines: 857
2103
+ .section-desc "Virtual-scrolling data grid. Click to select, arrows to navigate, double-click to edit."
2104
+ .demo-row
2105
+ . style: "display:flex;justify-content:space-between;align-items:baseline;margin-bottom:8px"
2106
+ .demo-label style: "margin-bottom:0", "#{gridData.length} rows, #{gridColumns.length} columns"
2107
+ span.grid-cell-ref gridCellRef
2108
+ Grid data: gridData, columns: gridColumns, striped: true
2109
+ . style: "margin-top:12px;display:flex;gap:8px;align-items:center"
2110
+ button.demo-btn @click: (=> @_loadGridRows(1000))
2111
+ "1K rows"
2112
+ button.demo-btn @click: (=> @_loadGridRows(10000))
2113
+ "10K rows"
2114
+ button.demo-btn @click: (=> @_loadGridRows(100000))
2115
+ "100K rows"
2116
+ .api
2117
+ dl
2118
+ dt "Props"
2119
+ dd
2120
+ code "@data"
2121
+ code "@columns"
2122
+ code "@rowHeight"
2123
+ code "@overscan"
2124
+ code "@striped"
2125
+ code "@beforeEdit"
2126
+ code "@afterEdit"
2127
+ dt "Keyboard"
2128
+ dd
2129
+ kbd "Arrows"
2130
+ kbd "Tab"
2131
+ kbd "Enter"
2132
+ kbd "F2"
2133
+ kbd "Esc"
2134
+ kbd "Home"
2135
+ kbd "End"
2136
+ kbd "PgUp"
2137
+ kbd "PgDn"
2138
+ kbd "Ctrl+A"
2139
+ kbd "Ctrl+C"
2140
+ kbd "Ctrl+V"
2141
+ kbd "Ctrl+X"
2142
+ dt "Data"
2143
+ dd
2144
+ code "$editing"
2145
+ code "$selecting"
2146
+ code "$sorted"
2147
+
2148
+ # ========================================================================
2149
+ # ACCORDION
2150
+ # ========================================================================
2151
+ .section id: "accordion"
2152
+ SectionHead tag: "accordion", name: "Accordion", lines: 87
2153
+ .section-desc "Expand/collapse sections, single or multiple mode."
2154
+ .demo-row
2155
+ .demo-label "Single mode"
2156
+ Accordion
2157
+ div data-item: "a"
2158
+ button.accordion-trigger data-trigger: true
2159
+ "Section A"
2160
+ div.accordion-content data-content: true
2161
+ p "Content for section A."
2162
+ div data-item: "b"
2163
+ button.accordion-trigger data-trigger: true
2164
+ "Section B"
2165
+ div.accordion-content data-content: true
2166
+ p "Content for section B."
2167
+ div data-item: "c"
2168
+ button.accordion-trigger data-trigger: true
2169
+ "Section C"
2170
+ div.accordion-content data-content: true
2171
+ p "Content for section C."
2172
+ .api
2173
+ dl
2174
+ dt "Props"
2175
+ dd
2176
+ code "@multiple"
2177
+ dt "Events"
2178
+ dd
2179
+ code "change"
2180
+ dt "Keyboard"
2181
+ dd
2182
+ kbd "Enter"
2183
+ kbd "Space"
2184
+ kbd "↕"
2185
+ kbd "Home"
2186
+ kbd "End"
2187
+ dt "Data"
2188
+ dd
2189
+ code "$item"
2190
+ code "$trigger"
2191
+ code "$content"
2192
+ code "$open"
2193
+ code "$disabled"
2194
+
2195
+ # ========================================================================
2196
+ # TABLE
2197
+ # ========================================================================
2198
+ .section id: "table"
2199
+ SectionHead tag: "table", name: "Table", lines: 11
2200
+ .section-desc "Semantic table wrapper. For data-heavy tables, use Grid."
2201
+ .demo-row.table-demo
2202
+ Table caption: "Team", striped: true
2203
+ thead
2204
+ tr
2205
+ th "Name"
2206
+ th "Role"
2207
+ th "City"
2208
+ tbody
2209
+ tr
2210
+ td "Alice"
2211
+ td "Engineer"
2212
+ td "Seattle"
2213
+ tr
2214
+ td "Bob"
2215
+ td "Designer"
2216
+ td "Portland"
2217
+ tr
2218
+ td "Carol"
2219
+ td "Manager"
2220
+ td "Denver"
2221
+ .api
2222
+ dl
2223
+ dt "Props"
2224
+ dd
2225
+ code "@caption"
2226
+ code "@striped"
2227
+ dt "Data"
2228
+ dd
2229
+ code "$striped"
2230
+
2231
+ # ========================================================================
2232
+ # COLLAPSIBLE
2233
+ # ========================================================================
2234
+ .section id: "collapsible"
2235
+ SectionHead tag: "collapsible", name: "Collapsible", lines: 39
2236
+ .section-desc "Single open/close section. Simpler than Accordion."
2237
+ .demo-row.collapsible-demo
2238
+ Collapsible open <=> collapsibleOpen
2239
+ button $trigger: true
2240
+ span style: "font-size:10px;margin-right:6px"
2241
+ if collapsibleOpen then "▼" else "▶"
2242
+ "Show details"
2243
+ div $content: true
2244
+ p "These are the collapsible details. Click the trigger or press Enter/Space to toggle."
2245
+ .status "open: #{collapsibleOpen}"
2246
+ .api
2247
+ dl
2248
+ dt "Props"
2249
+ dd
2250
+ code "@open"
2251
+ code "@disabled"
2252
+ dt "Events"
2253
+ dd
2254
+ code "change"
2255
+ dt "Keyboard"
2256
+ dd
2257
+ kbd "Enter"
2258
+ kbd "Space"
2259
+ dt "Data"
2260
+ dd
2261
+ code "$open"
2262
+ code "$disabled"
2263
+
2264
+ # ========================================================================
2265
+ # PAGINATION
2266
+ # ========================================================================
2267
+ .section id: "pagination"
2268
+ SectionHead tag: "pagination", name: "Pagination", lines: 113
2269
+ .section-desc "Page navigation with prev/next buttons and ellipsis gaps."
2270
+ .demo-row
2271
+ .demo-label "10 pages"
2272
+ Pagination page <=> currentPage, total: 100, perPage: 10
2273
+ .status "page: #{currentPage}"
2274
+ .api
2275
+ dl
2276
+ dt "Props"
2277
+ dd
2278
+ code "@page"
2279
+ code "@total"
2280
+ code "@perPage"
2281
+ code "@siblingCount"
2282
+ dt "Events"
2283
+ dd
2284
+ code "change"
2285
+ dt "Keyboard"
2286
+ dd
2287
+ kbd "← →"
2288
+ kbd "Home"
2289
+ kbd "End"
2290
+ dt "Data"
2291
+ dd
2292
+ code "$active"
2293
+ code "$disabled"
2294
+ code "$ellipsis"
2295
+
2296
+ # ========================================================================
2297
+ # CAROUSEL
2298
+ # ========================================================================
2299
+ .section id: "carousel"
2300
+ SectionHead tag: "carousel", name: "Carousel", lines: 89
2301
+ .section-desc "Slide carousel with loop and keyboard navigation."
2302
+ .demo-row
2303
+ Carousel loop: true
2304
+ div $slide: true
2305
+ "🍎 Slide 1 — Apples"
2306
+ div $slide: true
2307
+ "🍌 Slide 2 — Bananas"
2308
+ div $slide: true
2309
+ "🍒 Slide 3 — Cherries"
2310
+ .api
2311
+ dl
2312
+ dt "Props"
2313
+ dd
2314
+ code "@orientation"
2315
+ code "@loop"
2316
+ code "@autoplay"
2317
+ code "@interval"
2318
+ code "@label"
2319
+ dt "Events"
2320
+ dd
2321
+ code "change"
2322
+ dt "Keyboard"
2323
+ dd
2324
+ kbd "← →"
2325
+ kbd "↕"
2326
+ kbd "Home"
2327
+ kbd "End"
2328
+ dt "Data"
2329
+ dd
2330
+ code "$orientation"
2331
+ code "$active"
2332
+ code "$prev"
2333
+ code "$next"
2334
+
2335
+ # ========================================================================
2336
+ # RESIZABLE
2337
+ # ========================================================================
2338
+ .section id: "resizable"
2339
+ SectionHead tag: "resizable", name: "Resizable", lines: 104
2340
+ .section-desc "Draggable resize handles between panels."
2341
+ .demo-row.resizable-demo
2342
+ Resizable
2343
+ div $panel: true, "Left"
2344
+ div $handle: true
2345
+ div $panel: true, "Right"
2346
+ .api
2347
+ dl
2348
+ dt "Props"
2349
+ dd
2350
+ code "@orientation"
2351
+ code "@minSize"
2352
+ code "@maxSize"
2353
+ dt "Events"
2354
+ dd
2355
+ code "resize"
2356
+ dt "Keyboard"
2357
+ dd
2358
+ kbd "← →"
2359
+ kbd "↕"
2360
+ dt "Data"
2361
+ dd
2362
+ code "$orientation"
2363
+ code "$dragging"
2364
+
2365
+ # ========================================================================
2366
+ # SOURCE CODE VIEWER
2367
+ # ========================================================================
2368
+ if sourceCode
2369
+ .source-overlay @click: (=> @_closeSource())
2370
+ .source-modal @click: @_stopProp
2371
+ .source-header
2372
+ .source-title
2373
+ span.source-name sourceName
2374
+ span.source-badge "#{sourceLines} lines"
2375
+ button.source-close @click: (=> @_closeSource())
2376
+ "×"
2377
+ .source-body
2378
+ pre.source-pre
2379
+ span.source-gutter
2380
+ code.source-code
2381
+
2382
+ </script>
2383
+
2384
+ </body>
2385
+ </html>