velocis 1.1.1 → 1.3.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/dist/velocis.css CHANGED
@@ -1 +1 @@
1
- *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.border{border-width:1px}:root{--velocis-color-primary:#6366f1;--velocis-color-secondary:#8b5cf6;--velocis-color-accent:#06b6d4;--velocis-color-background:#fff;--velocis-color-foreground:#0f172a;--velocis-color-muted:#64748b;--velocis-color-border:#e2e8f0;--velocis-radius-sm:0.25rem;--velocis-radius-md:0.5rem;--velocis-radius-lg:0.75rem;--velocis-radius-xl:1rem;--velocis-spacing-1:0.25rem;--velocis-spacing-2:0.5rem;--velocis-spacing-3:0.75rem;--velocis-spacing-4:1rem;--velocis-spacing-6:1.5rem;--velocis-spacing-8:2rem;--velocis-font-sans:ui-sans-serif,system-ui,sans-serif;--velocis-shadow-sm:0 1px 2px 0 rgba(0,0,0,.05);--velocis-shadow-md:0 4px 6px -1px rgba(0,0,0,.1);--velocis-shadow-lg:0 10px 15px -3px rgba(0,0,0,.1);--velocis-motion-duration-fast:150ms;--velocis-motion-duration-normal:250ms;--velocis-motion-duration-slow:400ms;--velocis-z-dropdown:1000;--velocis-z-drawer:1300;--velocis-z-modal:1400;--velocis-z-tooltip:1600}.dark{--velocis-color-primary:#818cf8;--velocis-color-secondary:#a78bfa;--velocis-color-accent:#22d3ee;--velocis-color-background:#0f172a;--velocis-color-foreground:#f8fafc;--velocis-color-muted:#94a3b8;--velocis-color-border:#334155}@media print{[data-velocis-card],table{-moz-column-break-inside:avoid;break-inside:avoid}thead{display:table-header-group}[data-hero-video],video{display:none!important}a[href]:after{content:" (" attr(href) ")";font-size:.8em;color:#666}.velocis-no-print{display:none!important}}@media (prefers-reduced-motion:reduce){*,:after,:before{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@media (forced-colors:active){.velocis-forced-colors-border{border:1px solid CanvasText}}
1
+ *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.fixed{position:fixed}.border{border-width:1px}.bg-velocis-background{background-color:var(--velocis-color-background)}.p-4{padding:1rem}.text-velocis-foreground{color:var(--velocis-color-foreground)}:root{--velocis-color-primary:#6366f1;--velocis-color-secondary:#8b5cf6;--velocis-color-accent:#06b6d4;--velocis-color-background:#fff;--velocis-color-foreground:#0f172a;--velocis-color-muted:#64748b;--velocis-color-border:#e2e8f0;--velocis-radius-sm:0.25rem;--velocis-radius-md:0.5rem;--velocis-radius-lg:0.75rem;--velocis-radius-xl:1rem;--velocis-spacing-1:0.25rem;--velocis-spacing-2:0.5rem;--velocis-spacing-3:0.75rem;--velocis-spacing-4:1rem;--velocis-spacing-6:1.5rem;--velocis-spacing-8:2rem;--velocis-font-sans:ui-sans-serif,system-ui,sans-serif;--velocis-shadow-sm:0 1px 2px 0 rgba(0,0,0,.05);--velocis-shadow-md:0 4px 6px -1px rgba(0,0,0,.1);--velocis-shadow-lg:0 10px 15px -3px rgba(0,0,0,.1);--velocis-motion-duration-fast:150ms;--velocis-motion-duration-normal:250ms;--velocis-motion-duration-slow:400ms;--velocis-z-dropdown:1000;--velocis-z-drawer:1300;--velocis-z-modal:1400;--velocis-z-tooltip:1600}.dark{--velocis-color-primary:#818cf8;--velocis-color-secondary:#a78bfa;--velocis-color-accent:#22d3ee;--velocis-color-background:#0f172a;--velocis-color-foreground:#f8fafc;--velocis-color-muted:#94a3b8;--velocis-color-border:#334155}[data-velocis-scheme=light]{color-scheme:light;--velocis-color-primary:#6366f1;--velocis-color-secondary:#8b5cf6;--velocis-color-accent:#06b6d4;--velocis-color-background:#fff;--velocis-color-foreground:#0f172a;--velocis-color-muted:#64748b;--velocis-color-border:#e2e8f0}[data-velocis-scheme=dark]{color-scheme:dark;--velocis-color-primary:#818cf8;--velocis-color-secondary:#a78bfa;--velocis-color-accent:#22d3ee;--velocis-color-background:#0f172a;--velocis-color-foreground:#f8fafc;--velocis-color-muted:#94a3b8;--velocis-color-border:#334155}@media print{[data-velocis-card],table{-moz-column-break-inside:avoid;break-inside:avoid}thead{display:table-header-group}[data-hero-video],video{display:none!important}a[href]:after{content:" (" attr(href) ")";font-size:.8em;color:#666}.velocis-no-print{display:none!important}}@media (prefers-reduced-motion:reduce){*,:after,:before{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@media (forced-colors:active){.velocis-forced-colors-border{border:1px solid CanvasText}}
package/docs/dropdown1.md CHANGED
@@ -58,7 +58,7 @@ const { open, setOpen, dropdownProps } = useDropdown1();
58
58
 
59
59
  ## Styling — coordinated colors
60
60
 
61
- Every layer uses **bg + text + hover** pairs from `dropdown1Styles` (Velocis tokens by default). Override any layer via the `styles` prop `subMenuContent` inherits `content` when omitted.
61
+ Every layer uses **bg + text + hover** pairs from `dropdown1Styles` (Velocis tokens by default). Override any layer via the `styles` prop. `subMenuContent` has its own default and does **not** inherit `content` unless you set both to the same classes.
62
62
 
63
63
  ```tsx
64
64
  import { Dropdown1, dropdown1Styles } from 'velocis/dropdown1';
@@ -75,6 +75,7 @@ import { Dropdown1, dropdown1Styles } from 'velocis/dropdown1';
75
75
  content: 'bg-slate-900 text-white border-slate-700 shadow-xl ring-1 ring-slate-700/50',
76
76
  item: 'text-slate-100 hover:bg-slate-800 hover:text-white',
77
77
  subMenuTrigger: 'text-slate-100 hover:bg-slate-800 hover:text-white',
78
+ subMenuContent: 'bg-indigo-950 text-indigo-100 border-indigo-800 shadow-xl',
78
79
  subMenuIcon: 'text-slate-400',
79
80
  }}
80
81
  >
@@ -94,11 +95,91 @@ import { Dropdown1, dropdown1Styles } from 'velocis/dropdown1';
94
95
  | `content` | `bg-velocis-background text-velocis-foreground …` | Main menu panel |
95
96
  | `item` | `text-velocis-foreground hover:bg-velocis-border/40` | `Dropdown1.Item` |
96
97
  | `subMenuTrigger` | same as `item` | Sub-menu row |
97
- | `subMenuContent` | inherits `content` | Sub-menu panel |
98
+ | `subMenuContent` | independent panel default | Sub-menu panel |
98
99
  | `subMenuIcon` | `text-velocis-muted` | Sub-menu chevron |
99
100
 
100
101
  Export `dropdown1Styles` and `resolveDropdown1Styles` for app-wide presets.
101
102
 
103
+ ## SubMenu placement
104
+
105
+ Control where the sub-menu panel opens relative to the **main menu panel** with the `placement` prop on `Dropdown1.SubMenu`.
106
+
107
+ ```
108
+ left right (auto in LTR)
109
+ ┌─────────┐ ┌─────────┐
110
+ │ SubMenu │◄── gap ──► │ Main │── gap ──► ┌─────────┐
111
+ └─────────┘ │ panel │ │ SubMenu │
112
+ └─────────┘ └─────────┘
113
+
114
+ center
115
+ ┌─────────┐
116
+ │ Main │
117
+ │ panel │
118
+ └────┬────┘
119
+
120
+ ┌─────────┐
121
+ │ SubMenu │ (horizontally centered below panel)
122
+ └─────────┘
123
+ ```
124
+
125
+ | `placement` | Position |
126
+ |-------------|----------|
127
+ | `'auto'` (default) | **RTL** → left of main panel — **LTR** → right of main panel |
128
+ | `'left'` | Always to the physical left of the main panel edge |
129
+ | `'right'` | Always to the physical right of the main panel edge |
130
+ | `'center'` | Below the main panel, horizontally centered |
131
+
132
+ Vertical alignment for `left` / `right` / `auto`: aligned with the sub-menu trigger row (natural cascade). For `center`: `top = panel.bottom + gap`.
133
+
134
+ ```tsx
135
+ // auto — follows DirectionProvider (RTL=left, LTR=right)
136
+ <Dropdown1.SubMenu trigger="More">...</Dropdown1.SubMenu>
137
+
138
+ // force physical sides
139
+ <Dropdown1.SubMenu placement="left" trigger="More">...</Dropdown1.SubMenu>
140
+ <Dropdown1.SubMenu placement="right" trigger="More">...</Dropdown1.SubMenu>
141
+
142
+ // below main panel, centered
143
+ <Dropdown1.SubMenu placement="center" trigger="More">...</Dropdown1.SubMenu>
144
+ ```
145
+
146
+ ## SubMenu background (independent from main panel)
147
+
148
+ The sub-menu panel can use a **different background** than the main menu:
149
+
150
+ | Method | Use when |
151
+ |--------|----------|
152
+ | `styles={{ subMenuContent: '…' }}` on root or `SubMenu` | Tailwind class override |
153
+ | `surface={{ background, tokens }}` on `Dropdown1.SubMenu` | Velocis surface API (same as root `surface`) |
154
+ | Root `styles.content` only | Main panel — does **not** change sub-menu unless you also set `subMenuContent` |
155
+
156
+ ```tsx
157
+ // White main menu + dark sub-menu via styles
158
+ <Dropdown1 trigger={<span>Menu</span>}>
159
+ <Dropdown1.Item>Item</Dropdown1.Item>
160
+ <Dropdown1.SubMenu
161
+ trigger="More"
162
+ styles={{
163
+ subMenuContent:
164
+ 'bg-indigo-950 text-indigo-100 border border-indigo-800 shadow-xl',
165
+ }}
166
+ >
167
+ <Dropdown1.Item>Sub item</Dropdown1.Item>
168
+ </Dropdown1.SubMenu>
169
+ </Dropdown1>
170
+
171
+ // Or surface on SubMenu only
172
+ <Dropdown1.SubMenu
173
+ trigger="More"
174
+ surface={{
175
+ background: '#1e1b4b',
176
+ tokens: { foreground: '#e0e7ff' },
177
+ }}
178
+ >
179
+ ...
180
+ </Dropdown1.SubMenu>
181
+ ```
182
+
102
183
  ## Props — `Dropdown1`
103
184
 
104
185
  | Prop | Default | Description |
@@ -131,13 +212,15 @@ Export `dropdown1Styles` and `resolveDropdown1Styles` for app-wide presets.
131
212
  |------|---------|-------------|
132
213
  | `trigger` | required | Sub-menu label |
133
214
  | `children` | required | Nested `Dropdown1.Item` elements |
215
+ | `placement` | `'auto'` | `'auto' \| 'left' \| 'right' \| 'center'` — see [SubMenu placement](#submenu-placement) |
134
216
  | `contentWidth` | `w-48` | Sub-menu panel width |
135
- | `styles` | root sub-menu styles | Override sub-menu colors |
217
+ | `styles` | root sub-menu styles | Override `subMenuTrigger` / `subMenuContent` / `subMenuIcon` |
218
+ | `surface` | — | Independent panel surface (background, tokens) |
136
219
  | `className` | — | Extra CSS on sub-menu panel |
137
220
 
138
221
  ## RTL / LTR
139
222
 
140
- Dropdown1 respects `DirectionProvider` from `velocis/theme`. Sub-menus open toward inline-end (right in LTR, left in RTL) using logical CSS properties.
223
+ Dropdown1 respects `DirectionProvider` from `velocis/theme`. Main panel alignment follows direction. Sub-menu `placement="auto"` opens toward inline-start in RTL (left of panel) and inline-end in LTR (right of panel). Use `placement="left"` or `"right"` for fixed physical sides.
141
224
 
142
225
  ## Accessibility
143
226
 
package/docs/theme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Theme (`velocis/theme`)
2
2
 
3
- Design tokens, i18n messages, and global providers.
3
+ Design tokens, i18n messages, global providers, and per-component surface customization.
4
4
 
5
5
  ## Import
6
6
 
@@ -8,9 +8,14 @@ Design tokens, i18n messages, and global providers.
8
8
  import {
9
9
  VelocisProvider,
10
10
  DirectionProvider,
11
+ Surface,
11
12
  useTheme,
12
13
  useMessages,
14
+ applySurface,
15
+ resolveSurfaceProps,
13
16
  defaultMessages,
17
+ type VelocisSurfaceConfig,
18
+ type VelocisTokenOverrides,
14
19
  } from 'velocis/theme';
15
20
  ```
16
21
 
@@ -18,10 +23,18 @@ import {
18
23
 
19
24
  ### `VelocisProvider`
20
25
 
21
- Wraps the app. Handles light/dark/system theme, optional persistence, and error logging.
26
+ Wraps the app. Handles light/dark/system theme, optional persistence, token overrides, and error logging.
22
27
 
23
28
  ```tsx
24
- <VelocisProvider theme="system" persistTheme storageKey="velocis-theme">
29
+ <VelocisProvider
30
+ theme="system"
31
+ persistTheme
32
+ storageKey="velocis-theme"
33
+ tokens={{
34
+ background: { light: '#fafafa', dark: '#0c0c0c' },
35
+ primary: '#6366f1',
36
+ }}
37
+ >
25
38
  {children}
26
39
  </VelocisProvider>
27
40
  ```
@@ -29,6 +42,7 @@ Wraps the app. Handles light/dark/system theme, optional persistence, and error
29
42
  | Prop | Type | Default | Description |
30
43
  |------|------|---------|-------------|
31
44
  | `theme` | `'light' \| 'dark' \| 'system'` | `'system'` | Color scheme |
45
+ | `tokens` | `VelocisTokenOverrides` | — | Override design tokens app-wide |
32
46
  | `messages` | `Partial<VelocisMessages>` | — | Override built-in strings |
33
47
  | `persistTheme` | `boolean` | `true` | Save theme to localStorage |
34
48
  | `storageKey` | `string` | `'velocis-theme'` | localStorage key |
@@ -46,21 +60,139 @@ Sets document direction for RTL/LTR layouts.
46
60
  |------|------|---------|
47
61
  | `direction` | `'ltr' \| 'rtl'` | `'ltr'` |
48
62
 
63
+ ### `Surface`
64
+
65
+ Wrap **any** UI (Velocis or custom) to override background/tokens and lock light or dark mode locally.
66
+
67
+ ```tsx
68
+ <Surface
69
+ colorScheme="light"
70
+ background="#fef9c3"
71
+ tokens={{ foreground: '#422006', border: '#fde047' }}
72
+ className="rounded-xl p-6"
73
+ >
74
+ <MyWidget />
75
+ </Surface>
76
+ ```
77
+
78
+ ## Surface customization
79
+
80
+ Three levels of flexibility:
81
+
82
+ | Level | API | Use when |
83
+ |-------|-----|----------|
84
+ | App-wide | `VelocisProvider tokens={...}` | Brand colors for the whole project |
85
+ | Per component | `surface` prop on `Card`, `Dialog1`, `Dropdown1`, … | One-off panel/card/menu styling |
86
+ | Any subtree | `<Surface>` wrapper or `applySurface()` | Custom components or nested overrides |
87
+
88
+ ### `VelocisSurfaceConfig`
89
+
90
+ ```tsx
91
+ type VelocisSurfaceConfig = {
92
+ /** `inherit` follows app theme; `light` / `dark` lock tokens for this subtree only */
93
+ colorScheme?: 'inherit' | 'light' | 'dark';
94
+ /** Shorthand for background token */
95
+ background?: string | { light?: string; dark?: string; base?: string };
96
+ /** Override any token locally */
97
+ tokens?: Partial<Record<
98
+ 'primary' | 'secondary' | 'accent' | 'background' | 'foreground' | 'muted' | 'border',
99
+ string | { light?: string; dark?: string; base?: string }
100
+ >>;
101
+ className?: string;
102
+ style?: React.CSSProperties;
103
+ };
104
+ ```
105
+
106
+ ### Examples
107
+
108
+ **Fixed background (same in light and dark):**
109
+
110
+ ```tsx
111
+ <Card surface={{ background: '#fef3c7' }}>...</Card>
112
+ ```
113
+
114
+ **Different backgrounds per mode:**
115
+
116
+ ```tsx
117
+ <Card surface={{ background: { light: '#ffffff', dark: '#1e293b' } }}>...</Card>
118
+ ```
119
+
120
+ **Light-only card inside a dark app:**
121
+
122
+ ```tsx
123
+ <Card surface={{ colorScheme: 'light', background: '#fff' }}>...</Card>
124
+ ```
125
+
126
+ **Dark-only dropdown menu:**
127
+
128
+ ```tsx
129
+ <Dropdown1
130
+ surface={{ colorScheme: 'dark' }}
131
+ trigger="Actions"
132
+ >
133
+ ...
134
+ </Dropdown1>
135
+ ```
136
+
137
+ **Dialog with custom palette:**
138
+
139
+ ```tsx
140
+ <Dialog1
141
+ open={open}
142
+ onOpenChange={setOpen}
143
+ surface={{
144
+ background: { light: '#f8fafc', dark: '#111827' },
145
+ tokens: { border: { light: '#e2e8f0', dark: '#374151' } },
146
+ }}
147
+ >
148
+ ...
149
+ </Dialog1>
150
+ ```
151
+
152
+ **Custom component in another project:**
153
+
154
+ ```tsx
155
+ function Panel({ surface, children }: { surface?: VelocisSurfaceConfig; children: React.ReactNode }) {
156
+ const { resolvedTheme } = useTheme();
157
+ const props = applySurface(surface, resolvedTheme, {
158
+ className: 'rounded-lg border border-velocis-border p-4 bg-velocis-background',
159
+ });
160
+ return <section {...props}>{children}</section>;
161
+ }
162
+ ```
163
+
164
+ Values can be any valid CSS color: hex, `rgb()`, `hsl()`, `var(--my-brand-surface)`, etc.
165
+
49
166
  ## Hooks
50
167
 
51
168
  | Hook | Returns |
52
169
  |------|---------|
53
- | `useTheme()` | `{ theme, resolvedTheme, setTheme }` |
170
+ | `useTheme()` | `{ theme, resolvedTheme, messages, logger }` |
54
171
  | `useMessages()` | Merged `VelocisMessages` object |
55
172
 
173
+ ## Utilities
174
+
175
+ | Export | Purpose |
176
+ |--------|---------|
177
+ | `resolveSurfaceProps(config, resolvedTheme)` | Build `data-*`, `style`, and `className` for a surface |
178
+ | `applySurface(config, resolvedTheme, elementProps?)` | Merge surface props into an existing element |
179
+ | `resolveColorValue(value, scheme)` | Resolve a token color for light or dark |
180
+ | `buildTokenStyle(tokens, scheme)` | Build inline CSS variable overrides |
181
+
56
182
  ## Design tokens
57
183
 
58
184
  Re-exported from `velocis/theme`:
59
185
 
60
186
  `colors`, `spacing`, `radius`, `typography`, `shadows`, `zIndex`, `animation`, `breakpoints`, `opacity`, `borders`, `gradients`, `iconSizes`
61
187
 
188
+ CSS variables (overridable globally or per surface):
189
+
190
+ `--velocis-color-primary`, `--velocis-color-background`, `--velocis-color-foreground`, `--velocis-color-muted`, `--velocis-color-border`, …
191
+
62
192
  ## When to use
63
193
 
64
194
  - **Always** wrap your app with `VelocisProvider` + `DirectionProvider` before using any Velocis component.
195
+ - Use `tokens` on `VelocisProvider` to match your project's brand in one place.
196
+ - Use `surface` or `<Surface>` when a single component needs its own background or must stay light-only / dark-only.
65
197
  - Use `direction="rtl"` for Arabic, Hebrew, etc.
66
198
  - Use `theme="system"` for automatic dark mode.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "velocis",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "Extremely dynamic RTL/LTR React UI component library",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -101,18 +101,18 @@
101
101
  }
102
102
  },
103
103
  "dependencies": {
104
- "@velocis/card": "0.1.0",
105
104
  "@velocis/core": "0.1.0",
105
+ "@velocis/card": "0.2.0",
106
106
  "@velocis/forms": "0.1.0",
107
- "@velocis/feedback": "0.1.1",
108
- "@velocis/navigation": "0.1.1",
109
- "@velocis/list": "0.1.1",
107
+ "@velocis/feedback": "0.1.2",
108
+ "@velocis/navigation": "0.1.2",
109
+ "@velocis/list": "0.1.2",
110
+ "@velocis/table": "0.1.2",
110
111
  "@velocis/hero": "0.1.0",
111
- "@velocis/table": "0.1.1",
112
- "@velocis/dropdown1": "0.2.1",
113
- "@velocis/table2": "0.2.1",
114
- "@velocis/theme": "0.1.1",
115
- "@velocis/dialog1": "0.2.1"
112
+ "@velocis/table2": "0.2.2",
113
+ "@velocis/dropdown1": "0.4.0",
114
+ "@velocis/dialog1": "0.3.0",
115
+ "@velocis/theme": "0.2.0"
116
116
  },
117
117
  "devDependencies": {
118
118
  "tsup": "^8.3.5",