@lexibase/rdx-ui 3.0.0 → 3.2.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.
@@ -0,0 +1,146 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { Dialog as SheetPrimitive } from '@base-ui/react/dialog'
5
+ import { cn, mergeStateClassName } from '../lib/utils'
6
+ import { useMessages } from '../providers/locale-provider'
7
+
8
+ import { Button } from './button'
9
+ import { XIcon } from 'lucide-react'
10
+
11
+ function Sheet({ ...props }: SheetPrimitive.Root.Props) {
12
+ return <SheetPrimitive.Root data-slot="sheet" {...props} />
13
+ }
14
+
15
+ function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {
16
+ return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
17
+ }
18
+
19
+ function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
20
+ return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
21
+ }
22
+
23
+ function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {
24
+ return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
25
+ }
26
+
27
+ function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
28
+ return (
29
+ <SheetPrimitive.Backdrop
30
+ data-slot="sheet-overlay"
31
+ className={mergeStateClassName(
32
+ 'fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs motion-reduce:transition-none',
33
+ className
34
+ )}
35
+ {...props}
36
+ />
37
+ )
38
+ }
39
+
40
+ function SheetContent({
41
+ className,
42
+ children,
43
+ side = 'right',
44
+ showCloseButton = true,
45
+ closeLabel,
46
+ portalProps,
47
+ ...props
48
+ }: SheetPrimitive.Popup.Props & {
49
+ side?: 'top' | 'right' | 'bottom' | 'left'
50
+ showCloseButton?: boolean
51
+ closeLabel?: string
52
+ portalProps?: SheetPrimitive.Portal.Props
53
+ }) {
54
+ const messages = useMessages()
55
+ return (
56
+ <SheetPortal {...portalProps}>
57
+ <SheetOverlay />
58
+ <SheetPrimitive.Popup
59
+ data-slot="sheet-content"
60
+ data-side={side}
61
+ className={mergeStateClassName(
62
+ 'fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm motion-reduce:transition-none',
63
+ className
64
+ )}
65
+ {...props}
66
+ >
67
+ {children}
68
+ {showCloseButton && (
69
+ <SheetPrimitive.Close
70
+ data-slot="sheet-close"
71
+ render={
72
+ <Button
73
+ variant="ghost"
74
+ className="absolute top-3 right-3"
75
+ size="icon-sm"
76
+ />
77
+ }
78
+ >
79
+ <XIcon aria-hidden="true" />
80
+ <span className="sr-only">{closeLabel ?? messages.close}</span>
81
+ </SheetPrimitive.Close>
82
+ )}
83
+ </SheetPrimitive.Popup>
84
+ </SheetPortal>
85
+ )
86
+ }
87
+
88
+ function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {
89
+ return (
90
+ <div
91
+ data-slot="sheet-header"
92
+ className={cn('flex flex-col gap-0.5 p-4', className)}
93
+ {...props}
94
+ />
95
+ )
96
+ }
97
+
98
+ function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {
99
+ return (
100
+ <div
101
+ data-slot="sheet-footer"
102
+ className={cn('mt-auto flex flex-col gap-2 p-4', className)}
103
+ {...props}
104
+ />
105
+ )
106
+ }
107
+
108
+ function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
109
+ return (
110
+ <SheetPrimitive.Title
111
+ data-slot="sheet-title"
112
+ className={mergeStateClassName(
113
+ 'cn-font-heading text-base font-medium text-foreground',
114
+ className
115
+ )}
116
+ {...props}
117
+ />
118
+ )
119
+ }
120
+
121
+ function SheetDescription({
122
+ className,
123
+ ...props
124
+ }: SheetPrimitive.Description.Props) {
125
+ return (
126
+ <SheetPrimitive.Description
127
+ data-slot="sheet-description"
128
+ className={mergeStateClassName(
129
+ 'text-sm text-muted-foreground',
130
+ className
131
+ )}
132
+ {...props}
133
+ />
134
+ )
135
+ }
136
+
137
+ export {
138
+ Sheet,
139
+ SheetTrigger,
140
+ SheetClose,
141
+ SheetContent,
142
+ SheetHeader,
143
+ SheetFooter,
144
+ SheetTitle,
145
+ SheetDescription,
146
+ }
@@ -0,0 +1,10 @@
1
+ import { getMessages } from '../lib/i18n'
2
+
3
+ /** First keyboard stop, shared by the website and generated documentation. */
4
+ export function SkipLink({ lang }: { lang?: string }) {
5
+ return (
6
+ <a className="rdx-skip-link" href="#rdx-main">
7
+ {getMessages(lang).skipContent}
8
+ </a>
9
+ )
10
+ }
@@ -25,6 +25,7 @@ function panelText(node: Node): string {
25
25
  )
26
26
  )
27
27
  return ''
28
+ if (node.dataset.rdxKbdValue !== undefined) return node.dataset.rdxKbdValue
28
29
  if (node.matches('[data-rdx-code-block], [data-slot="code-block"]')) {
29
30
  const pre = node.querySelector('pre')
30
31
  const lines = pre?.querySelectorAll('[data-slot="code-block-line-content"]')
@@ -144,6 +145,7 @@ function Tabs({ className, ...props }: TabsPrimitive.Root.Props) {
144
145
  }
145
146
 
146
147
  function TabsList({ className, ...props }: TabsPrimitive.List.Props) {
148
+ const messages = useMessages()
147
149
  return (
148
150
  <div
149
151
  data-slot="tabs-header"
@@ -151,6 +153,9 @@ function TabsList({ className, ...props }: TabsPrimitive.List.Props) {
151
153
  >
152
154
  <TabsPrimitive.List
153
155
  data-slot="tabs-list"
156
+ aria-label={
157
+ props['aria-labelledby'] ? undefined : messages.documentationTabs
158
+ }
154
159
  className={mergeStateClassName(
155
160
  'flex min-w-0 flex-1 gap-0.5 overflow-x-auto p-1.5 data-[orientation=vertical]:flex-col',
156
161
  className
@@ -167,7 +172,7 @@ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
167
172
  <TabsPrimitive.Tab
168
173
  data-slot="tabs-trigger"
169
174
  className={mergeStateClassName(
170
- 'relative shrink-0 rounded-md border border-transparent px-2.5 py-1 text-sm font-medium whitespace-nowrap text-muted-foreground transition-colors hover:bg-muted/60 hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-active:border-border data-active:bg-background data-active:text-foreground data-active:shadow-xs data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:bg-transparent data-disabled:hover:text-muted-foreground motion-reduce:transition-none',
175
+ 'relative shrink-0 rounded-md border border-transparent px-2.5 py-1 text-sm font-medium whitespace-nowrap text-muted-foreground transition-colors hover:bg-muted/60 hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-active:border-border data-active:bg-background data-active:text-rdx-selected data-active:shadow-xs data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:bg-transparent data-disabled:hover:text-muted-foreground motion-reduce:transition-none',
171
176
  className
172
177
  )}
173
178
  {...props}
package/src/lib/i18n.ts CHANGED
@@ -1,5 +1,19 @@
1
1
  /** Shared labels only: document content and author-provided names stay untouched. */
2
2
  const en = {
3
+ keyboardJoiner: ' plus ',
4
+ keyboardControl: 'Control',
5
+ keyboardShift: 'Shift',
6
+ keyboardAlt: 'Alt',
7
+ keyboardCommand: 'Command',
8
+ keyboardOption: 'Option',
9
+ keyboardEscape: 'Escape',
10
+ keyboardEnter: 'Enter',
11
+ keyboardSpace: 'Space',
12
+ keyboardPlus: 'Plus',
13
+ keyboardArrowUp: 'Up arrow',
14
+ keyboardArrowDown: 'Down arrow',
15
+ keyboardArrowLeft: 'Left arrow',
16
+ keyboardArrowRight: 'Right arrow',
3
17
  skipContent: 'Skip to content',
4
18
  mainNavigation: 'Main navigation',
5
19
  docs: 'Docs',
@@ -32,15 +46,25 @@ const en = {
32
46
  'Press Escape or use the close button to return to the document.',
33
47
  copyCode: 'Copy code',
34
48
  wrapCode: 'Wrap lines',
49
+ showMore: 'Show more',
50
+ showLess: 'Show less',
35
51
  codeCopied: 'Code copied to clipboard.',
36
52
  copyCodeError: 'Could not copy. Select the code and copy it manually.',
37
53
  copyRetry: 'Could not copy. Try again or copy manually.',
38
54
  copied: 'Copied!',
39
55
  copyTab: 'Copy active tab',
56
+ documentationTabs: 'Documentation tabs',
40
57
  tabCopied: 'Tab content copied to clipboard.',
41
58
  copyTabError: 'Could not copy. Select the tab content and copy it manually.',
42
59
  code: 'Code',
43
60
  folderTree: 'Folder',
61
+ diagram: 'Diagram',
62
+ showDiagram: 'Show diagram',
63
+ showDiagramSource: 'Show diagram source',
64
+ enlargeDiagram: 'Enlarge diagram',
65
+ diagramLoading: 'Rendering diagram…',
66
+ diagramError:
67
+ 'Could not render this diagram. Check the Mermaid source below.',
44
68
  table: 'Table',
45
69
  current: 'Current',
46
70
  version: 'Version {version}',
@@ -69,6 +93,20 @@ export type InterfaceMessages = { [Key in keyof typeof en]: string }
69
93
  export type InterfaceLocale = 'en' | 'pt-BR'
70
94
 
71
95
  const ptBR = {
96
+ keyboardJoiner: ' mais ',
97
+ keyboardControl: 'Control',
98
+ keyboardShift: 'Shift',
99
+ keyboardAlt: 'Alt',
100
+ keyboardCommand: 'Command',
101
+ keyboardOption: 'Option',
102
+ keyboardEscape: 'Escape',
103
+ keyboardEnter: 'Enter',
104
+ keyboardSpace: 'Espaço',
105
+ keyboardPlus: 'Sinal de mais',
106
+ keyboardArrowUp: 'Seta para cima',
107
+ keyboardArrowDown: 'Seta para baixo',
108
+ keyboardArrowLeft: 'Seta para a esquerda',
109
+ keyboardArrowRight: 'Seta para a direita',
72
110
  skipContent: 'Ir para o conteúdo',
73
111
  mainNavigation: 'Navegação principal',
74
112
  docs: 'Docs',
@@ -101,17 +139,27 @@ const ptBR = {
101
139
  'Pressione Escape ou use o botão de fechar para voltar ao documento.',
102
140
  copyCode: 'Copiar código',
103
141
  wrapCode: 'Quebrar linhas',
142
+ showMore: 'Mostrar mais',
143
+ showLess: 'Mostrar menos',
104
144
  codeCopied: 'Código copiado para a área de transferência.',
105
145
  copyCodeError:
106
146
  'Não foi possível copiar. Selecione o código e copie manualmente.',
107
147
  copyRetry: 'Não foi possível copiar. Tente novamente ou copie manualmente.',
108
148
  copied: 'Copiado!',
109
149
  copyTab: 'Copiar aba ativa',
150
+ documentationTabs: 'Abas da documentação',
110
151
  tabCopied: 'Conteúdo da aba copiado para a área de transferência.',
111
152
  copyTabError:
112
153
  'Não foi possível copiar. Selecione o conteúdo da aba e copie manualmente.',
113
154
  code: 'Código',
114
155
  folderTree: 'Pastas',
156
+ diagram: 'Diagrama',
157
+ showDiagram: 'Mostrar diagrama',
158
+ showDiagramSource: 'Mostrar código do diagrama',
159
+ enlargeDiagram: 'Ampliar diagrama',
160
+ diagramLoading: 'Renderizando diagrama…',
161
+ diagramError:
162
+ 'Não foi possível renderizar o diagrama. Confira o código Mermaid abaixo.',
115
163
  table: 'Tabela',
116
164
  current: 'Atual',
117
165
  version: 'Versão {version}',
@@ -0,0 +1,114 @@
1
+ type Mermaid = (typeof import('mermaid'))['default']
2
+
3
+ let engine: Promise<Mermaid> | undefined
4
+ let queue: Promise<unknown> = Promise.resolve()
5
+ let serial = 0
6
+
7
+ function loadMermaid() {
8
+ engine ??= import('mermaid')
9
+ .then((module) => module.default)
10
+ .catch((error: unknown) => {
11
+ engine = undefined
12
+ throw error
13
+ })
14
+ return engine
15
+ }
16
+
17
+ /** Mermaid owns global configuration: serialize theme setup and rendering. */
18
+ export function renderMermaid(
19
+ source: string,
20
+ dark: boolean,
21
+ active: () => boolean
22
+ ): Promise<string | undefined> {
23
+ const task = queue.then(async () => {
24
+ if (!active()) return
25
+ if (source.length > 50_000)
26
+ throw new Error('Mermaid definition exceeds 50,000 characters.')
27
+ const mermaid = await loadMermaid()
28
+ if (!active()) return
29
+ const fontFamily = 'ui-sans-serif, system-ui, sans-serif'
30
+ mermaid.initialize({
31
+ startOnLoad: false,
32
+ securityLevel: 'strict',
33
+ suppressErrorRendering: true,
34
+ htmlLabels: false,
35
+ maxTextSize: 50_000,
36
+ maxEdges: 500,
37
+ theme: 'base',
38
+ fontFamily,
39
+ themeVariables: {
40
+ fontSize: '14px',
41
+ darkMode: dark,
42
+ background: 'transparent',
43
+ primaryColor: dark ? '#18181b' : '#f4f4f5',
44
+ primaryTextColor: dark ? '#fafafa' : '#18181b',
45
+ primaryBorderColor: dark ? '#52525b' : '#a1a1aa',
46
+ lineColor: dark ? '#a1a1aa' : '#52525b',
47
+ secondaryColor: dark ? '#27272a' : '#e4e4e7',
48
+ tertiaryColor: dark ? '#18181b' : '#fafafa',
49
+ fontFamily,
50
+ },
51
+ // Diagram directives cannot change the site's safety, theme or limits.
52
+ secure: [
53
+ ...new Set([
54
+ 'secure',
55
+ 'securityLevel',
56
+ 'startOnLoad',
57
+ 'maxTextSize',
58
+ 'maxEdges',
59
+ 'suppressErrorRendering',
60
+ 'theme',
61
+ 'flowchart',
62
+ 'htmlLabels',
63
+ 'dompurifyConfig',
64
+ 'themeCSS',
65
+ 'themeVariables',
66
+ 'fontFamily',
67
+ ]),
68
+ ],
69
+ })
70
+ const container = document.createElement('div')
71
+ container.setAttribute('aria-hidden', 'true')
72
+ container.inert = true
73
+ Object.assign(container.style, {
74
+ position: 'fixed',
75
+ left: '-100000px',
76
+ visibility: 'hidden',
77
+ })
78
+ document.body.append(container)
79
+ try {
80
+ const { svg } = await mermaid.render(
81
+ `rdx-mermaid-${++serial}`,
82
+ source,
83
+ container
84
+ )
85
+ if (!active()) return
86
+ // Mermaid's responsive width is intended for inline SVG. Give the image
87
+ // intrinsic dimensions so preview scaling and the expanded view stay crisp.
88
+ const document = new DOMParser().parseFromString(svg, 'image/svg+xml')
89
+ const element = document.documentElement
90
+ if (element.localName !== 'svg')
91
+ throw new Error('Mermaid returned invalid SVG.')
92
+ const viewBox = element
93
+ .getAttribute('viewBox')
94
+ ?.split(/[\s,]+/)
95
+ .map(Number)
96
+ if (
97
+ viewBox?.length === 4 &&
98
+ viewBox.every(Number.isFinite) &&
99
+ viewBox[2]! > 0 &&
100
+ viewBox[3]! > 0
101
+ ) {
102
+ element.setAttribute('width', String(viewBox[2]))
103
+ element.setAttribute('height', String(viewBox[3]))
104
+ element.style.removeProperty('max-width')
105
+ }
106
+ return new XMLSerializer().serializeToString(element)
107
+ } finally {
108
+ container.remove()
109
+ }
110
+ })
111
+ // A failed diagram must not block other diagrams on this or the next page.
112
+ queue = task.catch(() => undefined)
113
+ return task
114
+ }
@@ -6,6 +6,28 @@
6
6
 
7
7
  @custom-variant dark (&:is(.dark *));
8
8
 
9
+ .rdx-skip-link {
10
+ position: fixed;
11
+ top: 1rem;
12
+ inset-inline-start: 1rem;
13
+ z-index: 100;
14
+ max-width: calc(100vw - 2rem);
15
+ transform: translateY(calc(-100% - 2rem));
16
+ padding: 0.5rem 0.75rem;
17
+ border: 1px solid var(--border);
18
+ border-radius: var(--radius);
19
+ background: var(--background);
20
+ color: var(--foreground);
21
+ font-size: 0.875rem;
22
+ font-weight: 500;
23
+ outline: 2px solid var(--ring);
24
+ outline-offset: 2px;
25
+ }
26
+
27
+ .rdx-skip-link:focus {
28
+ transform: translateY(0);
29
+ }
30
+
9
31
  :root {
10
32
  --background: oklch(1 0 0);
11
33
  --foreground: oklch(0.145 0 0);
@@ -19,13 +41,13 @@
19
41
  --secondary-foreground: oklch(0.205 0 0);
20
42
  --muted: oklch(0.97 0 0);
21
43
  --muted-foreground: oklch(0.556 0 0);
22
- --accent: oklch(0.97 0 0);
23
- --accent-foreground: oklch(0.205 0 0);
44
+ --accent: var(--rdx-highlighting-soft, oklch(0.97 0 0));
45
+ --accent-foreground: var(--rdx-highlighting, oklch(0.205 0 0));
24
46
  --destructive: oklch(0.577 0.245 27.325);
25
47
  --destructive-foreground: oklch(0.577 0.245 27.325);
26
48
  --border: oklch(0.922 0 0);
27
49
  --input: oklch(0.922 0 0);
28
- --ring: oklch(0.708 0 0);
50
+ --ring: var(--rdx-highlighting, oklch(0.708 0 0));
29
51
  --chart-1: oklch(0.646 0.222 41.116);
30
52
  --chart-2: oklch(0.6 0.118 184.704);
31
53
  --chart-3: oklch(0.398 0.07 227.392);
@@ -34,12 +56,12 @@
34
56
  --radius: 0.625rem;
35
57
  --sidebar: oklch(0.985 0 0);
36
58
  --sidebar-foreground: oklch(0.145 0 0);
37
- --sidebar-primary: oklch(0.205 0 0);
59
+ --sidebar-primary: var(--rdx-highlighting, oklch(0.205 0 0));
38
60
  --sidebar-primary-foreground: oklch(0.985 0 0);
39
- --sidebar-accent: oklch(0.97 0 0);
40
- --sidebar-accent-foreground: oklch(0.205 0 0);
61
+ --sidebar-accent: var(--rdx-highlighting-soft, oklch(0.97 0 0));
62
+ --sidebar-accent-foreground: var(--rdx-highlighting, oklch(0.205 0 0));
41
63
  --sidebar-border: oklch(0.922 0 0);
42
- --sidebar-ring: oklch(0.708 0 0);
64
+ --sidebar-ring: var(--rdx-highlighting, oklch(0.708 0 0));
43
65
  }
44
66
 
45
67
  .dark {
@@ -55,13 +77,13 @@
55
77
  --secondary-foreground: oklch(0.985 0 0);
56
78
  --muted: oklch(0.269 0 0);
57
79
  --muted-foreground: oklch(0.708 0 0);
58
- --accent: oklch(0.269 0 0);
59
- --accent-foreground: oklch(0.985 0 0);
80
+ --accent: var(--rdx-highlighting-soft, oklch(0.269 0 0));
81
+ --accent-foreground: var(--rdx-highlighting, oklch(0.985 0 0));
60
82
  --destructive: oklch(0.396 0.141 25.723);
61
83
  --destructive-foreground: oklch(0.637 0.237 25.331);
62
84
  --border: oklch(0.269 0 0);
63
85
  --input: oklch(0.269 0 0);
64
- --ring: oklch(0.556 0 0);
86
+ --ring: var(--rdx-highlighting, oklch(0.556 0 0));
65
87
  --chart-1: oklch(0.488 0.243 264.376);
66
88
  --chart-2: oklch(0.696 0.17 162.48);
67
89
  --chart-3: oklch(0.769 0.188 70.08);
@@ -69,15 +91,27 @@
69
91
  --chart-5: oklch(0.645 0.246 16.439);
70
92
  --sidebar: oklch(0.205 0 0);
71
93
  --sidebar-foreground: oklch(0.985 0 0);
72
- --sidebar-primary: oklch(0.488 0.243 264.376);
94
+ --sidebar-primary: var(--rdx-highlighting, oklch(0.488 0.243 264.376));
73
95
  --sidebar-primary-foreground: oklch(0.985 0 0);
74
- --sidebar-accent: oklch(0.269 0 0);
75
- --sidebar-accent-foreground: oklch(0.985 0 0);
96
+ --sidebar-accent: var(--rdx-highlighting-soft, oklch(0.269 0 0));
97
+ --sidebar-accent-foreground: var(--rdx-highlighting, oklch(0.985 0 0));
76
98
  --sidebar-border: oklch(0.269 0 0);
77
- --sidebar-ring: oklch(0.439 0 0);
99
+ --sidebar-ring: var(--rdx-highlighting, oklch(0.439 0 0));
100
+ }
101
+
102
+ :root,
103
+ .dark {
104
+ /* Invalid without an override, allowing each existing token to keep its default. */
105
+ --rdx-highlighting-soft: color-mix(
106
+ in oklab,
107
+ var(--rdx-highlighting) 14%,
108
+ var(--background)
109
+ );
78
110
  }
79
111
 
80
112
  @theme inline {
113
+ --color-rdx-highlighting: var(--rdx-highlighting, var(--color-blue-500));
114
+ --color-rdx-selected: var(--rdx-highlighting, var(--foreground));
81
115
  /* shadcn accordion animation, using the height measured by Base UI. */
82
116
  --animate-accordion-down: accordion-down 0.2s ease-out;
83
117
  --animate-accordion-up: accordion-up 0.2s ease-out;
@@ -141,6 +175,7 @@
141
175
  @layer base {
142
176
  * {
143
177
  @apply border-border outline-ring/50;
178
+ scrollbar-width: thin;
144
179
  }
145
180
  body {
146
181
  @apply bg-background text-foreground;
@@ -158,3 +193,66 @@
158
193
  .dark [data-rdx-code-block] pre.shiki span {
159
194
  color: var(--shiki-dark, inherit);
160
195
  }
196
+
197
+ /* Only document navigation participates; desktop and reduced motion stay still. */
198
+ :root:active-view-transition-type(
199
+ rdx-doc-forward,
200
+ rdx-doc-back
201
+ )::view-transition {
202
+ pointer-events: none;
203
+ }
204
+ :root:active-view-transition-type(
205
+ rdx-doc-forward,
206
+ rdx-doc-back
207
+ )::view-transition-group(root) {
208
+ display: none;
209
+ }
210
+
211
+ ::view-transition-group(.rdx-doc-forward),
212
+ ::view-transition-group(.rdx-doc-back),
213
+ ::view-transition-old(.rdx-doc-forward),
214
+ ::view-transition-new(.rdx-doc-forward),
215
+ ::view-transition-old(.rdx-doc-back),
216
+ ::view-transition-new(.rdx-doc-back) {
217
+ animation: none;
218
+ }
219
+
220
+ @media (width < 768px) and (prefers-reduced-motion: no-preference) {
221
+ ::view-transition-old(.rdx-doc-forward) {
222
+ --rdx-page-offset: -48px;
223
+ animation: rdx-page-exit 220ms ease-out both;
224
+ }
225
+ ::view-transition-new(.rdx-doc-forward) {
226
+ --rdx-page-offset: 48px;
227
+ animation: rdx-page-enter 260ms cubic-bezier(0.22, 1, 0.36, 1) both;
228
+ }
229
+ ::view-transition-old(.rdx-doc-back) {
230
+ --rdx-page-offset: 48px;
231
+ animation: rdx-page-exit 220ms ease-out both;
232
+ }
233
+ ::view-transition-new(.rdx-doc-back) {
234
+ --rdx-page-offset: -48px;
235
+ animation: rdx-page-enter 260ms cubic-bezier(0.22, 1, 0.36, 1) both;
236
+ }
237
+ }
238
+
239
+ @keyframes rdx-page-enter {
240
+ from {
241
+ opacity: 0;
242
+ transform: translateX(var(--rdx-page-offset));
243
+ }
244
+ to {
245
+ opacity: 1;
246
+ transform: translateX(0);
247
+ }
248
+ }
249
+ @keyframes rdx-page-exit {
250
+ from {
251
+ opacity: 1;
252
+ transform: translateX(0);
253
+ }
254
+ to {
255
+ opacity: 0;
256
+ transform: translateX(var(--rdx-page-offset));
257
+ }
258
+ }