@ziyarago/ui 0.12.0 → 0.14.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/CHANGELOG.md +12 -0
- package/dist/llms-full.txt +1 -1
- package/dist/llms.txt +1 -1
- package/dist/manifest.json +1 -1
- package/dist/style.css +8 -5
- package/dist/tokens.css +3 -3
- package/docs/INTEGRATION.md +11 -0
- package/package.json +6 -4
- package/src/core/components.js +8 -7
- package/src/core/components2.js +13 -8
- package/src/core/components3.js +3 -2
- package/src/core/components4.js +1 -1
- package/src/core/components6.js +2 -1
- package/src/core/index.js +6 -0
- package/src/core/keys.js +55 -0
- package/src/i18n/components/Progress.js +5 -0
- package/src/i18n/components/index.js +2 -0
- package/src/meta/index.js +13 -0
- package/src/nuxt/module.mjs +9 -1
- package/src/react/index.js +8 -1
- package/src/resolver.js +17 -4
- package/src/styles/components.css +1 -1
- package/src/styles/components2.css +1 -1
- package/src/styles/components8.css +3 -0
- package/src/styles/tokens.css +3 -3
- package/src/vue/index.js +8 -1
- package/tokens/tokens.json +1 -1
- package/types/core.d.ts +3 -0
- package/types/meta.d.ts +3 -0
- package/types/nuxt.d.ts +2 -0
- package/types/react.d.ts +2 -0
- package/types/resolver.d.ts +2 -0
- package/types/vue.d.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [0.14.0](https://github.com/ziyarago/ui/compare/v0.13.0...v0.14.0) (2026-10-21)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* plugin API (registerComponents, defineUiComponents, registerMeta, resolver and Nuxt plugins option) ([ab8b844](https://github.com/ziyarago/ui/commit/ab8b8442a9968cfc0c978d9a5d4c3dc22b28b467))
|
|
6
|
+
|
|
7
|
+
## [0.13.0](https://github.com/ziyarago/ui/compare/v0.12.0...v0.13.0) (2026-10-21)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* accessibility audit with axe (0 violations), keyboard navigation for tabs, selects and menus, readable tooltips ([95a25e1](https://github.com/ziyarago/ui/commit/95a25e1d763f2c63ee788986943aeb3abbb669ae))
|
|
12
|
+
|
|
1
13
|
## [0.12.0](https://github.com/ziyarago/ui/compare/v0.11.0...v0.12.0) (2026-10-21)
|
|
2
14
|
|
|
3
15
|
### Features
|
package/dist/llms-full.txt
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> ZiyaraGo UI — glass design system for Umrah and Hajj products. OKLCH tokens, two palettes, dark and light, RTL, Thmanyah. One framework-neutral core with Vue 3, Nuxt and React adapters, 208 components and 40 page templates.
|
|
4
4
|
|
|
5
|
-
Version 0.
|
|
5
|
+
Version 0.14.0. Docs: https://ziyara.vip . Manifest (machine readable): https://ziyara.vip/manifest.json
|
|
6
6
|
|
|
7
7
|
## Setup
|
|
8
8
|
- import { UiButton } from '@ziyarago/ui/vue'
|
package/dist/llms.txt
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> ZiyaraGo UI — glass design system for Umrah and Hajj products. OKLCH tokens, two palettes, dark and light, RTL, Thmanyah. One framework-neutral core with Vue 3, Nuxt and React adapters, 208 components and 40 page templates.
|
|
4
4
|
|
|
5
|
-
Version 0.
|
|
5
|
+
Version 0.14.0. Docs: https://ziyara.vip . Manifest (machine readable): https://ziyara.vip/manifest.json
|
|
6
6
|
|
|
7
7
|
## Setup
|
|
8
8
|
- import { UiButton } from '@ziyarago/ui/vue'
|
package/dist/manifest.json
CHANGED
package/dist/style.css
CHANGED
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
--zg-s1:oklch(1 0 0 / .055);--zg-s2:oklch(1 0 0 / .085);--zg-s3:oklch(1 0 0 / .13);
|
|
44
44
|
--zg-glass:oklch(0.235 0.006 40 / .92);
|
|
45
45
|
--zg-stroke:oklch(1 0 0 / .08);
|
|
46
|
-
--zg-text-1:oklch(0.97 0.004 70);--zg-text-2:oklch(0.82 0.008 70);--zg-text-3:oklch(0.
|
|
46
|
+
--zg-text-1:oklch(0.97 0.004 70);--zg-text-2:oklch(0.82 0.008 70);--zg-text-3:oklch(0.74 0.01 70);
|
|
47
47
|
--zg-inv-bg:oklch(0.96 0.004 70);--zg-inv-fg:oklch(0.2 0.01 60);
|
|
48
|
-
--zg-green:oklch(0.76 0.15 150);--zg-amber:oklch(0.8 0.14 80);--zg-red:oklch(0.7 0.18 25);
|
|
48
|
+
--zg-green:oklch(0.76 0.15 150);--zg-amber:oklch(0.8 0.14 80);--zg-red:oklch(0.7 0.18 25);--zg-red-text:oklch(0.86 0.1 25);
|
|
49
49
|
--zg-rim:inset 0 1px 0 oklch(1 0 0 / .16),inset 0 0 0 1px oklch(1 0 0 / .07),inset 0 -1px 0 oklch(0 0 0 / .25);
|
|
50
50
|
--zg-shadow-1:0 8px 24px oklch(0 0 0 / .28);--zg-shadow-2:0 24px 64px oklch(0 0 0 / .5);
|
|
51
51
|
}
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
--zg-stroke:oklch(0.3 0.02 60 / .15);
|
|
60
60
|
--zg-text-1:oklch(0.2 0.01 60);--zg-text-2:oklch(0.36 0.012 60);--zg-text-3:oklch(0.47 0.012 60);
|
|
61
61
|
--zg-inv-bg:oklch(0.22 0.01 60);--zg-inv-fg:oklch(0.97 0.004 70);
|
|
62
|
-
--zg-green:oklch(0.5 0.14 150);--zg-amber:oklch(0.55 0.13 70);--zg-red:oklch(0.52 0.19 25);
|
|
62
|
+
--zg-green:oklch(0.5 0.14 150);--zg-amber:oklch(0.55 0.13 70);--zg-red:oklch(0.52 0.19 25);--zg-red-text:oklch(0.44 0.17 25);
|
|
63
63
|
--zg-rim:inset 0 1px 0 oklch(1 0 0 / .95),inset 0 0 0 1px oklch(1 0 0 / .45),0 0 0 1px oklch(0.3 0.02 60 / .13);
|
|
64
64
|
--zg-shadow-1:0 8px 24px oklch(0.4 0.03 60 / .17);--zg-shadow-2:0 24px 64px oklch(0.4 0.03 60 / .22);
|
|
65
65
|
}
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
.zg-btn--primary{background:var(--zg-brand);color:var(--zg-on-brand);box-shadow:inset 0 1px 0 oklch(1 0 0 / .3),0 8px 24px var(--zg-brand-glow)}.zg-btn--primary:hover{background:var(--zg-brand-hover)}
|
|
106
106
|
.zg-btn--inverse{background:var(--zg-inv-bg);color:var(--zg-inv-fg)}.zg-btn--inverse:hover{background:var(--zg-inv-bg);filter:brightness(1.1)}
|
|
107
107
|
.zg-btn--ghost{background:transparent;box-shadow:none}.zg-btn--ghost:hover{background:var(--zg-s2)}
|
|
108
|
-
.zg-btn--danger{background:color-mix(in oklch,var(--zg-red) 18%,transparent);color:var(--zg-red)}
|
|
108
|
+
.zg-btn--danger{background:color-mix(in oklch,var(--zg-red) 18%,transparent);color:var(--zg-red-text)}
|
|
109
109
|
.zg-btn--xs{--h:var(--zg-h-xs);font-size:12px}.zg-btn--sm{--h:var(--zg-h-sm);font-size:13px}.zg-btn--lg{--h:var(--zg-h-lg);font-size:15px}.zg-btn--xl{--h:var(--zg-h-xl);font-size:16px}
|
|
110
110
|
.zg-btn--block{display:flex;width:100%}
|
|
111
111
|
.zg-spin{width:1em;height:1em;border-radius:50%;border:2px solid currentColor;border-inline-end-color:transparent;animation:zg-spin .7s linear infinite}
|
|
@@ -319,7 +319,7 @@
|
|
|
319
319
|
.zg-rc{display:grid;grid-template-columns:56px repeat(var(--d),minmax(0,1fr));gap:2px;align-items:center;direction:ltr;font-size:12px}.zg-rc__h{text-align:center;color:var(--zg-text-3)}.zg-rc__c{height:28px;border-radius:6px;background:var(--zg-s2)}.zg-rc__c[data-b]{background:var(--zg-brand-soft);border-radius:0}.zg-rc__c[data-b="start"]{background:var(--zg-brand);border-radius:999px 0 0 999px}
|
|
320
320
|
.zg-occ{display:flex;align-items:flex-end;gap:8px;height:160px}.zg-occ__c{flex:1;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:6px}.zg-occ__c i{width:100%;border-radius:10px 10px 4px 4px}
|
|
321
321
|
|
|
322
|
-
.zg-kpi--brand .zg-delta{color:inherit;opacity
|
|
322
|
+
.zg-kpi--brand .zg-kpi__label,.zg-kpi--brand .zg-kpi__sub{opacity:.9}.zg-kpi--brand .zg-delta{color:inherit;opacity:1}.zg-kpi--brand .zg-delta::before{opacity:.9}
|
|
323
323
|
|
|
324
324
|
|
|
325
325
|
/* ./components3.css */
|
|
@@ -567,6 +567,9 @@
|
|
|
567
567
|
.zg-qb__txt b{font-size:calc(22px*var(--dz,1));font-weight:600;color:var(--zg-text-1)}.zg-qb__txt small{color:var(--zg-text-3)}
|
|
568
568
|
/* Numbers: tabular figures with a plain zero (no slashed or dotted zero) in KPI values, tables and code cells */
|
|
569
569
|
.zg-num,.zg-mono,.zg-kpi__value,.zg-table .num{font-feature-settings:"zero" 0,"tnum" 1}
|
|
570
|
+
.zg-sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
|
|
571
|
+
.zg-select__opt:focus-visible{outline:3px solid var(--zg-brand-soft);outline-offset:-3px;background:var(--zg-s2)}
|
|
572
|
+
.zg-seg button:focus-visible,.zg-tabs button:focus-visible,.zg-code-b pre:focus-visible{outline:3px solid var(--zg-brand-soft);outline-offset:2px}
|
|
570
573
|
|
|
571
574
|
|
|
572
575
|
/* ./sizes/typography.css */
|
package/dist/tokens.css
CHANGED
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
--zg-s1:oklch(1 0 0 / .055);--zg-s2:oklch(1 0 0 / .085);--zg-s3:oklch(1 0 0 / .13);
|
|
13
13
|
--zg-glass:oklch(0.235 0.006 40 / .92);
|
|
14
14
|
--zg-stroke:oklch(1 0 0 / .08);
|
|
15
|
-
--zg-text-1:oklch(0.97 0.004 70);--zg-text-2:oklch(0.82 0.008 70);--zg-text-3:oklch(0.
|
|
15
|
+
--zg-text-1:oklch(0.97 0.004 70);--zg-text-2:oklch(0.82 0.008 70);--zg-text-3:oklch(0.74 0.01 70);
|
|
16
16
|
--zg-inv-bg:oklch(0.96 0.004 70);--zg-inv-fg:oklch(0.2 0.01 60);
|
|
17
|
-
--zg-green:oklch(0.76 0.15 150);--zg-amber:oklch(0.8 0.14 80);--zg-red:oklch(0.7 0.18 25);
|
|
17
|
+
--zg-green:oklch(0.76 0.15 150);--zg-amber:oklch(0.8 0.14 80);--zg-red:oklch(0.7 0.18 25);--zg-red-text:oklch(0.86 0.1 25);
|
|
18
18
|
--zg-rim:inset 0 1px 0 oklch(1 0 0 / .16),inset 0 0 0 1px oklch(1 0 0 / .07),inset 0 -1px 0 oklch(0 0 0 / .25);
|
|
19
19
|
--zg-shadow-1:0 8px 24px oklch(0 0 0 / .28);--zg-shadow-2:0 24px 64px oklch(0 0 0 / .5);
|
|
20
20
|
}
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
--zg-stroke:oklch(0.3 0.02 60 / .15);
|
|
29
29
|
--zg-text-1:oklch(0.2 0.01 60);--zg-text-2:oklch(0.36 0.012 60);--zg-text-3:oklch(0.47 0.012 60);
|
|
30
30
|
--zg-inv-bg:oklch(0.22 0.01 60);--zg-inv-fg:oklch(0.97 0.004 70);
|
|
31
|
-
--zg-green:oklch(0.5 0.14 150);--zg-amber:oklch(0.55 0.13 70);--zg-red:oklch(0.52 0.19 25);
|
|
31
|
+
--zg-green:oklch(0.5 0.14 150);--zg-amber:oklch(0.55 0.13 70);--zg-red:oklch(0.52 0.19 25);--zg-red-text:oklch(0.44 0.17 25);
|
|
32
32
|
--zg-rim:inset 0 1px 0 oklch(1 0 0 / .95),inset 0 0 0 1px oklch(1 0 0 / .45),0 0 0 1px oklch(0.3 0.02 60 / .13);
|
|
33
33
|
--zg-shadow-1:0 8px 24px oklch(0.4 0.03 60 / .17);--zg-shadow-2:0 24px 64px oklch(0.4 0.03 60 / .22);
|
|
34
34
|
}
|
package/docs/INTEGRATION.md
CHANGED
|
@@ -151,3 +151,14 @@ React types: `Ui<Name>Props` (props, callbacks, named slots as `ReactNode` props
|
|
|
151
151
|
## 7. Checking the types yourself
|
|
152
152
|
|
|
153
153
|
`npm run test:types` in this repository runs vue-tsc and tsc over `tests/types/*`: valid usage must compile, invalid enum / missing required prop / wrong event payload / wrong model type / unknown slot / wrong slot type must fail with the expected message.
|
|
154
|
+
|
|
155
|
+
## 8. Add-on packages (plugins)
|
|
156
|
+
|
|
157
|
+
An add-on package (for example `@ziyarago/ui-pro`) plugs components into the same registry through `registerComponents` / `defineUiComponents`, so its definitions can use `C.Button` and the core components can reference them. A plugin ships its own stylesheet: import it after the core one.
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import '@ziyarago/ui/style.css'
|
|
161
|
+
import '@ziyarago/ui-pro/style.css' // plugin css, always after style.css
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Plugin CSS must only use the public design tokens (`--zg-*`, see `@ziyarago/ui/tokens.css`) and its own prefixed classes. Auto-import: pass `plugins: ['@ziyarago/ui-pro']` to the Nuxt module options (`ziyarago: { plugins: [...] }`) or to `ZiyaragoUiResolver({ plugins: [...] })`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziyarago/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "ZiyaraGo UI — glass design system for Umrah and Hajj products. OKLCH tokens, two palettes, dark and light, RTL, Thmanyah. One framework-neutral core with Vue 3, Nuxt and React adapters, 208 components and 40 page templates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui",
|
|
@@ -115,7 +115,8 @@
|
|
|
115
115
|
"test:browser": "node tests/browser/run.mjs && node tests/browser/interactions.mjs",
|
|
116
116
|
"prepack": "npm run build && npm run check",
|
|
117
117
|
"pack:dry": "npm pack --dry-run",
|
|
118
|
-
"test:types": "node scripts/typecheck-fixtures.mjs"
|
|
118
|
+
"test:types": "node scripts/typecheck-fixtures.mjs",
|
|
119
|
+
"a11y": "node scripts/a11y.mjs"
|
|
119
120
|
},
|
|
120
121
|
"engines": {
|
|
121
122
|
"node": ">=18"
|
|
@@ -123,8 +124,8 @@
|
|
|
123
124
|
"peerDependencies": {
|
|
124
125
|
"@nuxt/kit": ">=3.0.0",
|
|
125
126
|
"react": ">=18",
|
|
126
|
-
"
|
|
127
|
-
"
|
|
127
|
+
"react-dom": ">=18",
|
|
128
|
+
"vue": ">=3.5"
|
|
128
129
|
},
|
|
129
130
|
"peerDependenciesMeta": {
|
|
130
131
|
"vue": {
|
|
@@ -152,6 +153,7 @@
|
|
|
152
153
|
"@types/react": "^19.3.0",
|
|
153
154
|
"@vitejs/plugin-react": "^4.7.0",
|
|
154
155
|
"@vitejs/plugin-vue": "^5.2.4",
|
|
156
|
+
"axe-core": "^4.14.0",
|
|
155
157
|
"conventional-changelog-conventionalcommits": "^9.3.1",
|
|
156
158
|
"esbuild": "^0.28.2",
|
|
157
159
|
"puppeteer-core": "^23.11.1",
|
package/src/core/components.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// h(tag, props, ...children) is supplied by the Vue / React adapter. No hooks inside.
|
|
4
4
|
import { trap } from './dialog.js';
|
|
5
5
|
import { t } from '../i18n/index.js';
|
|
6
|
+
import { rove, tabIdx, listKeys, openKeys, focusIn, toTrigger, trigger } from './keys.js';
|
|
6
7
|
export const cx = (...a) => a.filter(Boolean).join(' ');
|
|
7
8
|
// Copies the listed props that are set; used to forward native attributes and listeners to the real <input>/<textarea>.
|
|
8
9
|
export const pick = (p, keys) => { const o = {}; for (const k of keys) if (p[k] !== undefined) o[k] = p[k]; return o; };
|
|
@@ -27,7 +28,7 @@ const ddSync = () => {
|
|
|
27
28
|
};
|
|
28
29
|
export const ddRef = (close, o = {}) => el => {
|
|
29
30
|
if (typeof document === 'undefined') return;
|
|
30
|
-
if (el) { const r = ddReg.find(x => x.el === el); if (r) { r.close = close; r.self = !!o.self; } else { ddReg.push({ el, close, self: !!o.self }); if (!o.self && el.scrollIntoView) el.scrollIntoView({ block: 'nearest', inline: 'nearest' }); } ddSync(); }
|
|
31
|
+
if (el) { const r = ddReg.find(x => x.el === el); if (r) { r.close = close; r.self = !!o.self; } else { ddReg.push({ el, close, self: !!o.self }); if (!o.self && el.scrollIntoView) el.scrollIntoView({ block: 'nearest', inline: 'nearest' }); if (o.focus) focusIn(el); } ddSync(); }
|
|
31
32
|
else Promise.resolve().then(ddPrune);
|
|
32
33
|
};
|
|
33
34
|
export const dzp = (p, b) => Math.round(b * (DZ[p.size] || 1));
|
|
@@ -66,16 +67,16 @@ export const components = {
|
|
|
66
67
|
return h('button', { type: 'button', role: 'switch', class: 'zg-switch', 'aria-checked': String(!!p.modelValue), disabled: p.disabled, 'aria-disabled': p.disabled ? 'true' : undefined, onClick: () => !p.disabled && p.onChange && p.onChange(!p.modelValue) }, ...(p.labelLeft ? [txt, tr] : [tr, txt])); } },
|
|
67
68
|
Checkbox: { render: ({ h, props: p }) => h('button', { type: 'button', role: 'checkbox', class: cx('zg-check', p.error && 'zg-check--error'), 'aria-checked': p.indeterminate ? 'mixed' : String(!!p.modelValue), disabled: p.disabled, 'aria-disabled': p.disabled ? 'true' : undefined, onClick: () => !p.disabled && p.onChange && p.onChange(p.indeterminate ? true : !p.modelValue) }, h('span', { class: 'zg-check__box' }, p.indeterminate ? icon(h, 'minus', 14) : p.modelValue ? icon(h, 'check', 14) : null),
|
|
68
69
|
p.label || p.hint || p.error ? h('span', null, p.label || null, p.hint && !p.error ? h('span', { class: 'zg-hint', style: { display: 'block' } }, p.hint) : null, p.error && typeof p.error === 'string' ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert', style: { display: 'block' } }, p.error) : null) : null) },
|
|
69
|
-
SegmentedControl: { render: ({ h, props: p }) => h('div', { class: cx('zg-seg', p.size === 'sm' && 'zg-seg--sm', p.block && 'zg-seg--block'), role: 'group', 'aria-label': p.ariaLabel }, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', disabled: it.disabled, title: p.iconOnly ? it.label : undefined, 'aria-label': p.iconOnly && it.icon ? it.label : undefined, 'aria-pressed': String(it.value === p.modelValue), onClick: () => !it.disabled && p.onChange && p.onChange(it.value) }, it.icon ? icon(h, it.icon, 16) : null, p.iconOnly && it.icon ? null : it.label))) },
|
|
70
|
-
Tabs: { render: ({ h, props: p }) => h('div', { class: 'zg-tabs', role: 'tablist' }, ...(p.items || []).map(it => h('button', { key: it.value, role: 'tab', 'aria-selected': String(it.value === p.modelValue), onClick: () => p.onChange && p.onChange(it.value) }, it.label))) },
|
|
70
|
+
SegmentedControl: { render: ({ h, props: p }) => h('div', { class: cx('zg-seg', p.size === 'sm' && 'zg-seg--sm', p.block && 'zg-seg--block'), role: 'group', 'aria-label': p.ariaLabel, onKeydown: rove('button') }, ...(p.items || []).map((it, i, all) => h('button', { key: it.value, type: 'button', tabIndex: tabIdx(all, x => x.value === p.modelValue)(i), disabled: it.disabled, title: p.iconOnly ? it.label : undefined, 'aria-label': p.iconOnly && it.icon ? it.label : undefined, 'aria-pressed': String(it.value === p.modelValue), onClick: () => !it.disabled && p.onChange && p.onChange(it.value) }, it.icon ? icon(h, it.icon, 16) : null, p.iconOnly && it.icon ? null : it.label))) },
|
|
71
|
+
Tabs: { render: ({ h, props: p }) => h('div', { class: 'zg-tabs', role: 'tablist', onKeydown: rove('[role=tab]') }, ...(p.items || []).map((it, i, all) => h('button', { key: it.value, role: 'tab', type: 'button', tabIndex: tabIdx(all, x => x.value === p.modelValue)(i), disabled: it.disabled, 'aria-selected': String(it.value === p.modelValue), onClick: () => p.onChange && p.onChange(it.value) }, it.label))) },
|
|
71
72
|
FilterChips: { render: ({ h, props: p }) => { const sel = p.multi ? (p.modelValue || []) : [p.modelValue]; return h('div', { class: 'zg-chips' }, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', class: 'zg-chip', 'aria-pressed': String(sel.includes(it.value)), onClick: () => { if (!p.onChange) return; p.onChange(p.multi ? (sel.includes(it.value) ? sel.filter(v => v !== it.value) : [...sel, it.value]) : it.value); } }, it.label))); } },
|
|
72
73
|
TextField: { slots: ['prefix', 'suffix'], render: ({ h, props: p, slots }) => h('label', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label, p.required ? h('span', { class: 'zg-req', 'aria-hidden': 'true' }, ' *') : null) : null,
|
|
73
74
|
h('span', { class: cx('zg-input', p.error && 'zg-input--error', p.variant && 'zg-input--' + p.variant, p.density === 'compact' && 'zg-input--compact', p.disabled && 'zg-input--disabled') }, slots.prefix || (p.icon ? icon(h, p.icon, 18) : null), h('input', { ...pick(p, INPUT_ATTRS), type: p.type || 'text', value: p.modelValue ?? '', placeholder: p.placeholder, disabled: p.disabled, readOnly: p.readonly, onInput: e => { p.onChange && p.onChange(e.target.value); p.onInput && p.onInput(e); } }), slots.suffix || p.suffix || null),
|
|
74
75
|
p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null) },
|
|
75
76
|
Select: { state: { open: false }, render: ({ h, props: p, state, set }) => { const cur = (p.options || []).find(o => o.value === p.modelValue), open = state.open && !p.disabled;
|
|
76
77
|
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label, p.required ? h('span', { class: 'zg-req', 'aria-hidden': 'true' }, ' *') : null) : null,
|
|
77
|
-
h('div', { class: 'zg-select' }, h('button', { type: 'button', class: cx('zg-btn', 'zg-select__trigger', p.error && 'zg-select__trigger--error'), 'aria-haspopup': 'listbox', 'aria-expanded': String(open), disabled: p.disabled, style: { height: 'var(--zg-h-lg)' }, onClick: () => set({ open: !state.open }) }, h('span', null, cur ? cur.label : p.placeholder || '—'), icon(h, open ? 'chevron-up' : 'chevron-down', 16)),
|
|
78
|
-
open ? h('div', { class: 'zg-select__list', role: 'listbox', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...(p.options || []).map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === p.modelValue), onClick:
|
|
78
|
+
h('div', { class: 'zg-select' }, h('button', { type: 'button', class: cx('zg-btn', 'zg-select__trigger', p.error && 'zg-select__trigger--error'), 'aria-haspopup': 'listbox', 'aria-expanded': String(open), disabled: p.disabled, style: { height: 'var(--zg-h-lg)' }, onClick: () => set({ open: !state.open }), onKeydown: openKeys(open, () => set({ open: true })) }, h('span', null, cur ? cur.label : p.placeholder || '—'), icon(h, open ? 'chevron-up' : 'chevron-down', 16)),
|
|
79
|
+
open ? h('div', { class: 'zg-select__list', role: 'listbox', 'aria-label': p.label || p.placeholder, 'data-zg-dd': '', ref: ddRef(() => set({ open: false }), { focus: true }), onKeydown: listKeys('[role=option]', { onTab: e => { const t = trigger(e.currentTarget); t && t.focus(); set({ open: false }); } }) }, ...(p.options || []).map((o, i, all) => h('button', { key: o.value, type: 'button', role: 'option', tabIndex: tabIdx(all, x => x.value === p.modelValue)(i), class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === p.modelValue), onClick: e => { toTrigger(e); set({ open: false }); p.onChange && p.onChange(o.value); } }, o.label, o.value === p.modelValue ? icon(h, 'check', 16) : null))) : null),
|
|
79
80
|
p.name ? h('input', { type: 'hidden', name: p.name, value: p.modelValue ?? '' }) : null, p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null); } },
|
|
80
81
|
CodeInput: { render: ({ h, props: p }) => { const n = p.length || 6, v = p.modelValue || '';
|
|
81
82
|
return h('div', { class: 'zg-code', style: { '--n': n } }, ...Array.from({ length: n }, (_, i) => h('span', { key: i, class: cx('zg-code__cell', i === v.length && 'zg-code__cell--on') }, v[i] || '')),
|
|
@@ -95,7 +96,7 @@ export const components = {
|
|
|
95
96
|
return h('nav', { class: 'zg-pager', 'aria-label': p.label || t('Pagination', 'label') }, p.hideArrows ? null : h('button', { key: 'prev', type: 'button', class: 'zg-pager__nav', 'aria-label': (p.labels && p.labels.prev) || t('Pagination', 'prev'), disabled: cur <= 1, onClick: () => go(cur - 1) }, icon(h, 'chevron-left', 16)),
|
|
96
97
|
...items.map((n, i) => n === '…' ? h('span', { key: 'e' + i, class: 'zg-pager__gap', 'aria-hidden': 'true' }, '…') : h('button', { key: n, type: 'button', 'aria-label': ((p.labels && p.labels.page) || t('Pagination', 'page')) + ' ' + n, 'aria-current': n === p.page ? 'page' : undefined, onClick: () => go(n) }, n)),
|
|
97
98
|
p.hideArrows ? null : h('button', { key: 'next', type: 'button', class: 'zg-pager__nav', 'aria-label': (p.labels && p.labels.next) || t('Pagination', 'next'), disabled: cur >= total, onClick: () => go(cur + 1) }, icon(h, 'chevron-right', 16))); } },
|
|
98
|
-
Progress: { render: ({ h, props: p }) => h('div', { class: cx('zg-progress', p.tone && 'zg-progress--' + p.tone) }, (p.label || p.meta) ? h('div', { class: 'zg-progress__row' }, h('span', null, p.label), h('span', { class: 'zg-mono' }, p.meta)) : null, h('div', { class: 'zg-progress__track', role: 'progressbar', 'aria-valuenow': p.value, 'aria-valuemin': 0, 'aria-valuemax': 100 }, h('div', { class: 'zg-progress__bar', style: { width: Math.max(0, Math.min(100, p.value || 0)) + '%' } }))) },
|
|
99
|
+
Progress: { render: ({ h, props: p }) => h('div', { class: cx('zg-progress', p.tone && 'zg-progress--' + p.tone) }, (p.label || p.meta) ? h('div', { class: 'zg-progress__row' }, h('span', null, p.label), h('span', { class: 'zg-mono' }, p.meta)) : null, h('div', { class: 'zg-progress__track', role: 'progressbar', 'aria-label': p.label || t('Progress', 'label'), 'aria-valuenow': p.value, 'aria-valuemin': 0, 'aria-valuemax': 100 }, h('div', { class: 'zg-progress__bar', style: { width: Math.max(0, Math.min(100, p.value || 0)) + '%' } }))) },
|
|
99
100
|
Ring: { render: ({ h, props: p }) => { const s = { xs: 72, sm: 120, md: 180, lg: 240, xl: 300 }[p.size] || (Number(p.size) > 0 ? Number(p.size) : 180), th = p.thickness || Math.max(8, Math.round(s * .078)), ticks = p.ticks !== false, v = Math.max(0, Math.min(100, p.value || 0)) / 100, col = p.color || 'var(--zg-brand)';
|
|
100
101
|
const r = (s - th) / 2 - (ticks ? 12 : 0), cir = 2 * Math.PI * r, tk = [];
|
|
101
102
|
if (ticks) for (let i = 0; i < 60; i++) { const an = i / 60 * 2 * Math.PI, R = s / 2 - 2, L = i % 5 ? 4 : 8; tk.push(h('line', { key: i, x1: +(s / 2 + Math.cos(an) * R).toFixed(2), y1: +(s / 2 + Math.sin(an) * R).toFixed(2), x2: +(s / 2 + Math.cos(an) * (R - L)).toFixed(2), y2: +(s / 2 + Math.sin(an) * (R - L)).toFixed(2), stroke: i / 60 <= v ? col : 'var(--zg-s3)', 'stroke-width': i % 5 ? 1 : 1.6, 'stroke-linecap': 'round' })); }
|
|
@@ -106,7 +107,7 @@ export const components = {
|
|
|
106
107
|
KpiTile: { render: ({ h, props: p }) => h('div', { class: cx('zg-kpi', p.variant && 'zg-kpi--' + p.variant) }, h('span', { class: 'zg-kpi__label' }, p.label), h('span', { class: 'zg-kpi__value zg-num' }, p.value), (p.sub || p.delta) ? h('span', { class: 'zg-kpi__sub' }, p.delta ? h('span', { class: cx('zg-delta', String(p.delta).startsWith('-') ? 'zg-delta--down' : 'zg-delta--up') }, p.delta + ' ') : null, p.sub) : null) },
|
|
107
108
|
DataTable: { render: ({ h, props: p, slots }) => { const cs = c => (c.align || c.width) ? { textAlign: c.align, width: c.width } : undefined, cols = p.columns || [], rows = p.rows || [], sel = p.selected || [], all = rows.length && rows.every(r => sel.includes(r.id));
|
|
108
109
|
const th = c => h('th', { key: c.key, 'aria-sort': p.sortKey === c.key ? (p.sortDir === 'desc' ? 'descending' : 'ascending') : undefined, class: c.num ? 'num' : undefined, style: cs(c) }, c.sortable ? h('button', { type: 'button', onClick: () => p.onSort && p.onSort(c.key, p.sortKey === c.key && p.sortDir !== 'desc' ? 'desc' : 'asc') }, c.label, p.sortKey === c.key ? icon(h, p.sortDir === 'desc' ? 'arrow-down' : 'arrow-up', 12) : null) : c.label);
|
|
109
|
-
return h('div', { class: 'zg-table-wrap' }, h('table', { class: 'zg-table' }, h('thead', null, h('tr', null, p.onSelect ? h('th', { style: { width: '44px' } }, h('button', { type: 'button', role: 'checkbox', class: 'zg-check', 'aria-checked': String(!!all), 'aria-label': (p.labels && p.labels.selectAll) || t('DataTable', 'selectAll'), onClick: () => p.onSelect(all ? [] : rows.map(r => r.id)) }, h('span', { class: 'zg-check__box' }, all ? icon(h, 'check', 14) : null))) : null, ...cols.map(th))),
|
|
110
|
+
return h('div', { class: 'zg-table-wrap', tabIndex: 0 }, h('table', { class: 'zg-table' }, h('thead', null, h('tr', null, p.onSelect ? h('th', { style: { width: '44px' } }, h('button', { type: 'button', role: 'checkbox', class: 'zg-check', 'aria-checked': String(!!all), 'aria-label': (p.labels && p.labels.selectAll) || t('DataTable', 'selectAll'), onClick: () => p.onSelect(all ? [] : rows.map(r => r.id)) }, h('span', { class: 'zg-check__box' }, all ? icon(h, 'check', 14) : null))) : null, ...cols.map(th))),
|
|
110
111
|
h('tbody', null, ...(rows.length ? rows.map((r, ri) => h('tr', { key: r.id, 'aria-selected': sel.includes(r.id) ? 'true' : undefined, class: p.onRowClick ? 'zg-tr--click' : undefined, onClick: p.onRowClick ? () => p.onRowClick(r, ri) : undefined }, p.onSelect ? h('td', null, h('button', { type: 'button', role: 'checkbox', class: 'zg-check', 'aria-checked': String(sel.includes(r.id)), 'aria-label': (p.labels && p.labels.selectRow) || t('DataTable', 'selectRow'), onClick: () => p.onSelect(sel.includes(r.id) ? sel.filter(i => i !== r.id) : [...sel, r.id]) }, h('span', { class: 'zg-check__box' }, sel.includes(r.id) ? icon(h, 'check', 14) : null))) : null, ...cols.map(c => h('td', { key: c.key, class: c.num ? 'num' : undefined, style: cs(c) }, c.render ? c.render(r[c.key], r, h) : (r[c.key] ?? '—'))))) : [h('tr', { key: 'e' }, h('td', { colSpan: cols.length + 1 }, h('div', { class: 'zg-empty' }, slots.empty || p.empty || '—')))])))); } },
|
|
111
112
|
KanbanColumn: { render: ({ h, props: p, children }) => h('div', { class: 'zg-kcol zg-dm' }, h('div', { class: 'zg-kcol__head' }, p.title, h('span', { class: 'zg-badge' }, p.count ?? children.length)), ...children) },
|
|
112
113
|
KanbanCard: { render: ({ h, props: p, children }) => h('div', { class: 'zg-kcard zg-dm' }, h('b', { style: { fontSize: dzf(14) } }, p.title), p.sub ? h('span', { class: 'zg-hint' }, p.sub) : null, ...children, p.tag ? h('span', { class: cx('zg-badge', p.tone && 'zg-badge--' + p.tone), style: { alignSelf: 'flex-start' } }, p.tag) : null) },
|
package/src/core/components2.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// @ziyarago/ui · components, part 2 — Forms, Surfaces, Actions, Data, Navigation, Layout, CRM, Hotels.
|
|
2
2
|
import { icon, cx, fmt, dzp, dzf, dzb, ddRef, pick, INPUT_ATTRS } from './components.js';
|
|
3
3
|
import { trap } from './dialog.js';
|
|
4
|
+
import { listKeys, openKeys, tabIdx, toTrigger, trigger } from './keys.js';
|
|
5
|
+
const tipId = s => { let x = 5381; for (const c of String(s)) x = (x * 33 ^ c.charCodeAt(0)) >>> 0; return 'zg-tip-' + x.toString(36); };
|
|
6
|
+
let tipN = 0;
|
|
7
|
+
// Same text twice on one page would repeat the id: the later tooltip gets a unique one (browser only, after mount).
|
|
8
|
+
const tipRef = el => { const s = el && el.lastElementChild; if (!s || !s.id || typeof document === 'undefined') return; const same = document.querySelectorAll('[id="' + s.id + '"]'); if (same.length > 1 && same[0] !== s) { s.id += '-' + (++tipN); el.setAttribute('aria-describedby', s.id); } };
|
|
4
9
|
import { t, getUiLocale, intlLocale } from '../i18n/index.js';
|
|
5
10
|
const dloc = p => p.locale || (getUiLocale() === 'en' ? 'en-GB' : intlLocale());
|
|
6
11
|
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -53,16 +58,16 @@ export const more = {
|
|
|
53
58
|
Gallery: { state: { i: 0 }, render: ({ h, props: p, state, set }) => { const im = arr(p.images).filter(Boolean), n = im.length, i = idx(p, state, n), L = p.labels || {};
|
|
54
59
|
const to = k => { if (k === i) return; if (p.index === undefined || p.index === null) set({ i: k }); p.onChange && p.onChange(k); };
|
|
55
60
|
return h('div', { class: 'zg-gal' }, h('div', { class: 'zg-gal__m', style: { backgroundImage: n ? `url(${im[i]})` : undefined }, role: 'img', 'aria-label': p.alt || '' }, n ? h('span', { class: 'zg-badge zg-gal__n' }, `${i + 1} / ${n}`) : null), n ? h('div', { class: 'zg-gal__t' }, ...im.map((u, k) => h('button', { key: k, type: 'button', 'aria-label': (L.photo || t('Gallery', 'photo')) + ' ' + (k + 1), 'aria-current': String(k === i), style: { backgroundImage: `url(${u})` }, onClick: () => to(k) }))) : null); } },
|
|
56
|
-
Code: { render: ({ C, h, props: p }) => h('div', { class: 'zg-code-b' }, h('div', { class: 'zg-code-b__h' }, h('span', null, p.title || p.language || 'code'), h(C.CopyButton, { value: p.code, size: 'xs' })), h('pre', { class: 'zg-mono' }, p.code)) },
|
|
61
|
+
Code: { render: ({ C, h, props: p }) => h('div', { class: 'zg-code-b' }, h('div', { class: 'zg-code-b__h' }, h('span', null, p.title || p.language || 'code'), h(C.CopyButton, { value: p.code, size: 'xs' })), h('pre', { class: 'zg-mono', tabIndex: 0 }, p.code)) },
|
|
57
62
|
PricingCard: { render: ({ C, h, props: p }) => h('div', { class: cx('zg-card', 'zg-glass', p.featured && 'zg-price--on') }, h('div', { class: 'zg-card__head' }, h('h3', { class: 'zg-card__title' }, p.name), p.badge ? h(C.Badge, { tone: 'brand' }, p.badge) : null), h('div', { style: { display: 'flex', alignItems: 'baseline', gap: '6px' } }, h('span', { class: 'zg-num', style: { fontSize: '44px' } }, p.price), h('span', { class: 'zg-hint' }, p.period || '')), h('ul', { class: 'zg-feat' }, ...(p.features || []).map((f, i) => h('li', { key: i }, icon(h, 'check', 16), f))), h(C.Button, { variant: p.featured ? 'primary' : 'secondary', block: true, onClick: p.onSelect }, p.cta || t('PricingCard', 'choose'))) },
|
|
58
63
|
IntegrationCard: { render: ({ C, h, props: p }) => { const st = ['connected', 'error'].includes(p.status) ? p.status : 'off', tone = { connected: 'success', error: 'danger' }[st], L = { connected: t('IntegrationCard', 'connected'), error: t('IntegrationCard', 'error'), off: t('IntegrationCard', 'off'), manage: t('IntegrationCard', 'manage'), reconnect: t('IntegrationCard', 'reconnect'), link: t('IntegrationCard', 'link'), ...(p.labels || {}) };
|
|
59
64
|
return h('div', { class: 'zg-card zg-glass' }, h('div', { class: 'zg-card__head' }, h('span', { class: 'zg-tl__dot', style: { margin: '8px 4px 0' } }), h('div', { style: { flex: 1 } }, h('h3', { class: 'zg-card__title' }, p.name), h('div', { class: 'zg-card__sub' }, p.description)), h(C.Badge, { tone, dot: true }, L[st])), h(C.Button, { variant: st === 'connected' ? 'secondary' : 'primary', size: 'sm', onClick: p.onToggle }, st === 'connected' ? L.manage : st === 'error' ? L.reconnect : L.link)); } },
|
|
60
65
|
IncomingCall: { render: ({ C, h, props: p }) => h('div', { class: 'zg-inc zg-glass zg-dm' }, h(C.Avatar, { name: p.name, size: dzp(p, 48) }), h('div', { style: { flex: 1, minWidth: 0 } }, h('div', { class: 'zg-hint' }, p.label || t('IncomingCall', 'incoming')), h('b', { style: { fontSize: dzf(16) } }, p.name), h('div', { class: 'zg-mono zg-hint' }, p.phone)), h(C.IconButton, { icon: 'phone-off', label: t('IncomingCall', 'decline'), size: p.size ? p.size : undefined, onClick: p.onDecline }), h('button', { type: 'button', class: 'zg-ibtn zg-inc__ok', 'aria-label': t('IncomingCall', 'answer'), onClick: p.onAnswer }, icon(h, 'phone'))) },
|
|
61
66
|
CallCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-card zg-glass zg-dm' }, h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.name, size: dzp(p, 36) }), h('div', { style: { flex: 1, minWidth: 0 } }, h('b', { style: { fontSize: dzf(14) } }, p.name), h('div', { class: 'zg-hint zg-mono' }, p.phone)), p.status ? h(C.Badge, { tone: { ...CALL_TONES, ...(p.statusTones || {}) }[String(p.status).toLowerCase()] || (p.statusTones || {})[p.status], dot: true }, p.status) : null), h('div', { class: 'zg-lead__row' }, h(C.IconButton, { icon: 'play', label: (p.labels && p.labels.play) || t('CallCard', 'play'), size: dzb(p), onClick: p.onPlay }), h(C.Progress, { value: num(p.progress) }), h('span', { class: 'zg-mono zg-hint' }, fmt.dur(p.duration, p.durUnits)))) },
|
|
62
67
|
/* ── Actions ── */
|
|
63
|
-
Dropdown: { state: { open: false }, render: ({ h, props: p, state, set }) => h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: cx('zg-btn', p.size && 'zg-btn--' + p.size), 'aria-haspopup': 'menu', 'aria-expanded': String(state.open && !p.disabled), disabled: p.disabled, onClick: () => set({ open: !state.open }) }, p.icon ? icon(h, p.icon, 16) : null, p.label, icon(h, 'chevron-down', 14)), state.open && !p.disabled ? h('div', { class: 'zg-menu', role: 'menu', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...(p.items || []).map((it, i) => it.separator ? h('div', { key: i, class: 'zg-menu__sep' }) : h('button', { key: i, type: 'button', role: 'menuitem', disabled: it.disabled, 'aria-disabled': it.disabled ? 'true' : undefined, class: cx('zg-menu__i', it.tone === 'danger' && 'zg-menu__i--danger'), onClick:
|
|
64
|
-
Menu: { render: ({ h, props: p }) => p.open === false ? null : h('div', { class: 'zg-menu zg-menu--static', role: 'menu', 'data-zg-dd': '', ref: p.open === true && p.onClose ? ddRef(p.onClose, { self: true }) : undefined }, ...(p.items || []).map((it, i) => it.separator ? h('div', { key: i, class: 'zg-menu__sep' }) : h('button', { key: i, type: 'button', role: 'menuitem', disabled: it.disabled, 'aria-disabled': it.disabled ? 'true' : undefined, class: cx('zg-menu__i', it.tone === 'danger' && 'zg-menu__i--danger'), onClick: () => { it.onClick && it.onClick(it); p.onSelect && p.onSelect(it.value); } }, it.icon ? icon(h, it.icon, 16) : null, it.label, it.shortcut ? h('span', { class: 'zg-kbd' }, it.shortcut) : null))) },
|
|
65
|
-
Tooltip: { render: ({ h, props: p, children }) => h('span', { class: cx('zg-tip', p.placement && p.placement !== 'top' && 'zg-tip--' + p.placement), 'data-tip': p.text, tabIndex: 0 }, ...children) },
|
|
68
|
+
Dropdown: { state: { open: false }, render: ({ h, props: p, state, set }) => h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: cx('zg-btn', p.size && 'zg-btn--' + p.size), 'aria-haspopup': 'menu', 'aria-expanded': String(state.open && !p.disabled), disabled: p.disabled, onClick: () => set({ open: !state.open }), onKeydown: openKeys(state.open && !p.disabled, () => set({ open: true })) }, p.icon ? icon(h, p.icon, 16) : null, p.label, icon(h, 'chevron-down', 14)), state.open && !p.disabled ? h('div', { class: 'zg-menu', role: 'menu', 'data-zg-dd': '', ref: ddRef(() => set({ open: false }), { focus: true }), onKeydown: listKeys('[role=menuitem]', { wrap: true, onTab: e => { const t = trigger(e.currentTarget); t && t.focus(); set({ open: false }); } }) }, ...(p.items || []).map((it, i, all) => it.separator ? h('div', { key: i, class: 'zg-menu__sep', role: 'separator' }) : h('button', { key: i, type: 'button', role: 'menuitem', tabIndex: tabIdx(all.map(x => ({ disabled: x.separator || x.disabled })), () => false)(i), disabled: it.disabled, 'aria-disabled': it.disabled ? 'true' : undefined, class: cx('zg-menu__i', it.tone === 'danger' && 'zg-menu__i--danger'), onClick: e => { toTrigger(e); set({ open: false }); it.onClick && it.onClick(it); p.onSelect && p.onSelect(it.value); } }, it.icon ? icon(h, it.icon, 16) : null, it.label, it.shortcut ? h('span', { class: 'zg-kbd' }, it.shortcut) : null))) : null) },
|
|
69
|
+
Menu: { render: ({ h, props: p }) => p.open === false ? null : h('div', { class: 'zg-menu zg-menu--static', role: 'menu', 'data-zg-dd': '', ref: p.open === true && p.onClose ? ddRef(p.onClose, { self: true, focus: true }) : undefined, onKeydown: listKeys('[role=menuitem]', { wrap: true, onTab: () => p.onClose && p.onClose() }) }, ...(p.items || []).map((it, i, all) => it.separator ? h('div', { key: i, class: 'zg-menu__sep', role: 'separator' }) : h('button', { key: i, type: 'button', role: 'menuitem', tabIndex: tabIdx(all.map(x => ({ disabled: x.separator || x.disabled })), () => false)(i), disabled: it.disabled, 'aria-disabled': it.disabled ? 'true' : undefined, class: cx('zg-menu__i', it.tone === 'danger' && 'zg-menu__i--danger'), onClick: () => { it.onClick && it.onClick(it); p.onSelect && p.onSelect(it.value); } }, it.icon ? icon(h, it.icon, 16) : null, it.label, it.shortcut ? h('span', { class: 'zg-kbd' }, it.shortcut) : null))) },
|
|
70
|
+
Tooltip: { render: ({ h, props: p, children }) => h('span', { class: cx('zg-tip', p.placement && p.placement !== 'top' && 'zg-tip--' + p.placement), 'data-tip': p.text, tabIndex: 0, 'aria-describedby': p.text ? tipId(p.text) : undefined, ref: p.text ? tipRef : undefined }, ...children, p.text ? h('span', { id: tipId(p.text), role: 'tooltip', class: 'zg-sr' }, p.text) : null) },
|
|
66
71
|
HoverCard: { render: ({ h, props: p, children }) => h('span', { class: 'zg-hc', tabIndex: 0 }, h('span', null, p.trigger), h('div', { class: 'zg-hc__c zg-glass' }, ...children)) },
|
|
67
72
|
Popover: { state: { open: false }, render: ({ C, h, props: p, state, set, children }) => h('span', { class: 'zg-dd' }, h(C.Button, { size: p.size, onClick: () => set({ open: !state.open }) }, p.label), state.open ? h('div', { class: 'zg-pop zg-glass', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...children) : null) },
|
|
68
73
|
CopyButton: { state: { done: false, err: false }, render: ({ C, h, props: p, state, set }) => { const reset = () => setTimeout(() => set({ done: false, err: false }), 1500), fail = e => { set({ done: false, err: true }); p.onError && p.onError(e); reset(); }, ok = () => { set({ done: true, err: false }); p.onCopy && p.onCopy(p.value); reset(); };
|
|
@@ -102,20 +107,20 @@ export const more = {
|
|
|
102
107
|
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-fdrop', role: 'button', tabIndex: 0, 'aria-label': p.title || t('FileUpload', 'title'), 'data-drag': state.drag ? '' : undefined, onClick: pick, onKeydown: e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); pick(e); } }, onDragenter: e => { e.preventDefault(); if (!state.drag) set({ drag: true }); }, onDragover: e => { e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'; if (!state.drag) set({ drag: true }); }, onDragleave: e => { if (!e.currentTarget.contains(e.relatedTarget)) set({ drag: false }); }, onDrop: e => { e.preventDefault(); set({ drag: false }); take([...(e.dataTransfer ? e.dataTransfer.files : [])]); } }, icon(h, p.image ? 'image-up' : 'upload', 24), h('b', null, p.title || t('FileUpload', 'title')), h('span', { class: 'zg-hint' }, p.hint || t('FileUpload', 'hint')), h('input', { type: 'file', multiple: p.multiple, accept: p.accept, tabIndex: -1, 'aria-hidden': 'true', style: { display: 'none' }, onClick: e => e.stopPropagation(), onChange: e => { take([...e.target.files]); e.target.value = ''; } })), p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : null); } },
|
|
103
108
|
Combobox: { state: { open: false, q: '' }, render: ({ h, props: p, state, set }) => { const cur = (p.options || []).find(o => o.value === p.modelValue), f = (p.options || []).filter(o => !state.q || (p.filter ? p.filter(o, state.q) : o.label.toLowerCase().includes(state.q.toLowerCase())));
|
|
104
109
|
return lab(h, p, h('div', { class: 'zg-select' }, field(h, p, icon(h, 'search', 16), h('input', { value: state.open && !p.disabled ? state.q : (cur ? cur.label : ''), disabled: p.disabled, placeholder: p.placeholder || t('Combobox', 'placeholder'), onFocus: () => set({ open: true, q: '' }), onInput: e => set({ q: e.target.value, open: true }) }), [p.clearable && cur && !p.disabled ? h('button', { key: 'x', type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('Combobox', 'clear'), onMouseDown: e => e.preventDefault(), onClick: () => { set({ open: false, q: '' }); p.onChange && p.onChange(null); } }, icon(h, 'x', 14)) : null, icon(h, 'chevron-down', 16)]),
|
|
105
|
-
state.open && !p.disabled ? h('div', { class: 'zg-select__list', role: 'listbox', 'data-zg-dd': '', ref: ddRef(() => set({ open: false, q: '' })) }, ...(f.length ? f.map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === p.modelValue), onMouseDown: e => e.preventDefault(), onClick: () => { set({ open: false, q: '' }); p.onChange && p.onChange(o.value); } }, o.label)) : [h('div', { key: 'e', class: 'zg-empty' }, p.emptyText || '—')])) : null)); } },
|
|
110
|
+
state.open && !p.disabled ? h('div', { class: 'zg-select__list', role: 'listbox', 'aria-label': p.label || p.placeholder, 'data-zg-dd': '', ref: ddRef(() => set({ open: false, q: '' })) }, ...(f.length ? f.map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === p.modelValue), onMouseDown: e => e.preventDefault(), onClick: () => { set({ open: false, q: '' }); p.onChange && p.onChange(o.value); } }, o.label)) : [h('div', { key: 'e', class: 'zg-empty' }, p.emptyText || '—')])) : null)); } },
|
|
106
111
|
MultiSelect: { state: { open: false }, render: ({ h, props: p, state, set }) => { const v = p.modelValue || [], tog = x => p.onChange && p.onChange(v.includes(x) ? v.filter(i => i !== x) : [...v, x]);
|
|
107
112
|
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-select' }, h('button', { type: 'button', class: 'zg-btn zg-select__trigger', 'aria-haspopup': 'listbox', 'aria-expanded': String(state.open && !p.disabled), disabled: p.disabled, style: { height: 'var(--zg-h-lg)' }, onClick: () => set({ open: !state.open }) }, h('span', null, v.length ? t('MultiSelect', 'selected', { n: v.length }) : (p.placeholder || '—')), icon(h, 'chevron-down', 16)),
|
|
108
|
-
state.open && !p.disabled ? h('div', { class: 'zg-select__list', role: 'listbox', 'aria-multiselectable': 'true', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...(p.options || []).map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(v.includes(o.value)), onClick: () => tog(o.value) }, h('span', { class: 'zg-check__box', style: { width: '18px', height: '18px', borderRadius: '6px', background: v.includes(o.value) ? 'var(--zg-brand)' : 'transparent' } }, v.includes(o.value) ? icon(h, 'check', 12) : null), h('span', { style: { flex: 1, marginInlineStart: '10px' } }, o.label)))) : null)); } },
|
|
113
|
+
state.open && !p.disabled ? h('div', { class: 'zg-select__list', role: 'listbox', 'aria-label': p.label || p.placeholder, 'aria-multiselectable': 'true', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...(p.options || []).map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(v.includes(o.value)), onClick: () => tog(o.value) }, h('span', { class: 'zg-check__box', style: { width: '18px', height: '18px', borderRadius: '6px', background: v.includes(o.value) ? 'var(--zg-brand)' : 'transparent' } }, v.includes(o.value) ? icon(h, 'check', 12) : null), h('span', { style: { flex: 1, marginInlineStart: '10px' } }, o.label)))) : null)); } },
|
|
109
114
|
GroupedSelect: { state: { open: false }, render: ({ h, props: p, state, set }) => { const all = (p.groups || []).flatMap(g => g.options), cur = all.find(o => o.value === p.modelValue);
|
|
110
115
|
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-select' }, h('button', { type: 'button', class: 'zg-btn zg-select__trigger', 'aria-haspopup': 'listbox', 'aria-expanded': String(state.open && !p.disabled), disabled: p.disabled, style: { height: 'var(--zg-h-lg)' }, onClick: () => set({ open: !state.open }) }, h('span', null, cur ? cur.label : (p.placeholder || '—')), icon(h, 'chevron-down', 16)),
|
|
111
|
-
state.open && !p.disabled ? h('div', { class: 'zg-select__list', role: 'listbox', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...(p.groups || []).flatMap(g => [h('div', { key: g.label, class: 'zg-sidenav__group', style: { padding: '8px 14px 2px' } }, g.label), ...g.options.map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === p.modelValue), onClick: () => { set({ open: false }); p.onChange && p.onChange(o.value); } }, o.label))])) : null)); } },
|
|
116
|
+
state.open && !p.disabled ? h('div', { class: 'zg-select__list', role: 'listbox', 'aria-label': p.label || p.placeholder, 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...(p.groups || []).flatMap(g => [h('div', { key: g.label, class: 'zg-sidenav__group', style: { padding: '8px 14px 2px' } }, g.label), ...g.options.map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === p.modelValue), onClick: () => { set({ open: false }); p.onChange && p.onChange(o.value); } }, o.label))])) : null)); } },
|
|
112
117
|
PhoneInput: { render: ({ C, h, props: p }) => { const CC = p.countries || [{ value: '+966', label: 'SA +966' }, { value: '+998', label: 'UZ +998' }, { value: '+7', label: 'RU +7' }, { value: '+90', label: 'TR +90' }, { value: '+20', label: 'EG +20' }], v = p.modelValue || {};
|
|
113
118
|
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { style: { display: 'grid', gridTemplateColumns: '130px minmax(0,1fr)', gap: '8px', alignItems: 'start' } }, h(C.Select, { options: CC, modelValue: v.code || CC[0].value, onChange: c => p.onChange && p.onChange({ ...v, code: c }) }), field(h, p, icon(h, 'phone', 16), h('input', { type: 'tel', inputMode: 'tel', value: v.number || '', placeholder: p.placeholder ?? '90 123 45 67', onInput: e => p.onChange && p.onChange({ ...v, number: e.target.value }) }))), typeof p.error === 'string' && p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null); } },
|
|
114
119
|
Calendar: { state: { ym: null }, render: ({ h, props: p, state, set }) => { const s = pd(p.modelValue), b = state.ym ? new Date(state.ym) : (s || new Date()), y = b.getFullYear(), m = b.getMonth(), loc = dloc(p), ws = p.weekStartsOn ?? 1, off = (new Date(y, m, 1).getDay() - ws + 7) % 7, dn = new Date(y, m + 1, 0).getDate(), today = new Date();
|
|
115
120
|
const wd = Array.from({ length: 7 }, (_, i) => new Intl.DateTimeFormat(loc, { weekday: 'narrow' }).format(new Date(2023, 0, 1 + ((i + ws) % 7))));
|
|
116
121
|
const same = (a, d) => a && a.getFullYear() === y && a.getMonth() === m && a.getDate() === d, ev = p.events || {}, lo = pd(p.min), hi = pd(p.max), day0 = x => new Date(x.getFullYear(), x.getMonth(), x.getDate()), blocked = d => { const t = new Date(y, m, d); return !!((lo && t < day0(lo)) || (hi && t > day0(hi)) || (p.disabledDate && p.disabledDate(t))); };
|
|
117
122
|
return h('div', { class: 'zg-cal' }, h('div', { class: 'zg-cal__h' }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('Calendar', 'prev'), onClick: () => set({ ym: new Date(y, m - 1, 1).getTime() }) }, icon(h, 'chevron-left', 16)), h('b', null, new Intl.DateTimeFormat(loc, { month: 'long', year: 'numeric' }).format(b)), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('Calendar', 'next'), onClick: () => set({ ym: new Date(y, m + 1, 1).getTime() }) }, icon(h, 'chevron-right', 16))),
|
|
118
|
-
h('div', { class: 'zg-cal__g' }, ...wd.map((d, i) => h('span', { key: 'w' + i, class: 'zg-cal__w' }, d)), ...Array.from({ length: off }, (_, i) => h('span', { key: 'o' + i })), ...Array.from({ length: dn }, (_, i) => { const d = i + 1, key = `${y}-${String(m + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`; return h('button', { key: d, type: 'button', class: 'zg-cal__d', 'aria-pressed': String(same(s, d)), 'data-today': String(same(today, d)), 'data-ev': ev[key] ? '' : undefined, title: ev[key] || undefined, disabled: blocked(d) || undefined, onClick: () => !blocked(d) && p.onChange && p.onChange(new Date(y, m, d)) }, d); }))); } },
|
|
123
|
+
h('div', { class: 'zg-cal__g' }, ...wd.map((d, i) => h('span', { key: 'w' + i, class: 'zg-cal__w' }, d)), ...Array.from({ length: off }, (_, i) => h('span', { key: 'o' + i })), ...Array.from({ length: dn }, (_, i) => { const d = i + 1, key = `${y}-${String(m + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`; return h('button', { key: d, type: 'button', class: 'zg-cal__d', 'aria-pressed': String(!!same(s, d)), 'data-today': String(same(today, d)), 'data-ev': ev[key] ? '' : undefined, title: ev[key] || undefined, disabled: blocked(d) || undefined, onClick: () => !blocked(d) && p.onChange && p.onChange(new Date(y, m, d)) }, d); }))); } },
|
|
119
124
|
DatePicker: { state: { open: false }, render: ({ C, h, props: p, state, set }) => lab(h, p, h('div', { class: 'zg-select' }, h('button', { type: 'button', class: 'zg-btn zg-select__trigger', 'aria-haspopup': 'dialog', 'aria-expanded': String(state.open && !p.disabled), disabled: p.disabled, style: { height: 'var(--zg-h-lg)' }, onClick: () => set({ open: !state.open }) }, h('span', { style: { display: 'flex', gap: '10px', alignItems: 'center' } }, icon(h, 'calendar', 16), p.modelValue ? new Intl.DateTimeFormat(dloc(p), { dateStyle: 'medium' }).format(pd(p.modelValue) || new Date()) : (p.placeholder || t('DatePicker', 'placeholder'))), h('span', { style: { display: 'flex', alignItems: 'center', gap: '6px' } }, p.clearable && p.modelValue && !p.disabled ? h('span', { role: 'button', tabIndex: 0, class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('DatePicker', 'clear'), onClick: e => { e.stopPropagation(); p.onChange && p.onChange(null); }, onKeydown: e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); p.onChange && p.onChange(null); } } }, icon(h, 'x', 14)) : null, icon(h, 'chevron-down', 16))),
|
|
120
125
|
state.open && !p.disabled ? h('div', { class: 'zg-select__list', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })), style: { padding: '12px' } }, h(C.Calendar, { modelValue: p.modelValue, locale: p.locale, weekStartsOn: p.weekStartsOn, min: p.min, max: p.max, disabledDate: p.disabledDate, onChange: d => { set({ open: false }); p.onChange && p.onChange(d); } })) : null)) },
|
|
121
126
|
TimePicker: { render: ({ C, h, props: p }) => { const [H0, M0] = (p.modelValue || '09:00').split(':'), H = String(H0 ?? '00').padStart(2, '0'), M = String(M0 ?? '00').padStart(2, '0'); const hs = Array.from({ length: 24 }, (_, i) => { const v = String(i).padStart(2, '0'); return { value: v, label: v }; }), st = Math.max(1, Math.min(60, Math.floor(+p.minuteStep) || 15)), mv = Array.from({ length: Math.ceil(60 / st) }, (_, i) => String(i * st).padStart(2, '0')); if (!mv.includes(M)) { mv.push(M); mv.sort(); } const ms = mv.map(v => ({ value: v, label: v }));
|
package/src/core/components3.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ziyarago/ui · components, part 3 — card patterns, tonal variants, shells for auth/profile/status pages.
|
|
2
2
|
import { icon, cx, fmt, ddRef } from './components.js';
|
|
3
3
|
import { t } from '../i18n/index.js';
|
|
4
|
+
import { rove, tabIdx, listKeys, openKeys, toTrigger, trigger } from './keys.js';
|
|
4
5
|
const TONES = ['brand', 'sky', 'success', 'warning', 'danger', 'neutral'];
|
|
5
6
|
const tc = t => 'zg-t-' + (TONES.includes(t) ? t : 'brand');
|
|
6
7
|
const delta = (h, d) => d ? h('span', { class: cx('zg-delta', String(d).trim().startsWith('-') ? 'zg-delta--down' : 'zg-delta--up') }, (String(d).trim().startsWith('-') ? '▼ ' : '▲ ') + String(d).replace(/^[+-]/, '')) : null;
|
|
@@ -21,8 +22,8 @@ export const more3 = {
|
|
|
21
22
|
return h('div', { class: 'zg-field', style: { gap: '12px' } }, h('div', { class: 'zg-segbar' }, ...s.map((x, i) => h('i', { key: i, title: x.label, style: { width: (x.value / t * 100) + '%', background: x.color || col[i % 5] } }))), h('div', { class: 'zg-chips' }, ...s.map((x, i) => h('span', { key: i, class: 'zg-hint', style: { display: 'inline-flex', alignItems: 'center', gap: '6px' } }, h('i', { style: { width: '8px', height: '8px', borderRadius: '50%', background: x.color || col[i % 5] } }), x.label + ' · ' + x.value)))); } },
|
|
22
23
|
HeroBanner: { render: ({ C, h, props: p, children }) => h('section', { class: 'zg-hero zg-glass' }, h('div', { class: 'zg-hero__t' }, h('b', { style: { fontSize: '20px', fontWeight: 500 } }, p.title), p.text ? h('span', { style: { color: 'var(--zg-text-2)', lineHeight: 1.5 } }, p.text) : null, p.value ? h('span', { class: 'zg-num', style: { fontSize: '36px', fontWeight: 300 } }, p.value) : null, ...children, p.cta ? h(C.Button, { variant: 'primary', size: 'sm', onClick: p.onCta }, p.cta) : null), p.image ? h('span', { class: 'zg-hero__img', style: { backgroundImage: `url(${p.image})` } }) : null) },
|
|
23
24
|
TabTiles: { render: ({ h, props: p }) => h('div', { class: 'zg-tt' }, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', class: 'zg-tt__i', 'aria-pressed': String(it.value === p.value), onClick: () => p.onChange && p.onChange(it.value) }, icon(h, it.icon || 'circle', 20), h('span', null, it.label))), p.addable ? h('button', { type: 'button', class: 'zg-tt__i zg-tt__add', 'aria-label': t('TabTiles', 'add'), onClick: p.onAdd }, icon(h, 'plus', 20)) : null) },
|
|
24
|
-
PillTabs: { render: ({ h, props: p }) => h('div', { class: cx('zg-pt', p.vertical && 'zg-pt--v'), role: 'tablist' }, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', role: 'tab', 'aria-selected': String(it.value === p.value), onClick: () => p.onChange && p.onChange(it.value) }, it.icon ? icon(h, it.icon, 16) : null, it.label))) },
|
|
25
|
-
PillSelect: { state: { open: false }, render: ({ h, props: p, state, set }) => { const opts = p.options || [], v = p.modelValue !== undefined ? p.modelValue : p.value, open = state.open && !p.disabled; return h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: 'zg-btn zg-btn--tonal zg-btn--sm', 'aria-haspopup': 'listbox', 'aria-expanded': String(open), disabled: p.disabled, onClick: () => set({ open: !state.open }) }, (opts.find(o => o.value === v) || {}).label || p.placeholder, icon(h, 'chevron-down', 14)), open ? h('div', { class: 'zg-menu', role: 'listbox', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...opts.map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-menu__i', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === v), onClick:
|
|
25
|
+
PillTabs: { render: ({ h, props: p }) => h('div', { class: cx('zg-pt', p.vertical && 'zg-pt--v'), role: 'tablist', 'aria-orientation': p.vertical ? 'vertical' : undefined, onKeydown: rove('[role=tab]', { vertical: !!p.vertical }) }, ...(p.items || []).map((it, i, all) => h('button', { key: it.value, type: 'button', role: 'tab', tabIndex: tabIdx(all, x => x.value === p.value)(i), disabled: it.disabled, 'aria-selected': String(it.value === p.value), onClick: () => p.onChange && p.onChange(it.value) }, it.icon ? icon(h, it.icon, 16) : null, it.label))) },
|
|
26
|
+
PillSelect: { state: { open: false }, render: ({ h, props: p, state, set }) => { const opts = p.options || [], v = p.modelValue !== undefined ? p.modelValue : p.value, open = state.open && !p.disabled; return h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: 'zg-btn zg-btn--tonal zg-btn--sm', 'aria-haspopup': 'listbox', 'aria-expanded': String(open), disabled: p.disabled, onClick: () => set({ open: !state.open }), onKeydown: openKeys(open, () => set({ open: true })) }, (opts.find(o => o.value === v) || {}).label || p.placeholder, icon(h, 'chevron-down', 14)), open ? h('div', { class: 'zg-menu', role: 'listbox', 'aria-label': p.placeholder || (opts.find(o => o.value === v) || {}).label, 'data-zg-dd': '', ref: ddRef(() => set({ open: false }), { focus: true }), onKeydown: listKeys('[role=option]', { onTab: e => { const t = trigger(e.currentTarget); t && t.focus(); set({ open: false }); } }) }, ...opts.map((o, i) => h('button', { key: o.value, type: 'button', role: 'option', tabIndex: tabIdx(opts, x => x.value === v)(i), class: 'zg-menu__i', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === v), onClick: e => { toTrigger(e); set({ open: false }); p.onChange && p.onChange(o.value); } }, o.label))) : null); } },
|
|
26
27
|
ChartCard: { state: { open: false }, render: ({ h, props: p, state, set, children }) => h('section', { class: 'zg-card zg-glass' }, h('div', { class: 'zg-card__head' }, h('div', { style: { flex: 1, minWidth: 0 } }, h('h3', { class: 'zg-card__title' }, p.title), p.subtitle ? h('div', { class: 'zg-card__sub' }, p.subtitle) : null),
|
|
27
28
|
p.menu ? h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('ChartCard', 'more'), 'aria-haspopup': 'menu', onClick: () => set({ open: !state.open }) }, icon(h, 'ellipsis-vertical')), state.open ? h('div', { class: 'zg-menu', role: 'menu', style: { insetInlineStart: 'auto', insetInlineEnd: 0 } }, ...p.menu.map(m => h('button', { key: m, type: 'button', role: 'menuitem', class: cx('zg-menu__i', m === 'Delete' && 'zg-menu__i--danger'), onClick: () => { set({ open: false }); p.onMenu && p.onMenu(m); } }, m))) : null) : null), ...children) },
|
|
28
29
|
ActionCard: { state: { collapsed: false, loading: false, removed: false }, render: ({ C, h, props: p, state, set, children }) => { const col = p.collapsed !== undefined ? !!p.collapsed && p.collapsed !== 'false' : state.collapsed, busy = p.loading !== undefined ? !!p.loading && p.loading !== 'false' : state.loading; return state.removed ? null : h('section', { class: 'zg-card zg-glass zg-lo' }, h('div', { class: 'zg-card__head' }, h('div', { style: { flex: 1 } }, h('h3', { class: 'zg-card__title' }, p.title), p.subtitle ? h('div', { class: 'zg-card__sub' }, p.subtitle) : null), h('div', { style: { display: 'flex', gap: '2px' } }, h(C.IconButton, { icon: col ? 'chevron-down' : 'chevron-up', label: col ? t('ActionCard', 'expand') : t('ActionCard', 'collapse'), size: 'sm', ghost: true, onClick: () => { set({ collapsed: !col }); p.onCollapse && p.onCollapse(!col); } }), h(C.IconButton, { icon: 'refresh-cw', label: t('ActionCard', 'refresh'), size: 'sm', ghost: true, onClick: () => { if (p.loading === undefined) { set({ loading: true }); setTimeout(() => set({ loading: false }), 1800); } p.onRefresh && p.onRefresh(); } }), h(C.IconButton, { icon: 'x', label: t('ActionCard', 'remove'), size: 'sm', ghost: true, onClick: () => { set({ removed: true }); p.onRemove && p.onRemove(); } }))),
|
package/src/core/components4.js
CHANGED
|
@@ -35,7 +35,7 @@ export const more4 = {
|
|
|
35
35
|
Footer: { render: ({ h, props: p, children }) => h('footer', { class: 'zg-foot' }, h('span', { style: { color: 'var(--zg-text-3)', fontSize: '13px' } }, p.copyright || '© 2026 ZiyaraGo'), h('nav', { style: { display: 'flex', gap: '16px', flexWrap: 'wrap' } }, ...(p.links || []).map(l => h('a', { key: l.label, href: l.href || '#', style: { color: 'var(--zg-brand-text)', fontSize: '13px', fontWeight: 600 } }, l.label))), ...children) },
|
|
36
36
|
DataGroup: { state: { open: {} , grp: {} }, render: ({ C, h, props: p, state, set }) => { const cols = p.columns || [];
|
|
37
37
|
const row = r => { const o = !!state.open[r.id]; return [h('tr', { key: r.id, 'aria-selected': o ? 'true' : undefined }, p.expand ? h('td', { style: { width: '44px' } }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-expanded': String(o), 'aria-label': t('DataGroup', 'expand'), onClick: () => set({ open: { ...state.open, [r.id]: !o } }) }, icon(h, o ? 'chevron-down' : 'chevron-right', 16))) : null, ...cols.map(c => h('td', { key: c.key, class: c.num ? 'num' : undefined }, c.render ? c.render(r[c.key], r) : r[c.key]))), o && p.expand ? h('tr', { key: r.id + 'x' }, h('td', { colSpan: cols.length + 1, style: { background: 'var(--zg-s1)', padding: '16px 20px' } }, p.expand(r))) : null]; };
|
|
38
|
-
return h('div', { class: 'zg-table-wrap' }, h('table', { class: 'zg-table' }, h('thead', null, h('tr', null, p.expand ? h('th') : null, ...cols.map(c => h('th', { key: c.key, class: c.num ? 'num' : undefined }, c.label)))),
|
|
38
|
+
return h('div', { class: 'zg-table-wrap', tabIndex: 0 }, h('table', { class: 'zg-table' }, h('thead', null, h('tr', null, p.expand ? h('th') : null, ...cols.map(c => h('th', { key: c.key, class: c.num ? 'num' : undefined }, c.label)))),
|
|
39
39
|
h('tbody', null, ...(p.groups || []).flatMap(g => { const c = !!state.grp[g.label]; return [h('tr', { key: 'g' + g.label }, h('td', { colSpan: cols.length + 1, style: { padding: 0 } }, h('button', { type: 'button', class: 'zg-dg__g', onClick: () => set({ grp: { ...state.grp, [g.label]: !c } }), style: { all: 'unset', boxSizing: 'border-box', display: 'flex', alignItems: 'center', gap: '8px', width: '100%', padding: '10px 16px', cursor: 'pointer', fontWeight: 700, fontSize: '13px', background: 'var(--zg-s1)' } }, icon(h, c ? 'chevron-right' : 'chevron-down', 14), g.label + ' (' + g.rows.length + ')'))), ...(c ? [] : g.rows.flatMap(row))]; })))); } },
|
|
40
40
|
OptionCard: { render: ({ h, props: p }) => h('button', { type: 'button', role: p.multiple ? 'checkbox' : 'radio', 'aria-checked': String(!!p.checked), class: 'zg-opt', onClick: p.onClick }, p.image ? h('span', { class: 'zg-opt__img', style: { backgroundImage: `url(${p.image})` } }) : p.icon ? h('span', { class: 'zg-tile zg-t-brand', style: { '--s': '40px' } }, icon(h, p.icon, 20)) : null, h('span', { style: { display: 'flex', flexDirection: 'column', gap: '2px', flex: 1, textAlign: 'start' } }, h('b', { style: { fontSize: '15px' } }, p.title), p.desc ? h('span', { class: 'zg-hint' }, p.desc) : null), p.price ? h('b', { class: 'zg-num', style: { fontSize: '16px' } }, p.price) : null, h('span', { class: cx('zg-check__box', !p.multiple && 'zg-radio'), style: { background: p.checked ? 'var(--zg-brand)' : 'transparent' } }, p.checked ? (p.multiple ? icon(h, 'check', 12) : h('i')) : null)) },
|
|
41
41
|
OptionCards: { render: ({ C, h, props: p }) => { const cur = p.multiple ? (p.modelValue || []) : p.modelValue;
|
package/src/core/components6.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { t } from '../i18n/index.js';
|
|
3
3
|
import { icon, cx, ddRef } from './components.js';
|
|
4
4
|
import { trap, life } from './dialog.js';
|
|
5
|
+
import { listKeys, tabIdx } from './keys.js';
|
|
5
6
|
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
6
7
|
|
|
7
8
|
export const more6 = {
|
|
@@ -15,7 +16,7 @@ export const more6 = {
|
|
|
15
16
|
ContextMenu: { state: { x: 0, y: 0, open: false }, render: ({ h, props: p, state, set, children }) => {
|
|
16
17
|
const close = () => set({ open: false });
|
|
17
18
|
return h('div', { class: 'zg-ctx', onContextmenu: e => { e.preventDefault(); set({ open: true, x: e.clientX, y: e.clientY }); } }, ...children,
|
|
18
|
-
state.open ? h('div', { class: 'zg-ctx__scrim', onClick: close, onContextmenu: e => { e.preventDefault(); close(); } }, h('div', { class: 'zg-menu', role: 'menu', 'data-zg-dd': '', ref: ddRef(close, { self: true }), style: { position: 'fixed', left: Math.min(state.x, innerWidth - 240) + 'px', top: Math.min(state.y, innerHeight - (p.items || []).length * 42 - 20) + 'px', insetInlineStart: 'auto' }, onClick: e => e.stopPropagation() }, ...(p.items || []).map((it, i) => it.separator ? h('div', { key: i, class: 'zg-menu__sep' }) : h('button', { key: i, type: 'button', role: 'menuitem', class: cx('zg-menu__i', it.tone === 'danger' && 'zg-menu__i--danger'), onClick: () => { close(); p.onSelect && p.onSelect(it.value); } }, it.icon ? icon(h, it.icon, 16) : null, it.label, it.shortcut ? h('span', { class: 'zg-kbd' }, it.shortcut) : null)))) : null); } },
|
|
19
|
+
state.open ? h('div', { class: 'zg-ctx__scrim', onClick: close, onContextmenu: e => { e.preventDefault(); close(); } }, h('div', { class: 'zg-menu', role: 'menu', 'data-zg-dd': '', ref: ddRef(close, { self: true, focus: true }), onKeydown: listKeys('[role=menuitem]', { wrap: true, onTab: close }), style: { position: 'fixed', left: Math.min(state.x, innerWidth - 240) + 'px', top: Math.min(state.y, innerHeight - (p.items || []).length * 42 - 20) + 'px', insetInlineStart: 'auto' }, onClick: e => e.stopPropagation() }, ...(p.items || []).map((it, i, all) => it.separator ? h('div', { key: i, class: 'zg-menu__sep', role: 'separator' }) : h('button', { key: i, type: 'button', role: 'menuitem', tabIndex: tabIdx(all.map(x => ({ disabled: x.separator || x.disabled })), () => false)(i), class: cx('zg-menu__i', it.tone === 'danger' && 'zg-menu__i--danger'), onClick: () => { close(); p.onSelect && p.onSelect(it.value); } }, it.icon ? icon(h, it.icon, 16) : null, it.label, it.shortcut ? h('span', { class: 'zg-kbd' }, it.shortcut) : null)))) : null); } },
|
|
19
20
|
Sortable: { state: { drag: null, over: null }, render: ({ h, props: p, state, set }) => { const items = p.items || [];
|
|
20
21
|
const drop = (to, f) => { const from = f === undefined ? state.drag : f; set({ drag: null, over: null }); if (from == null || from === to) return; const a = items.slice(); const [m] = a.splice(from, 1); a.splice(to, 0, m); p.onChange && p.onChange(a); };
|
|
21
22
|
return h('div', { class: 'zg-sort', role: 'list' }, ...items.map((it, i) => h('div', { key: it.id ?? i, role: 'listitem', class: cx('zg-sort__i', state.over === i && state.drag !== i && 'is-over', state.drag === i && 'is-drag'), draggable: 'true', onDragstart: e => { e.dataTransfer.effectAllowed = 'move'; try { e.dataTransfer.setData('text/plain', String(i)); } catch (x) {} set({ drag: i }); }, onDragover: e => { e.preventDefault(); if (state.over !== i) set({ over: i }); }, onDrop: e => { e.preventDefault(); drop(i); }, onDragend: () => set({ drag: null, over: null }) },
|
package/src/core/index.js
CHANGED
|
@@ -7,5 +7,11 @@ import { more6 } from './components6.js';
|
|
|
7
7
|
import { more7 } from './components7.js';
|
|
8
8
|
export const components = { ...base, ...more, ...more3, ...more4, ...more6, ...more7, ...pages };
|
|
9
9
|
export { fmt, setIconBase, rules };
|
|
10
|
+
/** Extension API: adds definitions to the shared registry (used by add-on packages such as @ziyarago/ui-pro). Registering the very same definition twice is a no-op (Vue and React adapters can both load one plugin). */
|
|
11
|
+
export function registerComponents(defs, { override = false } = {}) {
|
|
12
|
+
for (const name of Object.keys(defs)) if (!override && name in components && components[name] !== defs[name]) throw new Error(`@ziyarago/ui: component "${name}" is already registered (pass { override: true } to replace it)`);
|
|
13
|
+
Object.assign(components, defs);
|
|
14
|
+
return components;
|
|
15
|
+
}
|
|
10
16
|
export { LOCALES, FREE_LOCALES, t, setUiLocale, getUiLocale, setUiMessages, isRtlLocale, subscribeUiLocale, getUiVersion, defineMessages } from '../i18n/index.js';
|
|
11
17
|
import '../i18n/components/index.js';
|
package/src/core/keys.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Keyboard helpers shared by the core components. Handlers run in the browser only (SSR renders no events).
|
|
2
|
+
const isRtl = el => typeof getComputedStyle === 'function' && getComputedStyle(el).direction === 'rtl';
|
|
3
|
+
const live = (root, sel) => [...root.querySelectorAll(sel)].filter(n => !n.disabled && n.getAttribute('aria-disabled') !== 'true');
|
|
4
|
+
const mod = e => e.altKey || e.ctrlKey || e.metaKey;
|
|
5
|
+
|
|
6
|
+
/** Roving tabindex value per item: the active one (or the first enabled when none) is 0, the rest -1. Returns i => 0 | -1. */
|
|
7
|
+
export const tabIdx = (items, isOn) => {
|
|
8
|
+
let on = items.findIndex(it => isOn(it) && !it.disabled);
|
|
9
|
+
if (on < 0) on = items.findIndex(it => !it.disabled);
|
|
10
|
+
return i => (i === on ? 0 : -1);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const park = (items, n) => { for (const i of items) i.tabIndex = i === n ? 0 : -1; n.focus(); };
|
|
14
|
+
|
|
15
|
+
/** Tablist / button group: Left/Right (mirrored in RTL, Up/Down when vertical), Home, End. Focus moves, Enter/Space activates. */
|
|
16
|
+
export const rove = (sel, o = {}) => e => {
|
|
17
|
+
if (mod(e)) return;
|
|
18
|
+
const root = e.currentTarget, items = live(root, sel), cur = items.indexOf(e.target.closest && e.target.closest(sel));
|
|
19
|
+
if (cur < 0 || !items.length) return;
|
|
20
|
+
const rtl = isRtl(root), fwd = o.vertical ? 'ArrowDown' : rtl ? 'ArrowLeft' : 'ArrowRight', back = o.vertical ? 'ArrowUp' : rtl ? 'ArrowRight' : 'ArrowLeft';
|
|
21
|
+
const n = e.key === fwd ? (cur + 1) % items.length : e.key === back ? (cur - 1 + items.length) % items.length : e.key === 'Home' ? 0 : e.key === 'End' ? items.length - 1 : -1;
|
|
22
|
+
if (n < 0) return;
|
|
23
|
+
e.preventDefault(); park(items, items[n]);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** The element that opened a popup list: the first button or input of the component root that is not inside the list. */
|
|
27
|
+
export const trigger = list => { const root = list && list.parentElement; return root && [...root.querySelectorAll('button,input')].find(x => !list.contains(x)); };
|
|
28
|
+
/** Call from an item's onClick so focus lands on the trigger instead of being lost when the list unmounts. */
|
|
29
|
+
export const toTrigger = e => { const l = e.currentTarget && e.currentTarget.closest && e.currentTarget.closest('[data-zg-dd]'), t = l && trigger(l); if (t) t.focus({ preventScroll: true }); };
|
|
30
|
+
|
|
31
|
+
/** Popup listbox / menu: Up/Down (menus wrap, listboxes stop at the ends), Home, End, type-ahead, Tab closes. */
|
|
32
|
+
export const listKeys = (sel, o = {}) => e => {
|
|
33
|
+
if (mod(e)) return;
|
|
34
|
+
const root = e.currentTarget, items = live(root, sel), cur = items.indexOf(e.target.closest && e.target.closest(sel)), len = items.length;
|
|
35
|
+
if (!len) return;
|
|
36
|
+
if (e.key === 'Tab') { if (o.onTab) o.onTab(e); return; }
|
|
37
|
+
let n = -1;
|
|
38
|
+
if (e.key === 'ArrowDown') n = cur < 0 ? 0 : o.wrap ? (cur + 1) % len : Math.min(cur + 1, len - 1);
|
|
39
|
+
else if (e.key === 'ArrowUp') n = cur < 0 ? len - 1 : o.wrap ? (cur - 1 + len) % len : Math.max(cur - 1, 0);
|
|
40
|
+
else if (e.key === 'Home') n = 0;
|
|
41
|
+
else if (e.key === 'End') n = len - 1;
|
|
42
|
+
else if (e.key.length === 1 && e.key !== ' ') {
|
|
43
|
+
const c = e.key.toLowerCase(), order = [...items.slice(cur + 1), ...items.slice(0, cur + 1)];
|
|
44
|
+
const hit = order.find(i => (i.textContent || '').trim().toLowerCase().startsWith(c));
|
|
45
|
+
if (hit) n = items.indexOf(hit);
|
|
46
|
+
}
|
|
47
|
+
if (n < 0) return;
|
|
48
|
+
e.preventDefault(); park(items, items[n]);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** onKeydown for a popup trigger: ArrowDown / ArrowUp open the closed list. */
|
|
52
|
+
export const openKeys = (isOpen, open) => e => { if (!isOpen && !mod(e) && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) { e.preventDefault(); open(); } };
|
|
53
|
+
|
|
54
|
+
/** Focus the entry point of a freshly opened popup: the item with tabindex 0, else the first one. */
|
|
55
|
+
export const focusIn = el => { const t = el.querySelector('[tabindex="0"]') || el.querySelector('button:not([disabled])'); if (t && t.focus) t.focus({ preventScroll: true }); };
|
|
@@ -44,6 +44,7 @@ import Pagination from './Pagination.js';
|
|
|
44
44
|
import PasswordInput from './PasswordInput.js';
|
|
45
45
|
import Popconfirm from './Popconfirm.js';
|
|
46
46
|
import PricingCard from './PricingCard.js';
|
|
47
|
+
import Progress from './Progress.js';
|
|
47
48
|
import QiblaCompass from './QiblaCompass.js';
|
|
48
49
|
import RamadanTracker from './RamadanTracker.js';
|
|
49
50
|
import RangeCalendar from './RangeCalendar.js';
|
|
@@ -113,6 +114,7 @@ defineMessages('Pagination', Pagination);
|
|
|
113
114
|
defineMessages('PasswordInput', PasswordInput);
|
|
114
115
|
defineMessages('Popconfirm', Popconfirm);
|
|
115
116
|
defineMessages('PricingCard', PricingCard);
|
|
117
|
+
defineMessages('Progress', Progress);
|
|
116
118
|
defineMessages('QiblaCompass', QiblaCompass);
|
|
117
119
|
defineMessages('RamadanTracker', RamadanTracker);
|
|
118
120
|
defineMessages('RangeCalendar', RangeCalendar);
|
package/src/meta/index.js
CHANGED
|
@@ -11,4 +11,17 @@ import domain from './domain.js';
|
|
|
11
11
|
import pages from './pages.js';
|
|
12
12
|
|
|
13
13
|
export const meta = { ...typography, ...layout, ...actions, ...forms, ...data, ...feedback, ...overlay, ...navigation, ...media, ...domain, ...pages };
|
|
14
|
+
import { PRO_NAMES } from './tiers.js';
|
|
15
|
+
import { GROUPS } from './groups.js';
|
|
16
|
+
/** Extension API: adds API metadata for plugin components. `tiers` marks names as Pro, `groups` ({ id, title, blurb, names }) lists them in the manifest. */
|
|
17
|
+
export function registerMeta(extra, { tiers, groups } = {}) {
|
|
18
|
+
Object.assign(meta, extra);
|
|
19
|
+
for (const n of tiers || []) PRO_NAMES.add(n);
|
|
20
|
+
for (const g of groups || []) {
|
|
21
|
+
const ex = GROUPS.find(x => x.id === g.id);
|
|
22
|
+
if (ex) ex.names.push(...g.names.filter(n => !ex.names.includes(n)));
|
|
23
|
+
else GROUPS.push({ blurb: '', title: g.id, ...g, names: [...g.names] });
|
|
24
|
+
}
|
|
25
|
+
return meta;
|
|
26
|
+
}
|
|
14
27
|
export default meta;
|
package/src/nuxt/module.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// Nuxt 3/4 module. nuxt.config: modules: ['@ziyarago/ui/nuxt'], ziyarago: { theme: true }
|
|
2
2
|
import { defineNuxtModule, addComponent, addImports, addPlugin, createResolver } from '@nuxt/kit';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
3
4
|
import { components } from '../core/index.js';
|
|
4
5
|
import { themeInitScript } from '../theme.js';
|
|
5
6
|
|
|
6
7
|
export default defineNuxtModule({
|
|
7
8
|
meta: { name: '@ziyarago/ui', configKey: 'ziyarago', compatibility: { nuxt: '>=3.0.0' } },
|
|
8
|
-
defaults: { css: true, prefix: 'Ui', theme: { theme: 'dark', palette: 'gold', dir: 'ltr', bg: 'none', locale: 'en' } },
|
|
9
|
+
defaults: { css: true, plugins: [], prefix: 'Ui', theme: { theme: 'dark', palette: 'gold', dir: 'ltr', bg: 'none', locale: 'en' } },
|
|
9
10
|
setup(opts, nuxt) {
|
|
10
11
|
const { resolve } = createResolver(import.meta.url);
|
|
11
12
|
if (opts.css) nuxt.options.css.push('@ziyarago/ui/style.css');
|
|
@@ -13,6 +14,13 @@ export default defineNuxtModule({
|
|
|
13
14
|
for (const name of Object.keys(components)) {
|
|
14
15
|
addComponent({ name: opts.prefix + name, export: opts.prefix + name, filePath: '@ziyarago/ui/vue' });
|
|
15
16
|
}
|
|
17
|
+
for (const pkg of opts.plugins) {
|
|
18
|
+
nuxt.options.build.transpile.push(pkg);
|
|
19
|
+
let names = [];
|
|
20
|
+
try { names = createRequire(import.meta.url)(pkg + '/names'); } catch { try { names = createRequire(nuxt.options.rootDir + '/')(pkg + '/names'); } catch { console.warn(`[@ziyarago/ui] plugin ${pkg}: cannot load ${pkg}/names`); } }
|
|
21
|
+
for (const name of names) addComponent({ name: opts.prefix + name, export: 'Ui' + name, filePath: pkg + '/vue' });
|
|
22
|
+
if (opts.css) nuxt.options.css.push(pkg + '/style.css');
|
|
23
|
+
}
|
|
16
24
|
addImports([
|
|
17
25
|
{ name: 'useUiTheme', from: '@ziyarago/ui/theme' },
|
|
18
26
|
{ name: 'fmt', from: '@ziyarago/ui/vue' },
|
package/src/react/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// React 18+ adapter. Usage: import { UiButton, UiAppShell } from '@ziyarago/ui/react'
|
|
3
3
|
import React, { useState, useRef, useEffect, useLayoutEffect, useSyncExternalStore, createElement, cloneElement, isValidElement } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
|
-
import { components, subscribeUiLocale, getUiVersion } from '../core/index.js';
|
|
5
|
+
import { components, registerComponents, subscribeUiLocale, getUiVersion } from '../core/index.js';
|
|
6
6
|
|
|
7
7
|
const C = {};
|
|
8
8
|
const useIsoLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect;
|
|
@@ -37,6 +37,13 @@ const wrap = (name, def) => {
|
|
|
37
37
|
return Comp;
|
|
38
38
|
};
|
|
39
39
|
for (const [name, def] of Object.entries(components)) C[name] = wrap(name, def);
|
|
40
|
+
/** Wraps definitions like the built-in ones, registers them in C (cross references work both ways) and returns { UiName: Component }. */
|
|
41
|
+
export function defineUiComponents(defs, { override = false } = {}) {
|
|
42
|
+
registerComponents(defs, { override });
|
|
43
|
+
const out = {};
|
|
44
|
+
for (const [name, def] of Object.entries(defs)) out['Ui' + name] = C[name] = wrap(name, def);
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
40
47
|
|
|
41
48
|
// Explicit named exports (not destructuring): Next.js reads the export names of a 'use client' module statically.
|
|
42
49
|
export const UiButton = C.Button;
|
package/src/resolver.js
CHANGED
|
@@ -3,20 +3,31 @@
|
|
|
3
3
|
// import { ZiyaragoUiResolver } from '@ziyarago/ui/resolver'
|
|
4
4
|
// Components({ resolvers: [ZiyaragoUiResolver()] })
|
|
5
5
|
// Zero dependencies: it only maps <UiButton> to { name: 'UiButton', from: '@ziyarago/ui/vue' }.
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
6
7
|
import { components } from './core/index.js';
|
|
7
8
|
|
|
8
9
|
const NAMES = new Set(Object.keys(components));
|
|
9
10
|
|
|
11
|
+
// Plugin packages (see docs-private/PRO-SPLIT.md, "Plugin contract") export ./names (JSON array of component names without the Ui prefix) and ./vue.
|
|
12
|
+
// A plugin may be a package name or { from, names } for tests and custom setups.
|
|
13
|
+
const loadPlugin = p => {
|
|
14
|
+
if (typeof p !== 'string') return { from: p.from, names: new Set(p.names) };
|
|
15
|
+
const req = createRequire(process.cwd() + '/');
|
|
16
|
+
return { from: p + '/vue', names: new Set(req(p + '/names')) };
|
|
17
|
+
};
|
|
18
|
+
|
|
10
19
|
/**
|
|
11
|
-
* @param {{ prefix?: string, css?: boolean | string, exclude?: string[], from?: string }} [options]
|
|
20
|
+
* @param {{ prefix?: string, css?: boolean | string, exclude?: string[], from?: string, plugins?: Array<string | { from: string, names: string[] }> }} [options]
|
|
12
21
|
* prefix component prefix, default 'Ui' (must match the registered names, which are always Ui*)
|
|
13
22
|
* css true: import '@ziyarago/ui/style.css' as a side effect together with the first resolved component (once per module that uses one);
|
|
14
23
|
* a string: import that path instead (for example your own entry that imports the css inside a Tailwind @layer). Default false.
|
|
15
24
|
* exclude component names (without prefix) that must not be auto-imported, e.g. ['Button'] if you keep your own UiButton.
|
|
16
25
|
* from module the components are imported from, default '@ziyarago/ui/vue'
|
|
26
|
+
* plugins add-on packages whose components auto-import too: ['@ziyarago/ui-pro'] (needs its ./names and ./vue exports) or [{ from, names }]
|
|
17
27
|
*/
|
|
18
28
|
export function ZiyaragoUiResolver(options = {}) {
|
|
19
|
-
const { prefix = 'Ui', css = false, exclude = [], from = '@ziyarago/ui/vue' } = options;
|
|
29
|
+
const { prefix = 'Ui', css = false, exclude = [], from = '@ziyarago/ui/vue', plugins = [] } = options;
|
|
30
|
+
const loaded = plugins.map(loadPlugin);
|
|
20
31
|
const skip = new Set(exclude.map(n => n.replace(/^Ui/, '')));
|
|
21
32
|
const sideEffects = css ? (css === true ? '@ziyarago/ui/style.css' : css) : undefined;
|
|
22
33
|
return {
|
|
@@ -24,8 +35,10 @@ export function ZiyaragoUiResolver(options = {}) {
|
|
|
24
35
|
resolve(name) {
|
|
25
36
|
if (!name.startsWith(prefix)) return;
|
|
26
37
|
const bare = name.slice(prefix.length);
|
|
27
|
-
if (
|
|
28
|
-
|
|
38
|
+
if (skip.has(bare)) return;
|
|
39
|
+
const pl = NAMES.has(bare) ? null : loaded.find(x => x.names.has(bare));
|
|
40
|
+
if (!pl && !NAMES.has(bare)) return;
|
|
41
|
+
return { name: 'Ui' + bare, from: pl ? pl.from : from, ...(sideEffects ? { sideEffects } : {}) };
|
|
29
42
|
},
|
|
30
43
|
};
|
|
31
44
|
}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
.zg-btn--primary{background:var(--zg-brand);color:var(--zg-on-brand);box-shadow:inset 0 1px 0 oklch(1 0 0 / .3),0 8px 24px var(--zg-brand-glow)}.zg-btn--primary:hover{background:var(--zg-brand-hover)}
|
|
24
24
|
.zg-btn--inverse{background:var(--zg-inv-bg);color:var(--zg-inv-fg)}.zg-btn--inverse:hover{background:var(--zg-inv-bg);filter:brightness(1.1)}
|
|
25
25
|
.zg-btn--ghost{background:transparent;box-shadow:none}.zg-btn--ghost:hover{background:var(--zg-s2)}
|
|
26
|
-
.zg-btn--danger{background:color-mix(in oklch,var(--zg-red) 18%,transparent);color:var(--zg-red)}
|
|
26
|
+
.zg-btn--danger{background:color-mix(in oklch,var(--zg-red) 18%,transparent);color:var(--zg-red-text)}
|
|
27
27
|
.zg-btn--xs{--h:var(--zg-h-xs);font-size:12px}.zg-btn--sm{--h:var(--zg-h-sm);font-size:13px}.zg-btn--lg{--h:var(--zg-h-lg);font-size:15px}.zg-btn--xl{--h:var(--zg-h-xl);font-size:16px}
|
|
28
28
|
.zg-btn--block{display:flex;width:100%}
|
|
29
29
|
.zg-spin{width:1em;height:1em;border-radius:50%;border:2px solid currentColor;border-inline-end-color:transparent;animation:zg-spin .7s linear infinite}
|
|
@@ -65,4 +65,4 @@
|
|
|
65
65
|
.zg-rc{display:grid;grid-template-columns:56px repeat(var(--d),minmax(0,1fr));gap:2px;align-items:center;direction:ltr;font-size:12px}.zg-rc__h{text-align:center;color:var(--zg-text-3)}.zg-rc__c{height:28px;border-radius:6px;background:var(--zg-s2)}.zg-rc__c[data-b]{background:var(--zg-brand-soft);border-radius:0}.zg-rc__c[data-b="start"]{background:var(--zg-brand);border-radius:999px 0 0 999px}
|
|
66
66
|
.zg-occ{display:flex;align-items:flex-end;gap:8px;height:160px}.zg-occ__c{flex:1;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:6px}.zg-occ__c i{width:100%;border-radius:10px 10px 4px 4px}
|
|
67
67
|
|
|
68
|
-
.zg-kpi--brand .zg-delta{color:inherit;opacity
|
|
68
|
+
.zg-kpi--brand .zg-kpi__label,.zg-kpi--brand .zg-kpi__sub{opacity:.9}.zg-kpi--brand .zg-delta{color:inherit;opacity:1}.zg-kpi--brand .zg-delta::before{opacity:.9}
|
|
@@ -110,3 +110,6 @@
|
|
|
110
110
|
.zg-qb__txt b{font-size:calc(22px*var(--dz,1));font-weight:600;color:var(--zg-text-1)}.zg-qb__txt small{color:var(--zg-text-3)}
|
|
111
111
|
/* Numbers: tabular figures with a plain zero (no slashed or dotted zero) in KPI values, tables and code cells */
|
|
112
112
|
.zg-num,.zg-mono,.zg-kpi__value,.zg-table .num{font-feature-settings:"zero" 0,"tnum" 1}
|
|
113
|
+
.zg-sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
|
|
114
|
+
.zg-select__opt:focus-visible{outline:3px solid var(--zg-brand-soft);outline-offset:-3px;background:var(--zg-s2)}
|
|
115
|
+
.zg-seg button:focus-visible,.zg-tabs button:focus-visible,.zg-code-b pre:focus-visible{outline:3px solid var(--zg-brand-soft);outline-offset:2px}
|
package/src/styles/tokens.css
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
--zg-s1:oklch(1 0 0 / .055);--zg-s2:oklch(1 0 0 / .085);--zg-s3:oklch(1 0 0 / .13);
|
|
12
12
|
--zg-glass:oklch(0.235 0.006 40 / .92);
|
|
13
13
|
--zg-stroke:oklch(1 0 0 / .08);
|
|
14
|
-
--zg-text-1:oklch(0.97 0.004 70);--zg-text-2:oklch(0.82 0.008 70);--zg-text-3:oklch(0.
|
|
14
|
+
--zg-text-1:oklch(0.97 0.004 70);--zg-text-2:oklch(0.82 0.008 70);--zg-text-3:oklch(0.74 0.01 70);
|
|
15
15
|
--zg-inv-bg:oklch(0.96 0.004 70);--zg-inv-fg:oklch(0.2 0.01 60);
|
|
16
|
-
--zg-green:oklch(0.76 0.15 150);--zg-amber:oklch(0.8 0.14 80);--zg-red:oklch(0.7 0.18 25);
|
|
16
|
+
--zg-green:oklch(0.76 0.15 150);--zg-amber:oklch(0.8 0.14 80);--zg-red:oklch(0.7 0.18 25);--zg-red-text:oklch(0.86 0.1 25);
|
|
17
17
|
--zg-rim:inset 0 1px 0 oklch(1 0 0 / .16),inset 0 0 0 1px oklch(1 0 0 / .07),inset 0 -1px 0 oklch(0 0 0 / .25);
|
|
18
18
|
--zg-shadow-1:0 8px 24px oklch(0 0 0 / .28);--zg-shadow-2:0 24px 64px oklch(0 0 0 / .5);
|
|
19
19
|
}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
--zg-stroke:oklch(0.3 0.02 60 / .15);
|
|
28
28
|
--zg-text-1:oklch(0.2 0.01 60);--zg-text-2:oklch(0.36 0.012 60);--zg-text-3:oklch(0.47 0.012 60);
|
|
29
29
|
--zg-inv-bg:oklch(0.22 0.01 60);--zg-inv-fg:oklch(0.97 0.004 70);
|
|
30
|
-
--zg-green:oklch(0.5 0.14 150);--zg-amber:oklch(0.55 0.13 70);--zg-red:oklch(0.52 0.19 25);
|
|
30
|
+
--zg-green:oklch(0.5 0.14 150);--zg-amber:oklch(0.55 0.13 70);--zg-red:oklch(0.52 0.19 25);--zg-red-text:oklch(0.44 0.17 25);
|
|
31
31
|
--zg-rim:inset 0 1px 0 oklch(1 0 0 / .95),inset 0 0 0 1px oklch(1 0 0 / .45),0 0 0 1px oklch(0.3 0.02 60 / .13);
|
|
32
32
|
--zg-shadow-1:0 8px 24px oklch(0.4 0.03 60 / .17);--zg-shadow-2:0 24px 64px oklch(0.4 0.03 60 / .22);
|
|
33
33
|
}
|
package/src/vue/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Vue 3 adapter. Usage: import { UiButton, UiAppShell } from '@ziyarago/ui/vue'
|
|
2
2
|
import { h, ref, reactive, defineComponent, onMounted, onBeforeUnmount, Teleport, cloneVNode, isVNode } from 'vue';
|
|
3
|
-
import { components, subscribeUiLocale } from '../core/index.js';
|
|
3
|
+
import { components, registerComponents, subscribeUiLocale } from '../core/index.js';
|
|
4
4
|
import { BOOL_PROPS } from '../core/bools.js';
|
|
5
5
|
|
|
6
6
|
const C = {};
|
|
@@ -28,6 +28,13 @@ const wrap = (name, def) => defineComponent({
|
|
|
28
28
|
},
|
|
29
29
|
});
|
|
30
30
|
for (const [name, def] of Object.entries(components)) C[name] = wrap(name, def);
|
|
31
|
+
/** Wraps definitions like the built-in ones, registers them in C (cross references work both ways) and returns { UiName: Component }. */
|
|
32
|
+
export function defineUiComponents(defs, { override = false } = {}) {
|
|
33
|
+
registerComponents(defs, { override });
|
|
34
|
+
const out = {};
|
|
35
|
+
for (const [name, def] of Object.entries(defs)) out['Ui' + name] = C[name] = wrap(name, def);
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
31
38
|
|
|
32
39
|
export const { Button: UiButton, IconButton: UiIconButton, Badge: UiBadge, Avatar: UiAvatar, Switch: UiSwitch, Checkbox: UiCheckbox, SegmentedControl: UiSegmentedControl, Tabs: UiTabs, FilterChips: UiFilterChips, TextField: UiTextField, Select: UiSelect, CodeInput: UiCodeInput, Stepper: UiStepper, Card: UiCard, Modal: UiModal, Drawer: UiDrawer, Toast: UiToast, Breadcrumbs: UiBreadcrumbs, Pagination: UiPagination, Progress: UiProgress, Ring: UiRing, KpiTile: UiKpiTile, DataTable: UiDataTable, KanbanColumn: UiKanbanColumn, KanbanCard: UiKanbanCard, Timeline: UiTimeline, TopNav: UiTopNav, SideNav: UiSideNav, AppShell: UiAppShell, PageHeader: UiPageHeader, AuthLayout: UiAuthLayout, LeadCard: UiLeadCard, HotelCard: UiHotelCard, TourCard: UiTourCard, PrayerTimes: UiPrayerTimes, RamadanTracker: UiRamadanTracker, QiblaCompass: UiQiblaCompass, CallRow: UiCallRow, Alert: UiAlert, Banner: UiBanner, Accordion: UiAccordion, Skeleton: UiSkeleton, Spinner: UiSpinner, Divider: UiDivider, EmptyState: UiEmptyState, ErrorPage: UiErrorPage, DataState: UiDataState, LoadingOverlay: UiLoadingOverlay, ConfirmDialog: UiConfirmDialog, Popconfirm: UiPopconfirm, Carousel: UiCarousel, Gallery: UiGallery, Code: UiCode, PricingCard: UiPricingCard, IntegrationCard: UiIntegrationCard, IncomingCall: UiIncomingCall, CallCard: UiCallCard, Dropdown: UiDropdown, Menu: UiMenu, Tooltip: UiTooltip, HoverCard: UiHoverCard, Popover: UiPopover, CopyButton: UiCopyButton, CommandBar: UiCommandBar, CommandPalette: UiCommandPalette, NotificationBell: UiNotificationBell, ToggleGroup: UiToggleGroup, Fab: UiFab, Textarea: UiTextarea, NumberInput: UiNumberInput, PasswordInput: UiPasswordInput, SearchInput: UiSearchInput, RadioGroup: UiRadioGroup, Slider: UiSlider, Rating: UiRating, ChipInput: UiChipInput, InlineEdit: UiInlineEdit, ColorPicker: UiColorPicker, FileUpload: UiFileUpload, Combobox: UiCombobox, MultiSelect: UiMultiSelect, GroupedSelect: UiGroupedSelect, PhoneInput: UiPhoneInput, Calendar: UiCalendar, DatePicker: UiDatePicker, TimePicker: UiTimePicker, FormField: UiFormField, LoginForm: UiLoginForm, StepForm: UiStepForm, Tag: UiTag, Kbd: UiKbd, StatsCard: UiStatsCard, KpiInline: UiKpiInline, MiniChart: UiMiniChart, Descriptions: UiDescriptions, LazyImage: UiLazyImage, SystemStatus: UiSystemStatus, TreeView: UiTreeView, CalendarView: UiCalendarView, SubNav: UiSubNav, ActionRail: UiActionRail, SplitLayout: UiSplitLayout, SettingsSection: UiSettingsSection, ScrollArea: UiScrollArea, DealCard: UiDealCard, PipelineBoard: UiPipelineBoard, ContactPanel: UiContactPanel, SalesFunnel: UiSalesFunnel, BookingCard: UiBookingCard, GuestCard: UiGuestCard, RoomGrid: UiRoomGrid, RoomCalendar: UiRoomCalendar, OccupancyChart: UiOccupancyChart, Chip: UiChip, IconTile: UiIconTile, StatusIcon: UiStatusIcon, UserCell: UiUserCell, StatRow: UiStatRow, CountryRow: UiCountryRow, StatLine: UiStatLine, StatStrip: UiStatStrip, VersusBar: UiVersusBar, SegBar: UiSegBar, HeroBanner: UiHeroBanner, TabTiles: UiTabTiles, PillTabs: UiPillTabs, PillSelect: UiPillSelect, ChartCard: UiChartCard, ActionCard: UiActionCard, Snackbar: UiSnackbar, TourPopover: UiTourPopover, ProfileHeader: UiProfileHeader, AuthSplit: UiAuthSplit, AuthShowcase: UiAuthShowcase, SocialAuth: UiSocialAuth, StatusPage: UiStatusPage, ValidatedField: UiValidatedField, Footer: UiFooter, DataGroup: UiDataGroup, OptionCard: UiOptionCard, OptionCards: UiOptionCards, RichEditor: UiRichEditor, RangeSlider: UiRangeSlider, RangeCalendar: UiRangeCalendar, StepDialog: UiStepDialog, FullscreenDialog: UiFullscreenDialog, PageDashboard: UiPageDashboard, PagePatterns: UiPagePatterns, PageCrm: UiPageCrm, PageBookings: UiPageBookings, PageCatalog: UiPageCatalog, PageDetail: UiPageDetail, PageSettings: UiPageSettings, PageUserList: UiPageUserList, PageProfile: UiPageProfile, PageForm: UiPageForm, PageOrder: UiPageOrder, PageAccount: UiPageAccount, PagePricing: UiPagePricing, PageFaq: UiPageFaq, PageAnalytics: UiPageAnalytics, PageCalendar: UiPageCalendar, PageKanban: UiPageKanban, PageBilling: UiPageBilling, PageTeam: UiPageTeam, PageIntegrations: UiPageIntegrations, PageSearch: UiPageSearch, PageArticle: UiPageArticle, PageChangelog: UiPageChangelog, PageHelp: UiPageHelp, PageStates: UiPageStates, PageLegal: UiPageLegal, PageLocations: UiPageLocations, PageBookingFlow: UiPageBookingFlow, PageItinerary: UiPageItinerary, PagePrayer: UiPagePrayer, PageGroupManager: UiPageGroupManager, PageTourPublic: UiPageTourPublic, PageNotifications: UiPageNotifications, PageInbox: UiPageInbox, PageCheckout: UiPageCheckout, PageOnboarding: UiPageOnboarding, PageLanding: UiPageLanding, PageAuth: UiPageAuth, PageStatus: UiPageStatus, TreeSelect: UiTreeSelect, ContextMenu: UiContextMenu, Sortable: UiSortable, Map: UiMap, ImageUpload: UiImageUpload, Screensaver: UiScreensaver, Tour: UiTour, Heading: UiHeading, Text: UiText, Box: UiBox, Center: UiCenter, HStack: UiHStack, VStack: UiVStack, Grid: UiGrid, Link: UiLink, Pressable: UiPressable, Icon: UiIcon, Portal: UiPortal, LiquidGlass: UiLiquidGlass, BottomSheet: UiBottomSheet, Actionsheet: UiActionsheet, ImageViewer: UiImageViewer, ChatAi: UiChatAi } = C;
|
|
33
40
|
export const install = app => { for (const [n, c] of Object.entries(C)) app.component('Ui' + n, c); };
|
package/tokens/tokens.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$description": "Design tokens for Flutter / native. Colors are sRGB hex approximations of the OKLCH source in src/styles/tokens.css.",
|
|
3
3
|
"palettes": { "gold": { "brand": "#BC926A", "sky": "#5FAACC" }, "tamam": { "brand": "#3FD13F", "sky": "#4FB5C4" } },
|
|
4
|
-
"dark": { "bg": "#1B1919", "text1": "#F1EDE8", "text2": "#D0CAC3", "text3": "#
|
|
4
|
+
"dark": { "bg": "#1B1919", "text1": "#F1EDE8", "text2": "#D0CAC3", "text3": "#AAA49E", "onBrand": "#1F1812", "green": "#6ED08F", "amber": "#E8B964", "red": "#E26E5F", "surface1": "#FFFFFF0E", "surface2": "#FFFFFF16", "surface3": "#FFFFFF21", "stroke": "#FFFFFF14" },
|
|
5
5
|
"light": { "bg": "#E7E3DF", "text1": "#241E18", "text2": "#4E4640", "text3": "#6C645D", "onBrand": "#1F1812", "green": "#2F7D4D", "amber": "#8A5F12", "red": "#B33A2C", "surface1": "#FFFFFF66", "surface2": "#FFFFFF8C", "surface3": "#FFFFFFBF", "stroke": "#4A3B2A1A" },
|
|
6
6
|
"radius": { "tile": 20, "card": 32, "pill": 999 },
|
|
7
7
|
"control": { "xs": 28, "sm": 32, "md": 40, "lg": 48, "xl": 56 },
|
package/types/core.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// Core (framework-neutral) entry. Most apps import from '@ziyarago/ui/vue' or '@ziyarago/ui/react' instead.
|
|
2
2
|
export { rules, setIconBase, fmt } from './shared';
|
|
3
3
|
export type * from './shared';
|
|
4
|
+
export interface ComponentDefinition { state?: Record<string, unknown>; slots?: string[]; render: (ctx: any) => unknown }
|
|
5
|
+
/** Extension API for add-on packages: adds definitions to the shared registry. Throws on a name clash with a different definition unless { override: true }. */
|
|
6
|
+
export function registerComponents(defs: Record<string, ComponentDefinition>, options?: { override?: boolean }): Record<string, ComponentDefinition>;
|
|
4
7
|
export const components: Record<string, { state?: Record<string, unknown>; slots?: string[]; render: (ctx: any) => unknown }>;
|
package/types/meta.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export interface MetaProp { type: string; values?: string[]; default?: unknown; description?: string; required?: boolean; model?: boolean; forwarded?: boolean }
|
|
2
2
|
export interface ComponentMeta { description: string; props?: Record<string, MetaProp>; slots?: Record<string, string>; events?: Record<string, string>; notes?: string[] }
|
|
3
|
+
export interface MetaGroup { id: string; title?: string; blurb?: string; names: string[] }
|
|
4
|
+
/** Extension API: adds metadata for plugin components. `tiers` are marked as pro, `groups` list them in the manifest. */
|
|
5
|
+
export function registerMeta(extra: Record<string, ComponentMeta>, options?: { tiers?: string[]; groups?: MetaGroup[] }): Record<string, ComponentMeta>;
|
|
3
6
|
export const meta: Record<string, ComponentMeta>;
|
|
4
7
|
export default meta;
|
package/types/nuxt.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface ModuleOptions {
|
|
2
2
|
css?: boolean;
|
|
3
3
|
prefix?: string;
|
|
4
|
+
/** Add-on packages (for example '@ziyarago/ui-pro'): their components auto-import and their style.css is added after the core css. */
|
|
5
|
+
plugins?: string[];
|
|
4
6
|
theme?: false | { theme?: 'dark' | 'light'; palette?: 'gold' | 'tamam'; dir?: 'ltr' | 'rtl'; bg?: 'none' | 'aurora' | 'sunset' | 'ocean' | 'emerald' | 'rose' | 'mesh' };
|
|
5
7
|
}
|
|
6
8
|
declare const _default: import('@nuxt/schema').NuxtModule<ModuleOptions>;
|
package/types/react.d.ts
CHANGED
|
@@ -7739,3 +7739,5 @@ export const UiVersusBar: ComponentType<UiVersusBarProps>;
|
|
|
7739
7739
|
export const rules: typeof import('./shared').rules;
|
|
7740
7740
|
export { useUiTheme } from './theme';
|
|
7741
7741
|
export { fmt, setIconBase } from './shared';
|
|
7742
|
+
/** Extension API: wraps plugin component definitions like the built-in ones, registers them (C.Name cross references work) and returns { UiName: Component }. */
|
|
7743
|
+
export function defineUiComponents(defs: Record<string, import('./core').ComponentDefinition>, options?: { override?: boolean }): Record<string, ComponentType<any>>;
|
package/types/resolver.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export interface ZiyaragoUiResolverOptions {
|
|
|
7
7
|
exclude?: string[];
|
|
8
8
|
/** Module the components come from. Default '@ziyarago/ui/vue'. */
|
|
9
9
|
from?: string;
|
|
10
|
+
/** Add-on packages whose components auto-import too: a package name exporting ./names and ./vue, or an explicit { from, names }. */
|
|
11
|
+
plugins?: Array<string | { from: string; names: string[] }>;
|
|
10
12
|
}
|
|
11
13
|
export interface ZiyaragoUiResolverResult { name: string; from: string; sideEffects?: string }
|
|
12
14
|
export interface ZiyaragoUiResolver { type: 'component'; resolve(name: string): ZiyaragoUiResolverResult | undefined }
|
package/types/vue.d.ts
CHANGED
|
@@ -7217,6 +7217,8 @@ export const UiVersusBar: UiVueComponent<UiVersusBarProps, UiVersusBarSlots>;
|
|
|
7217
7217
|
|
|
7218
7218
|
export const rules: typeof import('./shared').rules;
|
|
7219
7219
|
export function install(app: import('vue').App): void;
|
|
7220
|
+
/** Extension API: wraps plugin component definitions like the built-in ones, registers them (C.Name cross references work) and returns { UiName: Component }. */
|
|
7221
|
+
export function defineUiComponents(defs: Record<string, import('./core').ComponentDefinition>, options?: { override?: boolean }): Record<string, import('vue').Component>;
|
|
7220
7222
|
declare const _default: { install: typeof install };
|
|
7221
7223
|
export default _default;
|
|
7222
7224
|
export { useUiTheme } from './theme';
|