@adea-ai/ui 0.106.2 → 0.107.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.
Files changed (69) hide show
  1. package/dist/components/composites/appearance-editor/appearance-editor.d.ts +9 -3
  2. package/dist/components/composites/appearance-editor/appearance-editor.d.ts.map +1 -1
  3. package/dist/components/composites/appearance-editor/appearance-editor.js +61 -29
  4. package/dist/components/composites/appearance-editor/appearance-editor.js.map +1 -1
  5. package/dist/components/composites/appearance-editor/appearance-types.d.ts +2 -0
  6. package/dist/components/composites/appearance-editor/appearance-types.d.ts.map +1 -1
  7. package/dist/components/composites/appearance-editor/index.d.ts +1 -1
  8. package/dist/components/composites/appearance-editor/index.d.ts.map +1 -1
  9. package/dist/components/composites/appearance-editor/index.js +2 -2
  10. package/dist/components/ui/badge/badge.d.ts.map +1 -1
  11. package/dist/components/ui/badge/badge.js +9 -5
  12. package/dist/components/ui/badge/badge.js.map +1 -1
  13. package/dist/components/ui/combobox/combobox.d.ts.map +1 -1
  14. package/dist/components/ui/combobox/combobox.js +2 -2
  15. package/dist/components/ui/combobox/combobox.js.map +1 -1
  16. package/dist/components/ui/context-menu/context-menu.d.ts.map +1 -1
  17. package/dist/components/ui/context-menu/context-menu.js +3 -3
  18. package/dist/components/ui/context-menu/context-menu.js.map +1 -1
  19. package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts.map +1 -1
  20. package/dist/components/ui/dropdown-menu/dropdown-menu.js +7 -4
  21. package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
  22. package/dist/components/ui/menubar/menubar.d.ts.map +1 -1
  23. package/dist/components/ui/menubar/menubar.js +3 -3
  24. package/dist/components/ui/menubar/menubar.js.map +1 -1
  25. package/dist/components/ui/popover/popover.d.ts.map +1 -1
  26. package/dist/components/ui/popover/popover.js +2 -2
  27. package/dist/components/ui/popover/popover.js.map +1 -1
  28. package/dist/components/ui/select/select.d.ts.map +1 -1
  29. package/dist/components/ui/select/select.js +2 -2
  30. package/dist/components/ui/select/select.js.map +1 -1
  31. package/dist/components/ui/sheet/sheet.d.ts +16 -0
  32. package/dist/components/ui/sheet/sheet.d.ts.map +1 -1
  33. package/dist/components/ui/sheet/sheet.js +82 -16
  34. package/dist/components/ui/sheet/sheet.js.map +1 -1
  35. package/dist/index.js +2 -2
  36. package/dist/lib/overlay.d.ts +15 -0
  37. package/dist/lib/overlay.d.ts.map +1 -1
  38. package/dist/lib/overlay.js +14 -1
  39. package/dist/lib/overlay.js.map +1 -1
  40. package/dist/r/appearance-editor.json +1 -1
  41. package/dist/r/registry.json +1 -1
  42. package/dist/r/src/components/composites/appearance-editor/appearance-editor.tsx +55 -33
  43. package/dist/r/src/components/composites/appearance-editor/appearance-types.ts +2 -0
  44. package/dist/r/src/components/composites/appearance-editor/index.ts +1 -1
  45. package/dist/r/src/components/ui/badge/badge.tsx +9 -5
  46. package/dist/r/src/components/ui/combobox/combobox.tsx +2 -1
  47. package/dist/r/src/components/ui/context-menu/context-menu.tsx +3 -0
  48. package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +9 -0
  49. package/dist/r/src/components/ui/menubar/menubar.tsx +3 -0
  50. package/dist/r/src/components/ui/popover/popover.tsx +8 -1
  51. package/dist/r/src/components/ui/select/select.tsx +2 -1
  52. package/dist/r/src/components/ui/sheet/sheet.tsx +78 -15
  53. package/dist/r/src/lib/overlay.ts +14 -0
  54. package/dist/r/src/styles/base.css +13 -0
  55. package/package.json +1 -1
  56. package/registry.json +1 -1
  57. package/src/components/composites/appearance-editor/appearance-editor.tsx +55 -33
  58. package/src/components/composites/appearance-editor/appearance-types.ts +2 -0
  59. package/src/components/composites/appearance-editor/index.ts +1 -1
  60. package/src/components/ui/badge/badge.tsx +9 -5
  61. package/src/components/ui/combobox/combobox.tsx +2 -1
  62. package/src/components/ui/context-menu/context-menu.tsx +3 -0
  63. package/src/components/ui/dropdown-menu/dropdown-menu.tsx +9 -0
  64. package/src/components/ui/menubar/menubar.tsx +3 -0
  65. package/src/components/ui/popover/popover.tsx +8 -1
  66. package/src/components/ui/select/select.tsx +2 -1
  67. package/src/components/ui/sheet/sheet.tsx +78 -15
  68. package/src/lib/overlay.ts +14 -0
  69. package/src/styles/base.css +13 -0
@@ -11,6 +11,7 @@ import {
11
11
  overlayMotion,
12
12
  overlaySurface,
13
13
  popoverArrow,
14
+ topLayerProps,
14
15
  } from '../../../lib/overlay'
15
16
  import { cn } from '../../../lib/utils'
16
17
 
@@ -71,6 +72,7 @@ export function MenubarContent(props: ComponentProps<typeof KobalteMenubar.Conte
71
72
  return (
72
73
  <KobalteMenubar.Portal>
73
74
  <KobalteMenubar.Content
75
+ {...topLayerProps}
74
76
  class={cn(
75
77
  overlaySurface,
76
78
  overlayMotion,
@@ -201,6 +203,7 @@ export function MenubarSubContent(props: ComponentProps<typeof KobalteMenubar.Su
201
203
  return (
202
204
  <KobalteMenubar.Portal>
203
205
  <KobalteMenubar.SubContent
206
+ {...topLayerProps}
204
207
  class={cn(
205
208
  overlaySurface,
206
209
  overlayMotion,
@@ -1,7 +1,13 @@
1
1
  import { Popover as KobaltePopover } from '@kobalte/core/popover'
2
2
  import type { ComponentProps } from 'solid-js'
3
3
  import { splitProps } from 'solid-js'
4
- import { menuContentPadding, popoverArrow, popoverMotion, overlaySurface } from '../../../lib/overlay'
4
+ import {
5
+ menuContentPadding,
6
+ popoverArrow,
7
+ popoverMotion,
8
+ overlaySurface,
9
+ topLayerProps,
10
+ } from '../../../lib/overlay'
5
11
  import { cn } from '../../../lib/utils'
6
12
 
7
13
  /**
@@ -39,6 +45,7 @@ export function PopoverContent(props: PopoverContentProps) {
39
45
  return (
40
46
  <KobaltePopover.Portal>
41
47
  <KobaltePopover.Content
48
+ {...topLayerProps}
42
49
  class={cn(
43
50
  overlaySurface,
44
51
  popoverMotion,
@@ -2,7 +2,7 @@ import { Select as KobalteSelect } from '@kobalte/core/select'
2
2
  import { Check, ChevronDown, ChevronsUpDown } from 'lucide-solid'
3
3
  import type { ComponentProps } from 'solid-js'
4
4
  import { Show, splitProps } from 'solid-js'
5
- import { menuContentPadding, menuItem, popoverArrow } from '../../../lib/overlay'
5
+ import { menuContentPadding, menuItem, popoverArrow, topLayerProps } from '../../../lib/overlay'
6
6
  import { cn } from '../../../lib/utils'
7
7
 
8
8
  /**
@@ -140,6 +140,7 @@ export function SelectContent(props: SelectContentProps) {
140
140
  return (
141
141
  <KobalteSelect.Portal mount={local.portalMount}>
142
142
  <KobalteSelect.Content
143
+ {...topLayerProps}
143
144
  class={cn(
144
145
  'bg-popover text-popover-foreground rounded-xl border border-border shadow-lg',
145
146
  'z-(--z-menu) max-h-(--kb-popper-content-available-height) min-w-[8rem] origin-(--kb-select-content-transform-origin) overflow-y-auto overflow-x-hidden',
@@ -24,31 +24,79 @@ import { headingVariants } from '../typography'
24
24
  * `end`, and a full-width row for `top` and `bottom`. The slide-in direction
25
25
  * follows from the side, so a caller never pairs them by hand.
26
26
  *
27
+ * `variant` decides how it meets that edge. `inset` — the default for `end` —
28
+ * docks the panel inside the main view: below the top bar, one equal gap from
29
+ * the top bar, the end edge and the bottom, with no scrim, so the work it edits
30
+ * stays visible and the top bar stays reachable. It is the shape for editing one
31
+ * thing beside its context (appearance settings, a task, a record). `edge` runs
32
+ * edge to edge over a scrim — the shape of a navigation drawer, and the default
33
+ * for every other side.
34
+ *
35
+ * Its parts read alike everywhere: `SheetHeader` holds a heading, a description
36
+ * and a full-width rule beneath, with the close button in the corner;
37
+ * `SheetBody` scrolls on its own; `SheetFooter` is a full-width tinted band that
38
+ * holds the decision.
39
+ *
27
40
  * Controlled sheets opened from outside a `SheetTrigger` restore focus to the
28
41
  * element focused before opening. Pass `restoreFocusRef` when a stable external
29
42
  * opener must be used instead, such as when another layer closes before the
30
43
  * sheet opens.
31
44
  */
45
+ // `overflow-clip`, not `overflow-hidden`: a hidden overflow can still be
46
+ // scrolled programmatically (focus, scrollIntoView, scrollTop), which slid the
47
+ // pinned footer out of view. Only SheetBody scrolls.
32
48
  const sheetVariants = cva(
33
- 'bg-popover text-popover-foreground fixed z-(--z-dialog) flex flex-col gap-4 border-border shadow-lg',
49
+ 'bg-popover text-popover-foreground fixed z-(--z-dialog) flex flex-col overflow-clip border-border shadow-lg',
34
50
  {
35
51
  variants: {
36
52
  side: {
37
- top: 'inset-x-0 top-0 h-auto max-h-[85vh] rounded-b-xl border-b data-expanded:slide-in-from-top data-closed:slide-out-to-top',
38
- bottom:
39
- 'inset-x-0 bottom-0 h-auto max-h-[85vh] rounded-t-xl border-t data-expanded:slide-in-from-bottom data-closed:slide-out-to-bottom',
40
- start:
41
- 'inset-y-0 start-0 h-full w-80 max-w-[85vw] rounded-e-xl border-e data-expanded:slide-in-from-left data-closed:slide-out-to-left',
42
- end: 'inset-y-0 end-0 h-full w-80 max-w-[85vw] rounded-s-xl border-s data-expanded:slide-in-from-right data-closed:slide-out-to-right',
53
+ top: 'data-expanded:slide-in-from-top data-closed:slide-out-to-top',
54
+ bottom: 'data-expanded:slide-in-from-bottom data-closed:slide-out-to-bottom',
55
+ start: 'data-expanded:slide-in-from-left data-closed:slide-out-to-left',
56
+ end: 'data-expanded:slide-in-from-right data-closed:slide-out-to-right',
57
+ },
58
+ variant: {
59
+ edge: '',
60
+ inset: 'panel-dialog-inset w-lg rounded-xl border',
43
61
  },
44
62
  },
45
- defaultVariants: { side: 'end' },
63
+ compoundVariants: [
64
+ {
65
+ side: 'top',
66
+ variant: 'edge',
67
+ class: 'inset-x-0 top-0 h-auto max-h-[85vh] rounded-b-xl border-b',
68
+ },
69
+ {
70
+ side: 'bottom',
71
+ variant: 'edge',
72
+ class: 'inset-x-0 bottom-0 h-auto max-h-[85vh] rounded-t-xl border-t',
73
+ },
74
+ {
75
+ side: 'start',
76
+ variant: 'edge',
77
+ class: 'inset-y-0 start-0 h-full w-80 max-w-[85vw] rounded-e-xl border-e',
78
+ },
79
+ {
80
+ side: 'end',
81
+ variant: 'edge',
82
+ class: 'inset-y-0 end-0 h-full w-80 max-w-[85vw] rounded-s-xl border-s',
83
+ },
84
+ ],
85
+ defaultVariants: { side: 'end', variant: 'edge' },
46
86
  }
47
87
  )
48
88
 
89
+ /** `inset` for the end edge, `edge` for every other side, unless the caller says. */
90
+ const sheetVariantFor = (
91
+ side: SheetContentProps['side'],
92
+ variant: SheetContentProps['variant']
93
+ ): 'edge' | 'inset' => variant ?? ((side ?? 'end') === 'end' ? 'inset' : 'edge')
94
+
49
95
  export type SheetContentProps = ComponentProps<typeof KobalteDialogContent> &
50
96
  VariantProps<typeof sheetVariants> & {
51
97
  closeButton?: JSX.Element | false
98
+ /** The close button's accessible name. Defaults to "Close". */
99
+ closeLabel?: string
52
100
  /** Supplies the stable external element to focus when the controlled sheet closes. */
53
101
  restoreFocusRef?: Accessor<HTMLElement | undefined>
54
102
  }
@@ -57,8 +105,10 @@ export function SheetContent(props: SheetContentProps) {
57
105
  const [local, rest] = splitProps(props, [
58
106
  'class',
59
107
  'side',
108
+ 'variant',
60
109
  'children',
61
110
  'closeButton',
111
+ 'closeLabel',
62
112
  'restoreFocusRef',
63
113
  'onOpenAutoFocus',
64
114
  'onCloseAutoFocus',
@@ -71,12 +121,19 @@ export function SheetContent(props: SheetContentProps) {
71
121
  onCloseAutoFocus: local.onCloseAutoFocus,
72
122
  })
73
123
 
124
+ const variant = () => sheetVariantFor(local.side, local.variant)
125
+
74
126
  return (
75
127
  <KobalteDialog.Portal>
76
- <DialogOverlay />
128
+ {/* An inset panel is beside the work, so the work stays in view: no scrim.
129
+ It is still modal — focus stays inside and an outside click dismisses. */}
130
+ <Show when={variant() === 'edge'}>
131
+ <DialogOverlay />
132
+ </Show>
77
133
  <KobalteDialogContent
134
+ data-variant={variant()}
78
135
  class={cn(
79
- sheetVariants({ side: local.side }),
136
+ sheetVariants({ side: local.side, variant: variant() }),
80
137
  'data-expanded:animate-in data-expanded:fade-in-0 data-expanded:duration-200',
81
138
  'data-closed:animate-out data-closed:fade-out-0 data-closed:duration-150',
82
139
  local.class
@@ -92,7 +149,7 @@ export function SheetContent(props: SheetContentProps) {
92
149
  WebKit walks only explicit tabindex and form controls) would skip
93
150
  an implicitly-tabbable button and jump past the sheet's end. */}
94
151
  <KobalteDialog.CloseButton
95
- aria-label="Close"
152
+ aria-label={local.closeLabel ?? 'Close'}
96
153
  tabindex="0"
97
154
  class="absolute top-3.5 end-3.5 rounded-md p-1 text-muted-foreground transition-colors ease-out outline-none hover:bg-surface-hover hover:text-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle"
98
155
  >
@@ -122,7 +179,10 @@ export function SheetHeader(props: ComponentProps<'div'>) {
122
179
  return (
123
180
  <div
124
181
  data-slot="sheet-header"
125
- class={cn('flex flex-col gap-1.5 border-b border-border p-4 pe-12 text-start', local.class)}
182
+ class={cn(
183
+ 'flex shrink-0 flex-col gap-1.5 border-b border-border p-4 pe-12 text-start',
184
+ local.class
185
+ )}
126
186
  {...rest}
127
187
  />
128
188
  )
@@ -133,7 +193,7 @@ export function SheetBody(props: ComponentProps<'div'>) {
133
193
  return (
134
194
  <div
135
195
  data-slot="sheet-body"
136
- class={cn('flex-1 overflow-y-auto px-4 pb-4', local.class)}
196
+ class={cn('min-h-0 flex-1 overflow-y-auto p-4', local.class)}
137
197
  {...rest}
138
198
  />
139
199
  )
@@ -144,7 +204,10 @@ export function SheetFooter(props: ComponentProps<'div'>) {
144
204
  return (
145
205
  <div
146
206
  data-slot="sheet-footer"
147
- class={cn('flex items-center justify-end gap-2 border-t border-border p-4', local.class)}
207
+ class={cn(
208
+ 'flex shrink-0 flex-wrap items-center justify-end gap-2 border-t border-border bg-background px-4 py-3',
209
+ local.class
210
+ )}
148
211
  {...rest}
149
212
  />
150
213
  )
@@ -154,7 +217,7 @@ export function SheetTitle(props: ComponentProps<typeof KobalteDialog.Title>) {
154
217
  const [local, rest] = splitProps(props, ['class'])
155
218
  return (
156
219
  <KobalteDialog.Title
157
- class={cn(headingVariants({ size: 'subsection', leading: 'none' }), local.class)}
220
+ class={cn(headingVariants({ size: 'card', leading: 'none' }), local.class)}
158
221
  {...rest}
159
222
  />
160
223
  )
@@ -81,3 +81,17 @@ export const menuSeparator = 'bg-border -mx-1 my-1 h-px'
81
81
 
82
82
  /** Padding shared by every menu-list surface. */
83
83
  export const menuContentPadding = 'p-1'
84
+
85
+ /**
86
+ * Marks a floating layer — a menu, a listbox, a popover — as part of the top
87
+ * layer.
88
+ *
89
+ * A modal dialog hides everything outside itself from assistive technology
90
+ * (`aria-hidden` on its siblings) and keeps watching `<body>` for new nodes.
91
+ * A menu or select opened from inside the dialog portals to `<body>`, so the
92
+ * dialog hid it too: the list was on screen, focused, and invisible to a
93
+ * screen reader, and an outside-click check could treat a click on it as a
94
+ * click away from the dialog. Kobalte exempts nodes carrying this attribute
95
+ * from both, so every floating layer carries it.
96
+ */
97
+ export const topLayerProps = { 'data-kb-top-layer': '' } as const
@@ -277,6 +277,19 @@
277
277
  text-orientation: mixed;
278
278
  }
279
279
 
280
+ /**
281
+ * The docking box of an inset Sheet: below the top bar, with one gap (two spacing
282
+ * steps) from the top bar, the end edge and the bottom of the window, so the
283
+ * panel fills the height of the main view and its edges line up on every side.
284
+ * Narrow windows keep the same gap at the start edge too.
285
+ */
286
+ @utility panel-dialog-inset {
287
+ top: calc(var(--topbar-height) + var(--spacing) * 2);
288
+ inset-inline-end: calc(var(--spacing) * 2);
289
+ bottom: calc(var(--spacing) * 2);
290
+ max-width: calc(100vw - var(--spacing) * 4);
291
+ }
292
+
280
293
  @utility no-scrollbar {
281
294
  -ms-overflow-style: none;
282
295
  scrollbar-width: none;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.106.2",
3
+ "version": "0.107.0",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
package/registry.json CHANGED
@@ -1918,7 +1918,7 @@
1918
1918
  "@adea-ai/ui/button",
1919
1919
  "@adea-ai/ui/dropdown-menu",
1920
1920
  "@adea-ai/ui/input",
1921
- "@adea-ai/ui/popover",
1921
+ "@adea-ai/ui/sheet",
1922
1922
  "@adea-ai/ui/switch",
1923
1923
  "@adea-ai/ui/theme",
1924
1924
  "lib"
@@ -41,12 +41,15 @@ import { DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS } from '#lib/appearance-font-se
41
41
 
42
42
  import { Switch } from '../../ui/switch/switch'
43
43
  import {
44
- Popover,
45
- PopoverContent,
46
- PopoverDescription,
47
- PopoverTitle,
48
- PopoverTrigger,
49
- } from '../../ui/popover/popover'
44
+ Sheet,
45
+ SheetBody,
46
+ SheetContent,
47
+ SheetDescription,
48
+ SheetFooter,
49
+ SheetHeader,
50
+ SheetTitle,
51
+ SheetTrigger,
52
+ } from '../../ui/sheet/sheet'
50
53
  import type { AppearanceEditorProps, AppearancePopoverProps } from './appearance-types'
51
54
  import { ModeChoices, AccentChoices, GlassChoices, isCustomAccent } from './appearance-choices'
52
55
  import { SettingsRow, TerminalRow, ThemeRow } from './appearance-rows'
@@ -61,7 +64,6 @@ import { SettingsRow, TerminalRow, ThemeRow } from './appearance-rows'
61
64
  */
62
65
  export function AppearanceEditor(props: AppearanceEditorProps) {
63
66
  const errorId = createUniqueId()
64
- const saveReasonId = createUniqueId()
65
67
  const custom = () => isCustomAccent(props)
66
68
  const accentDescription = () => {
67
69
  if (props.draft.accent === 'theme') return "Theme default · Uses the palette's intended color."
@@ -188,15 +190,35 @@ export function AppearanceEditor(props: AppearanceEditorProps) {
188
190
  </Button>
189
191
  </SettingsRow>
190
192
  </div>
193
+ <Show when={!props.hideActions}>
194
+ {/* Sticky so Save/Cancel stay reachable in a tall scroll container: the
195
+ editor scrolls with its host panel, and actions parked at the bottom
196
+ of a long form were unreachable without scrolling past every row. */}
197
+ <div class="sticky bottom-0 z-10 mt-2 border-t bg-background px-4 py-3">
198
+ <AppearanceEditorActions {...props} />
199
+ </div>
200
+ </Show>
201
+ </div>
202
+ )
203
+ }
204
+
205
+ /**
206
+ * Reset, Cancel and Save, with the reason Save is unavailable. Rendered by the
207
+ * editor itself, or — with `hideActions` — by a host that pins them in its own
208
+ * footer, as `AppearancePopover` does.
209
+ */
210
+ export function AppearanceEditorActions(props: AppearanceEditorProps) {
211
+ const saveReasonId = createUniqueId()
212
+ return (
213
+ <div class="flex w-full flex-col gap-2">
191
214
  <Show when={props.saveDisabledReason}>
192
- <p id={saveReasonId} role="status" class="px-4 pt-3 text-xs text-muted-foreground">
215
+ <p id={saveReasonId} role="status" class="text-xs text-muted-foreground">
193
216
  {props.saveDisabledReason}
194
217
  </p>
195
218
  </Show>
196
- {/* Sticky so Save/Cancel stay reachable in a tall scroll container: the
197
- editor scrolls with its host panel, and actions parked at the bottom
198
- of a long form were unreachable without scrolling past every row. */}
199
- <div class="global-appearance-editor-actions sticky bottom-0 z-10 mt-2 flex flex-wrap items-center gap-2 border-t bg-background px-4 py-3">
219
+ {/* `global-appearance-editor-actions` lets these buttons grow with the UI
220
+ font preference, like the rest of Appearance's own controls. */}
221
+ <div class="global-appearance-editor-actions flex flex-wrap items-center gap-2">
200
222
  <Button
201
223
  type="button"
202
224
  size="sm"
@@ -232,19 +254,15 @@ export function AppearanceEditor(props: AppearanceEditorProps) {
232
254
  )
233
255
  }
234
256
 
235
- /** Modal anchoring/dismissal and nested-menu behavior come from Kobalte.
236
- * Its measured available height also bounds scrolling, keeping footer actions
237
- * reachable when a narrow popup begins below its trigger. */
257
+ /** The editor in an inset Sheet docked beside the main view: the body scrolls and
258
+ * Reset/Cancel/Save sit in the panel's full-width footer, always reachable.
259
+ * Dismissal (Escape, the close button, an outside click) is `onDismiss`. */
238
260
  export function AppearancePopover(props: AppearancePopoverProps) {
239
261
  const [menuPortalMount, setMenuPortalMount] = createSignal<HTMLElement>()
240
262
 
241
263
  return (
242
- <Popover
243
- open={props.open}
244
- modal
245
- onOpenChange={(open) => (open ? props.onOpen() : props.onDismiss())}
246
- >
247
- <PopoverTrigger
264
+ <Sheet open={props.open} onOpenChange={(open) => (open ? props.onOpen() : props.onDismiss())}>
265
+ <SheetTrigger
248
266
  as={Button}
249
267
  type="button"
250
268
  variant="ghost"
@@ -252,17 +270,21 @@ export function AppearancePopover(props: AppearancePopoverProps) {
252
270
  aria-label="Appearance settings"
253
271
  >
254
272
  <Palette />
255
- </PopoverTrigger>
256
- <PopoverContent
257
- ref={setMenuPortalMount}
258
- class="w-lg max-w-[calc(100vw-2rem)] max-h-(--kb-popper-content-available-height) overflow-y-auto"
259
- >
260
- <PopoverTitle>Appearance</PopoverTitle>
261
- <PopoverDescription>
262
- Changes preview immediately. Save keeps them; Cancel restores the previous appearance.
263
- </PopoverDescription>
264
- <AppearanceEditor {...props} menuPortalMount={menuPortalMount()} />
265
- </PopoverContent>
266
- </Popover>
273
+ </SheetTrigger>
274
+ <SheetContent side="end" closeLabel="Close appearance settings" ref={setMenuPortalMount}>
275
+ <SheetHeader>
276
+ <SheetTitle>Appearance</SheetTitle>
277
+ <SheetDescription>
278
+ Changes preview immediately. Save keeps them; Cancel restores the previous appearance.
279
+ </SheetDescription>
280
+ </SheetHeader>
281
+ <SheetBody>
282
+ <AppearanceEditor {...props} menuPortalMount={menuPortalMount()} hideActions />
283
+ </SheetBody>
284
+ <SheetFooter>
285
+ <AppearanceEditorActions {...props} />
286
+ </SheetFooter>
287
+ </SheetContent>
288
+ </Sheet>
267
289
  )
268
290
  }
@@ -55,6 +55,8 @@ export type AppearanceEditorProps = {
55
55
  /** Mount nested font menus inside the containing overlay when one exists. */
56
56
  menuPortalMount?: HTMLElement
57
57
  class?: string
58
+ /** Leave Reset/Cancel/Save to the host, which renders `AppearanceEditorActions`. */
59
+ hideActions?: boolean
58
60
  }
59
61
 
60
62
  export type AppearancePopoverProps = AppearanceEditorProps & {
@@ -1,4 +1,4 @@
1
- export { AppearanceEditor, AppearancePopover } from './appearance-editor'
1
+ export { AppearanceEditor, AppearanceEditorActions, AppearancePopover } from './appearance-editor'
2
2
  export {
3
3
  AppearanceFontSettingsGroup,
4
4
  type AppearanceFontSettingsGroupProps,
@@ -19,11 +19,15 @@ export const badgeVariants = cva(
19
19
  default: 'bg-primary text-primary-foreground',
20
20
  secondary: 'bg-secondary text-secondary-foreground',
21
21
  outline: 'border-border text-foreground',
22
- /** Tinted status fills, for counts and states rather than actions. */
23
- success: 'bg-success-subtle text-foreground',
24
- warning: 'bg-warning-subtle text-foreground',
25
- destructive: 'bg-destructive-subtle text-foreground',
26
- info: 'bg-info-subtle text-foreground',
22
+ /**
23
+ * Tinted status fills, for counts and states rather than actions. The
24
+ * word stays in `foreground` for contrast; the glyph takes the status
25
+ * hue, so tags of different tones read apart at a glance.
26
+ */
27
+ success: 'bg-success-subtle text-foreground [&_svg]:text-success',
28
+ warning: 'bg-warning-subtle text-foreground [&_svg]:text-warning',
29
+ destructive: 'bg-destructive-subtle text-foreground [&_svg]:text-destructive',
30
+ info: 'bg-info-subtle text-foreground [&_svg]:text-info',
27
31
  subtle: 'bg-primary-subtle text-primary',
28
32
  /**
29
33
  * A count that asks for attention, pinned to a corner of another control
@@ -2,7 +2,7 @@ import { Combobox as KobalteCombobox } from '@kobalte/core/combobox'
2
2
  import { Check, ChevronDown, X } from 'lucide-solid'
3
3
  import type { ComponentProps } from 'solid-js'
4
4
  import { Show, splitProps } from 'solid-js'
5
- import { menuContentPadding, menuItem, popoverArrow } from '#lib/overlay'
5
+ import { menuContentPadding, menuItem, popoverArrow, topLayerProps } from '#lib/overlay'
6
6
  import { useFormFieldControl } from '#lib/form-field'
7
7
  import { cn } from '#lib/utils'
8
8
 
@@ -108,6 +108,7 @@ export function ComboboxContent(props: ComponentProps<typeof KobalteCombobox.Con
108
108
  return (
109
109
  <KobalteCombobox.Portal>
110
110
  <KobalteCombobox.Content
111
+ {...topLayerProps}
111
112
  class={cn(
112
113
  'bg-popover text-popover-foreground rounded-xl border border-border shadow-lg',
113
114
  'z-(--z-menu) max-h-72 min-w-(--kb-popper-anchor-width) origin-(--kb-combobox-content-transform-origin) overflow-hidden',
@@ -10,6 +10,7 @@ import {
10
10
  menuSeparator,
11
11
  overlayMotion,
12
12
  overlaySurface,
13
+ topLayerProps,
13
14
  } from '#lib/overlay'
14
15
  import { cn } from '#lib/utils'
15
16
 
@@ -40,6 +41,7 @@ export function ContextMenuContent(props: ComponentProps<typeof KobalteContextMe
40
41
  return (
41
42
  <KobalteContextMenu.Portal>
42
43
  <KobalteContextMenu.Content
44
+ {...topLayerProps}
43
45
  class={cn(
44
46
  overlaySurface,
45
47
  overlayMotion,
@@ -174,6 +176,7 @@ export function ContextMenuSubContent(props: ComponentProps<typeof KobalteContex
174
176
  return (
175
177
  <KobalteContextMenu.Portal>
176
178
  <KobalteContextMenu.SubContent
179
+ {...topLayerProps}
177
180
  class={cn(
178
181
  overlaySurface,
179
182
  overlayMotion,
@@ -11,6 +11,7 @@ import {
11
11
  overlayMotion,
12
12
  overlaySurface,
13
13
  popoverArrow,
14
+ topLayerProps,
14
15
  } from '#lib/overlay'
15
16
  import { cn } from '#lib/utils'
16
17
 
@@ -148,6 +149,12 @@ export function DropdownMenuTrigger(props: ComponentProps<typeof KobalteDropdown
148
149
  const isNativeLink = trigger instanceof HTMLAnchorElement && trigger.hasAttribute('href')
149
150
  if (trigger instanceof HTMLElement && !isNativeLink) {
150
151
  trigger.focus({ preventScroll: true })
152
+ // Focusing first is not enough in WebKit: the press's default mousedown
153
+ // action runs afterwards and moves focus off a button to the nearest
154
+ // focusable ancestor — inside a dialog, the dialog — so the menu that
155
+ // just opened still reads focus leaving it and closes. Cancelling the
156
+ // default keeps focus where it was put.
157
+ if (event.pointerType !== 'touch') event.preventDefault()
151
158
  }
152
159
  }
153
160
  const onKeyDown = (event: DropdownMenuTriggerKeyEvent) => {
@@ -196,6 +203,7 @@ export function DropdownMenuContent(props: DropdownMenuContentProps) {
196
203
  return (
197
204
  <KobalteDropdownMenu.Portal mount={local.portalMount}>
198
205
  <KobalteDropdownMenu.Content
206
+ {...topLayerProps}
199
207
  class={cn(
200
208
  overlaySurface,
201
209
  overlayMotion,
@@ -372,6 +380,7 @@ export function DropdownMenuSubContent(
372
380
  return (
373
381
  <KobalteDropdownMenu.Portal>
374
382
  <KobalteDropdownMenu.SubContent
383
+ {...topLayerProps}
375
384
  class={cn(
376
385
  overlaySurface,
377
386
  overlayMotion,
@@ -11,6 +11,7 @@ import {
11
11
  overlayMotion,
12
12
  overlaySurface,
13
13
  popoverArrow,
14
+ topLayerProps,
14
15
  } from '#lib/overlay'
15
16
  import { cn } from '#lib/utils'
16
17
 
@@ -71,6 +72,7 @@ export function MenubarContent(props: ComponentProps<typeof KobalteMenubar.Conte
71
72
  return (
72
73
  <KobalteMenubar.Portal>
73
74
  <KobalteMenubar.Content
75
+ {...topLayerProps}
74
76
  class={cn(
75
77
  overlaySurface,
76
78
  overlayMotion,
@@ -201,6 +203,7 @@ export function MenubarSubContent(props: ComponentProps<typeof KobalteMenubar.Su
201
203
  return (
202
204
  <KobalteMenubar.Portal>
203
205
  <KobalteMenubar.SubContent
206
+ {...topLayerProps}
204
207
  class={cn(
205
208
  overlaySurface,
206
209
  overlayMotion,
@@ -1,7 +1,13 @@
1
1
  import { Popover as KobaltePopover } from '@kobalte/core/popover'
2
2
  import type { ComponentProps } from 'solid-js'
3
3
  import { splitProps } from 'solid-js'
4
- import { menuContentPadding, popoverArrow, popoverMotion, overlaySurface } from '#lib/overlay'
4
+ import {
5
+ menuContentPadding,
6
+ popoverArrow,
7
+ popoverMotion,
8
+ overlaySurface,
9
+ topLayerProps,
10
+ } from '#lib/overlay'
5
11
  import { cn } from '#lib/utils'
6
12
 
7
13
  /**
@@ -39,6 +45,7 @@ export function PopoverContent(props: PopoverContentProps) {
39
45
  return (
40
46
  <KobaltePopover.Portal>
41
47
  <KobaltePopover.Content
48
+ {...topLayerProps}
42
49
  class={cn(
43
50
  overlaySurface,
44
51
  popoverMotion,
@@ -2,7 +2,7 @@ import { Select as KobalteSelect } from '@kobalte/core/select'
2
2
  import { Check, ChevronDown, ChevronsUpDown } from 'lucide-solid'
3
3
  import type { ComponentProps } from 'solid-js'
4
4
  import { Show, splitProps } from 'solid-js'
5
- import { menuContentPadding, menuItem, popoverArrow } from '#lib/overlay'
5
+ import { menuContentPadding, menuItem, popoverArrow, topLayerProps } from '#lib/overlay'
6
6
  import { cn } from '#lib/utils'
7
7
 
8
8
  /**
@@ -140,6 +140,7 @@ export function SelectContent(props: SelectContentProps) {
140
140
  return (
141
141
  <KobalteSelect.Portal mount={local.portalMount}>
142
142
  <KobalteSelect.Content
143
+ {...topLayerProps}
143
144
  class={cn(
144
145
  'bg-popover text-popover-foreground rounded-xl border border-border shadow-lg',
145
146
  'z-(--z-menu) max-h-(--kb-popper-content-available-height) min-w-[8rem] origin-(--kb-select-content-transform-origin) overflow-y-auto overflow-x-hidden',