monochrome 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts DELETED
@@ -1,561 +0,0 @@
1
- enum Focus {
2
- Trigger,
3
- First,
4
- Last,
5
- None,
6
- }
7
-
8
- enum Prefix {
9
- Trigger = "mct:",
10
- TriggerAccordion = "mct:a",
11
- TriggerCollapsible = "mct:c",
12
- TriggerMenu = "mct:m",
13
- TriggerTabs = "mct:t",
14
- Content = "mcc:",
15
- ContentMenu = "mcc:m",
16
- RootAccordion = "mcr:a",
17
- }
18
-
19
- if (typeof document !== "undefined") {
20
- let shouldPreventDefault: boolean | null = null
21
- let shouldMatchLetter: string | null = null
22
- let shouldResetRadio: HTMLElement | null = null
23
- let radioHeadDone: boolean | null = null
24
- let radioTailChain: HTMLElement[] = []
25
- const menuPopovers: HTMLElement[] = []
26
- let rovingBoundary: Element | null = null
27
- let safeGroup: HTMLElement | null = null
28
- let safeRect: DOMRect | null = null
29
- let safeDir = 0
30
-
31
- type RovingNavigator = (origin: Element | null | undefined) => HTMLElement | null
32
- type RovingFocusCallback = (
33
- node: Element | null | undefined,
34
- fallback: RovingNavigator,
35
- ) => HTMLElement | null
36
- type Roving = (focus: RovingFocusCallback) => [RovingNavigator, RovingNavigator]
37
-
38
- const isElement = (el: unknown): el is HTMLElement => el instanceof HTMLElement
39
-
40
- const isTrigger = (el: unknown, prefix?: string): el is HTMLButtonElement =>
41
- el instanceof HTMLButtonElement && (!prefix || el.id.startsWith(prefix))
42
-
43
- const isMenuItem = (el: unknown): el is HTMLElement =>
44
- isElement(el) && el.role?.startsWith("menuitem") === true && el.ariaDisabled !== "true"
45
-
46
- const getContent = (el: HTMLElement): HTMLElement | null =>
47
- document.getElementById(el.getAttribute("aria-controls") || "")
48
-
49
- const findAncestor = (el: HTMLElement | null, prefix: string): HTMLElement | null => {
50
- while (el) {
51
- if (el.id.startsWith(prefix)) return el
52
- el = el.parentElement
53
- }
54
- return null
55
- }
56
-
57
- const roving: Roving = (focus) => {
58
- const next: RovingNavigator = (origin) =>
59
- origin
60
- ? focus(origin.nextElementSibling || origin.parentElement?.firstElementChild, next)
61
- : null
62
- const previous: RovingNavigator = (origin) =>
63
- origin
64
- ? focus(origin.previousElementSibling || origin.parentElement?.lastElementChild, previous)
65
- : null
66
- return [next, previous]
67
- }
68
-
69
- const menuRoving: RovingFocusCallback = (element, fallback) => {
70
- if (isElement(element)) {
71
- const menuitem = element.firstElementChild
72
- if (shouldResetRadio) {
73
- if (isElement(menuitem)) {
74
- if (menuitem === shouldResetRadio) {
75
- for (const item of radioTailChain) item.ariaChecked = "false"
76
- return menuitem
77
- }
78
- if (menuitem.role === "menuitemradio") {
79
- if (!radioHeadDone) {
80
- menuitem.ariaChecked = "false"
81
- } else {
82
- radioTailChain.push(menuitem)
83
- }
84
- return fallback(element)
85
- }
86
- }
87
- radioHeadDone = true
88
- radioTailChain = []
89
- return fallback(element)
90
- }
91
- if (
92
- isMenuItem(menuitem) &&
93
- (!shouldMatchLetter || menuitem.textContent?.toLowerCase().startsWith(shouldMatchLetter))
94
- ) {
95
- menuitem.focus()
96
- shouldPreventDefault = true
97
- return menuitem
98
- } else if (rovingBoundary !== element) {
99
- if (!rovingBoundary) rovingBoundary = element
100
- return fallback(element)
101
- } else {
102
- rovingBoundary = null
103
- }
104
- }
105
- return null
106
- }
107
-
108
- const accordionRoving: RovingFocusCallback = (node, fallback) => {
109
- if (isElement(node)) {
110
- if (rovingBoundary === node) return null
111
- if (!rovingBoundary) rovingBoundary = node
112
- const trigger = node.firstElementChild?.firstElementChild
113
- if (isTrigger(trigger, Prefix.TriggerAccordion)) {
114
- if (trigger.ariaDisabled === "true") return fallback(node)
115
- shouldPreventDefault = true
116
- trigger.focus()
117
- return trigger
118
- }
119
- }
120
- return fallback(node)
121
- }
122
-
123
- const tabsRoving: RovingFocusCallback = (node, fallback) => {
124
- if (isTrigger(node, Prefix.TriggerTabs)) {
125
- if (rovingBoundary === node) return null
126
- if (!rovingBoundary) rovingBoundary = node
127
- if (node.ariaDisabled === "true") return fallback(node)
128
- shouldPreventDefault = true
129
- node.focus()
130
- return node
131
- } else {
132
- return fallback(node)
133
- }
134
- }
135
-
136
- const [menuNext, menuPrevious] = roving(menuRoving)
137
- const [accordionNext, accordionPrevious] = roving(accordionRoving)
138
- const [tabNext, tabPrevious] = roving(tabsRoving)
139
-
140
- const collapsible = (trigger: HTMLElement) => {
141
- const content = getContent(trigger)
142
- if (content) {
143
- const isOpen = trigger.ariaExpanded !== "true"
144
- trigger.ariaExpanded = isOpen ? "true" : "false"
145
- content.ariaHidden = isOpen ? "false" : "true"
146
- isOpen ? content.removeAttribute("hidden") : content.setAttribute("hidden", "until-found")
147
- }
148
- }
149
-
150
- const accordion = (trigger: HTMLElement) => {
151
- if (trigger.ariaDisabled === "true") return
152
- if (trigger.ariaExpanded === "true") {
153
- collapsible(trigger)
154
- } else {
155
- const root = findAncestor(trigger, Prefix.RootAccordion)
156
- if (!root || root.getAttribute("data-mode") !== "single") {
157
- collapsible(trigger)
158
- } else {
159
- let item = root.firstElementChild
160
- while (item) {
161
- const itemTrigger = item.firstElementChild?.firstElementChild
162
- if (
163
- isElement(itemTrigger) &&
164
- (itemTrigger === trigger || itemTrigger.ariaExpanded === "true")
165
- ) {
166
- collapsible(itemTrigger)
167
- }
168
- item = item.nextElementSibling
169
- }
170
- }
171
- }
172
- }
173
-
174
- const tabs = (trigger: HTMLElement) => {
175
- if (trigger.ariaDisabled !== "true" && trigger.ariaSelected !== "true") {
176
- let tab = trigger.parentElement?.firstElementChild
177
- while (isElement(tab)) {
178
- if (tab === trigger || tab.ariaSelected === "true") {
179
- const content = getContent(tab)
180
- if (content) {
181
- const isSelected = tab.ariaSelected !== "true"
182
- tab.ariaSelected = isSelected ? "true" : "false"
183
- tab.tabIndex = isSelected ? 0 : -1
184
- content.ariaHidden = isSelected ? "false" : "true"
185
- if (content.hasAttribute("tabindex")) content.tabIndex = isSelected ? 0 : -1
186
- isSelected
187
- ? content.removeAttribute("hidden")
188
- : content.setAttribute("hidden", "until-found")
189
- }
190
- }
191
- tab = tab.nextElementSibling
192
- }
193
- }
194
- }
195
-
196
- const menu = (trigger: HTMLElement | undefined, mode = Focus.Trigger) => {
197
- if (trigger?.id.startsWith(Prefix.TriggerMenu)) {
198
- const content = getContent(trigger)
199
- if (content) {
200
- if (trigger.ariaExpanded === "true") {
201
- if (safeGroup) safeGroup.removeAttribute("data-safe")
202
- safeGroup = null
203
- if (mode !== Focus.None) trigger.focus()
204
- content.hidePopover()
205
- trigger.ariaExpanded = "false"
206
- content.ariaHidden = "true"
207
- } else {
208
- menuPopovers.push(trigger)
209
- content.showPopover()
210
- trigger.ariaExpanded = "true"
211
- content.ariaHidden = "false"
212
- const rect = trigger.getBoundingClientRect()
213
- content.style.setProperty("--top", `${rect.top}px`)
214
- content.style.setProperty("--right", `${rect.right}px`)
215
- content.style.setProperty("--bottom", `${rect.bottom}px`)
216
- content.style.setProperty("--left", `${rect.left}px`)
217
- const group = trigger.parentElement
218
- if (group) {
219
- const cr = content.getBoundingClientRect()
220
- const right = cr.left > rect.right
221
- const sx = right ? cr.left : cr.right
222
- safeGroup = group
223
- safeRect = rect
224
- safeDir = right ? -4 : 4
225
- group.style.setProperty("--right", `${sx}px`)
226
- group.style.setProperty("--top", `${cr.top}px`)
227
- group.style.setProperty("--bottom", `${cr.bottom}px`)
228
- }
229
- if (mode === Focus.Trigger) {
230
- trigger.focus()
231
- } else if (mode === Focus.First) {
232
- menuRoving(content.firstElementChild, menuNext)
233
- } else if (mode === Focus.Last) {
234
- menuRoving(content.lastElementChild, menuPrevious)
235
- }
236
- }
237
- }
238
- }
239
- }
240
-
241
- const menuHideAll = (keep = 0) => {
242
- while (menuPopovers[keep]) menu(menuPopovers.pop(), Focus.None)
243
- }
244
-
245
- const menuItemAction = (el: HTMLElement) => {
246
- if (el.role === "menuitemcheckbox") {
247
- el.ariaChecked = el.ariaChecked === "true" ? "false" : "true"
248
- } else if (el.role === "menuitemradio") {
249
- shouldResetRadio = el
250
- radioHeadDone = null
251
- radioTailChain = []
252
- menuNext(el.parentElement)
253
- shouldResetRadio = null
254
- el.ariaChecked = "true"
255
- } else {
256
- menuHideAll()
257
- }
258
- }
259
-
260
- addEventListener("click", (event: MouseEvent) => {
261
- shouldPreventDefault = null
262
- const keyboard = event.detail === 0
263
-
264
- const start: HTMLElement | null = isElement(event.target)
265
- ? event.target
266
- : event.target instanceof Element
267
- ? event.target.parentElement
268
- : null
269
-
270
- if (start) {
271
- let target: HTMLElement | null = start
272
-
273
- while (target) {
274
- const id = target.id
275
-
276
- if (id.startsWith(Prefix.Trigger)) {
277
- if (id.startsWith(Prefix.TriggerMenu)) {
278
- const focusMode = keyboard ? Focus.First : Focus.None
279
- const inPopover = findAncestor(target.parentElement, Prefix.ContentMenu)
280
- if (inPopover) {
281
- if (!menuPopovers.includes(target)) menu(target, focusMode)
282
- } else {
283
- if (menuPopovers[0]) {
284
- const openTarget = target !== menuPopovers[0]
285
- menuHideAll()
286
- if (openTarget) menu(target, focusMode)
287
- } else {
288
- menu(target, focusMode)
289
- }
290
- }
291
- } else {
292
- if (menuPopovers[0]) menuHideAll()
293
- if (id.startsWith(Prefix.TriggerAccordion)) accordion(target)
294
- else if (id.startsWith(Prefix.TriggerCollapsible)) collapsible(target)
295
- else if (id.startsWith(Prefix.TriggerTabs)) tabs(target)
296
- }
297
- break
298
- } else if (id.startsWith(Prefix.ContentMenu) && menuPopovers[0]) {
299
- let el: HTMLElement | null = start
300
- while (el && el !== target) {
301
- if (isMenuItem(el) && !isTrigger(el, Prefix.TriggerMenu)) {
302
- menuItemAction(el)
303
- break
304
- }
305
- el = el.parentElement
306
- }
307
- break
308
- }
309
-
310
- target = target.parentElement
311
- }
312
-
313
- if (!target && menuPopovers[0]) menuHideAll()
314
- }
315
-
316
- if (shouldPreventDefault) event.preventDefault()
317
- })
318
-
319
- addEventListener("pointermove", (event: PointerEvent) => {
320
- if (event.pointerType === "touch") return
321
- if (menuPopovers[0]) {
322
- if (menuPopovers[1] && safeGroup && safeRect) {
323
- if (
324
- event.clientX >= safeRect.left &&
325
- event.clientX <= safeRect.right &&
326
- event.clientY >= safeRect.top &&
327
- event.clientY <= safeRect.bottom
328
- ) {
329
- safeGroup.style.setProperty("--left", `${event.clientX + safeDir}px`)
330
- safeGroup.style.setProperty("--center", `${event.clientY}px`)
331
- if (!safeGroup.hasAttribute("data-safe")) safeGroup.setAttribute("data-safe", "")
332
- } else if (
333
- safeGroup.hasAttribute("data-safe") &&
334
- (event.target !== safeGroup || safeDir * event.movementX > 0)
335
- ) {
336
- safeGroup.removeAttribute("data-safe")
337
- }
338
- }
339
- const el = event.target
340
-
341
- if (isElement(el)) {
342
- const popoverTriggers: HTMLButtonElement[] = []
343
- let target: HTMLElement | null = el
344
- let bail = false
345
- let foundItem = false
346
-
347
- while (target) {
348
- if (isMenuItem(target)) {
349
- foundItem = true
350
- }
351
- if (!foundItem && target.id.startsWith(Prefix.Content)) {
352
- bail = true
353
- break
354
- }
355
- const firstChild = target.firstElementChild
356
- if (isTrigger(firstChild, Prefix.TriggerMenu)) {
357
- popoverTriggers.unshift(firstChild)
358
- }
359
- target = target.parentElement
360
- }
361
-
362
- if (!bail && popoverTriggers[0]) {
363
- let i = 0
364
- while (menuPopovers[i] && menuPopovers[i] === popoverTriggers[i]) i++
365
- if (i === 0 && popoverTriggers[0].role !== "menuitem") return
366
- menuHideAll(i)
367
- menu(popoverTriggers[i], Focus.None)
368
- }
369
- }
370
- }
371
- })
372
-
373
- addEventListener("keydown", (event: KeyboardEvent) => {
374
- shouldPreventDefault = null
375
- shouldMatchLetter = null
376
- rovingBoundary = null
377
-
378
- const target = event.target
379
-
380
- if (isTrigger(target, Prefix.TriggerAccordion)) {
381
- const item = target.parentElement?.parentElement
382
- if (item) {
383
- switch (event.key) {
384
- case "ArrowDown":
385
- accordionNext(item)
386
- break
387
- case "ArrowUp":
388
- accordionPrevious(item)
389
- break
390
- case "Home": {
391
- const root = item.parentElement
392
- if (root) accordionRoving(root.firstElementChild, accordionNext)
393
- break
394
- }
395
- case "End": {
396
- const root = item.parentElement
397
- if (root) accordionRoving(root.lastElementChild, accordionPrevious)
398
- break
399
- }
400
- }
401
- }
402
- } else if (isTrigger(target, Prefix.TriggerTabs)) {
403
- const vertical = target.parentElement?.ariaOrientation === "vertical"
404
- switch (event.key) {
405
- case "ArrowDown":
406
- if (vertical) tabNext(target)
407
- break
408
- case "ArrowUp":
409
- if (vertical) tabPrevious(target)
410
- break
411
- case "ArrowRight":
412
- if (!vertical) tabNext(target)
413
- break
414
- case "ArrowLeft":
415
- if (!vertical) tabPrevious(target)
416
- break
417
- case "Home":
418
- tabsRoving(target.parentElement?.firstElementChild, tabNext)
419
- break
420
- case "End":
421
- tabsRoving(target.parentElement?.lastElementChild, tabPrevious)
422
- break
423
- }
424
- } else {
425
- if (isTrigger(target, Prefix.TriggerMenu)) {
426
- const isRootTrigger = findAncestor(target, Prefix.ContentMenu) === null
427
-
428
- switch (event.key) {
429
- case "ArrowDown":
430
- if (isRootTrigger) {
431
- if (target.ariaExpanded !== "true") {
432
- menu(target, Focus.First)
433
- } else {
434
- const content = getContent(target)
435
- if (content) menuRoving(content.firstElementChild, menuNext)
436
- }
437
- }
438
- break
439
- case "ArrowUp":
440
- if (isRootTrigger) {
441
- if (target.ariaExpanded !== "true") {
442
- menu(target, Focus.Last)
443
- } else {
444
- const content = getContent(target)
445
- if (content) menuRoving(content.lastElementChild, menuPrevious)
446
- }
447
- }
448
- break
449
- case "ArrowRight":
450
- if (!isRootTrigger) menu(target, Focus.First)
451
- break
452
- }
453
- }
454
-
455
- if (
456
- !shouldPreventDefault &&
457
- isElement(target) &&
458
- target.role?.startsWith("menuitem") &&
459
- target.parentElement
460
- ) {
461
- const parent = target.parentElement
462
- const menubarRoot = menuPopovers[0]?.parentElement || parent
463
-
464
- const inPopover = findAncestor(target.parentElement, Prefix.ContentMenu)
465
-
466
- switch (event.key) {
467
- case "Tab":
468
- if (menuPopovers[0]) menuPopovers[0].focus()
469
- menuHideAll()
470
- break
471
- case "ArrowDown":
472
- if (inPopover) menuNext(parent)
473
- break
474
- case "ArrowUp":
475
- if (inPopover) menuPrevious(parent)
476
- break
477
- case "ArrowRight": {
478
- const nextNode = menuNext(menubarRoot)
479
- if (nextNode) {
480
- const hadOpenMenu = menuPopovers[0]
481
- menuHideAll()
482
- if (hadOpenMenu && isTrigger(nextNode, Prefix.TriggerMenu)) {
483
- menu(nextNode, Focus.None)
484
- }
485
- }
486
- break
487
- }
488
- case "ArrowLeft":
489
- if (menuPopovers[1]) {
490
- menu(menuPopovers.pop(), Focus.Trigger)
491
- } else {
492
- const nextNode = menuPrevious(menubarRoot)
493
- if (nextNode) {
494
- const hadOpenMenu = menuPopovers[0]
495
- menuHideAll()
496
- if (hadOpenMenu && isTrigger(nextNode, Prefix.TriggerMenu)) {
497
- menu(nextNode, Focus.None)
498
- }
499
- }
500
- }
501
- break
502
- case "Home":
503
- menuRoving(parent.parentElement?.firstElementChild, menuNext)
504
- break
505
- case "End":
506
- menuRoving(parent.parentElement?.lastElementChild, menuPrevious)
507
- break
508
- default:
509
- if (/^[a-zA-Z]$/.test(event.key)) {
510
- shouldMatchLetter = event.key.toLowerCase()
511
- menuNext(parent)
512
- }
513
- break
514
- }
515
- }
516
- }
517
-
518
- if (event.key === "Escape" && menuPopovers[0]) {
519
- menu(menuPopovers.pop(), Focus.Trigger)
520
- }
521
-
522
- if (shouldPreventDefault) event.preventDefault()
523
- })
524
-
525
- addEventListener(
526
- "scroll",
527
- (event) => {
528
- if (
529
- menuPopovers[0] &&
530
- (!isElement(event.target) || !event.target.id.startsWith(Prefix.ContentMenu))
531
- ) {
532
- menuHideAll()
533
- }
534
- },
535
- true,
536
- )
537
-
538
- addEventListener("resize", () => {
539
- if (menuPopovers[0]) menuHideAll()
540
- })
541
-
542
- addEventListener("beforematch", (event) => {
543
- if (isElement(event.target)) {
544
- let target: HTMLElement | null = event.target
545
- while (target) {
546
- const triggerId = target.getAttribute("aria-labelledby")
547
- if (triggerId) {
548
- const trigger = document.getElementById(triggerId)
549
- if (isTrigger(trigger, Prefix.TriggerAccordion)) {
550
- if (trigger.ariaExpanded !== "true") accordion(trigger)
551
- } else if (isTrigger(trigger, Prefix.TriggerCollapsible)) {
552
- if (trigger.ariaExpanded !== "true") collapsible(trigger)
553
- } else if (isTrigger(trigger, Prefix.TriggerTabs)) {
554
- tabs(trigger)
555
- }
556
- }
557
- target = target.parentElement
558
- }
559
- }
560
- })
561
- }
@@ -1,85 +0,0 @@
1
- "use client"
2
-
3
- import { createContext, useContext, useId } from "react"
4
- import { type BaseProps, HiddenUntilFound } from "./shared.js"
5
-
6
- type AccordionContextValue = { baseId: string; open: boolean; disabled: boolean }
7
- const AccordionContext = createContext<AccordionContextValue | null>(null)
8
-
9
- function useAccordionContext() {
10
- const context = useContext(AccordionContext)
11
- if (!context) throw new Error("Accordion components must be used within Accordion.Item")
12
- return context
13
- }
14
-
15
- function Root({ children, type, ...props }: BaseProps & { type?: "single" | "multiple" }) {
16
- const id = useId()
17
- return (
18
- <div {...props} data-mode={type ?? "single"} id={`mcr:accordion:${id}`}>
19
- {children}
20
- </div>
21
- )
22
- }
23
-
24
- function Item({
25
- children,
26
- open,
27
- disabled,
28
- ...props
29
- }: BaseProps & { open?: boolean; disabled?: boolean }) {
30
- const baseId = useId()
31
- return (
32
- <AccordionContext.Provider value={{ baseId, open: open ?? false, disabled: disabled ?? false }}>
33
- <div {...props}>{children}</div>
34
- </AccordionContext.Provider>
35
- )
36
- }
37
-
38
- function Header({
39
- children,
40
- as,
41
- ...props
42
- }: BaseProps & { as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" }) {
43
- const Heading = as ?? "h3"
44
- return <Heading {...props}>{children}</Heading>
45
- }
46
-
47
- function Trigger({ children, ...props }: BaseProps) {
48
- const context = useAccordionContext()
49
- const fullId = context.baseId
50
- const isOpen = context.open
51
- return (
52
- <button
53
- {...props}
54
- type="button"
55
- id={`mct:accordion:${fullId}`}
56
- aria-expanded={isOpen}
57
- aria-controls={`mcc:accordion:${fullId}`}
58
- aria-disabled={context.disabled || undefined}
59
- >
60
- {children}
61
- </button>
62
- )
63
- }
64
-
65
- function Panel({ children, ...props }: BaseProps) {
66
- const context = useAccordionContext()
67
- const fullId = context.baseId
68
- const isOpen = context.open
69
- return (
70
- // biome-ignore lint/a11y/useSemanticElements: WAI-ARIA Accordion Pattern
71
- <div
72
- {...props}
73
- id={`mcc:accordion:${fullId}`}
74
- role="region"
75
- aria-labelledby={`mct:accordion:${fullId}`}
76
- aria-hidden={!isOpen}
77
- hidden={isOpen ? undefined : true}
78
- >
79
- {!isOpen && <HiddenUntilFound />}
80
- {children}
81
- </div>
82
- )
83
- }
84
-
85
- export const Accordion = { Root, Item, Header, Trigger, Panel }
@@ -1,60 +0,0 @@
1
- "use client"
2
-
3
- import { createContext, useContext, useId } from "react"
4
- import { type BaseProps, HiddenUntilFound } from "./shared.js"
5
-
6
- type CollapsibleContextValue = { baseId: string; open: boolean }
7
- const CollapsibleContext = createContext<CollapsibleContextValue | null>(null)
8
-
9
- function useCollapsibleContext() {
10
- const context = useContext(CollapsibleContext)
11
- if (!context) throw new Error("Collapsible components must be used within Collapsible.Root")
12
- return context
13
- }
14
-
15
- function Root({ children, open, ...props }: BaseProps & { open?: boolean }) {
16
- const baseId = useId()
17
- return (
18
- <CollapsibleContext.Provider value={{ baseId, open: open ?? false }}>
19
- <div {...props}>{children}</div>
20
- </CollapsibleContext.Provider>
21
- )
22
- }
23
-
24
- function Trigger({ children, ...props }: BaseProps) {
25
- const context = useCollapsibleContext()
26
- const fullId = context.baseId
27
- const isOpen = context.open
28
- return (
29
- <button
30
- {...props}
31
- type="button"
32
- id={`mct:collapsible:${fullId}`}
33
- aria-expanded={isOpen}
34
- aria-controls={`mcc:collapsible:${fullId}`}
35
- >
36
- {children}
37
- </button>
38
- )
39
- }
40
-
41
- function Panel({ children, ...props }: BaseProps) {
42
- const context = useCollapsibleContext()
43
- const fullId = context.baseId
44
- const isOpen = context.open
45
- return (
46
- // biome-ignore lint/a11y/useAriaPropsSupportedByRole: WAI-ARIA disclosure pattern
47
- <div
48
- {...props}
49
- id={`mcc:collapsible:${fullId}`}
50
- aria-labelledby={`mct:collapsible:${fullId}`}
51
- aria-hidden={!isOpen}
52
- hidden={isOpen ? undefined : true}
53
- >
54
- {!isOpen && <HiddenUntilFound />}
55
- {children}
56
- </div>
57
- )
58
- }
59
-
60
- export const Collapsible = { Root, Trigger, Panel }
@@ -1,4 +0,0 @@
1
- export { Accordion } from "./accordion.js"
2
- export { Collapsible } from "./collapsible.js"
3
- export { Menu } from "./menu.js"
4
- export { Tabs } from "./tabs.js"