rip-lang 3.13.65 → 3.13.67

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