@ziyarago/ui 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +23 -2
- package/dist/llms-full.txt +2 -2
- package/dist/llms.txt +2 -2
- package/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/src/core/components.js +15 -14
- package/src/core/components2.js +37 -36
- package/src/core/components3.js +8 -7
- package/src/core/components4.js +23 -22
- package/src/core/components6.js +8 -7
- package/src/core/components7.js +10 -9
- package/src/core/index.js +1 -1
- package/src/i18n/components/ActionCard.js +5 -0
- package/src/i18n/components/Actionsheet.js +5 -0
- package/src/i18n/components/Alert.js +5 -0
- package/src/i18n/components/Breadcrumbs.js +5 -0
- package/src/i18n/components/Calendar.js +5 -0
- package/src/i18n/components/CallCard.js +5 -0
- package/src/i18n/components/CallRow.js +5 -0
- package/src/i18n/components/Carousel.js +5 -0
- package/src/i18n/components/ChartCard.js +5 -0
- package/src/i18n/components/ChatAi.js +5 -0
- package/src/i18n/components/Chip.js +5 -0
- package/src/i18n/components/ChipInput.js +5 -0
- package/src/i18n/components/CodeInput.js +5 -0
- package/src/i18n/components/Combobox.js +5 -0
- package/src/i18n/components/CommandPalette.js +5 -0
- package/src/i18n/components/ConfirmDialog.js +5 -0
- package/src/i18n/components/ContactPanel.js +5 -0
- package/src/i18n/components/CopyButton.js +2 -0
- package/src/i18n/components/DataGroup.js +5 -0
- package/src/i18n/components/DataState.js +5 -0
- package/src/i18n/components/DataTable.js +5 -0
- package/src/i18n/components/DatePicker.js +5 -0
- package/src/i18n/components/DealCard.js +5 -0
- package/src/i18n/components/Drawer.js +5 -0
- package/src/i18n/components/ErrorPage.js +5 -0
- package/src/i18n/components/FileUpload.js +5 -0
- package/src/i18n/components/FullscreenDialog.js +5 -0
- package/src/i18n/components/Gallery.js +5 -0
- package/src/i18n/components/GuestCard.js +5 -0
- package/src/i18n/components/ImageUpload.js +5 -0
- package/src/i18n/components/ImageViewer.js +5 -0
- package/src/i18n/components/IncomingCall.js +5 -0
- package/src/i18n/components/InlineEdit.js +5 -0
- package/src/i18n/components/IntegrationCard.js +5 -0
- package/src/i18n/components/LoginForm.js +5 -0
- package/src/i18n/components/Map.js +5 -0
- package/src/i18n/components/Modal.js +5 -0
- package/src/i18n/components/MultiSelect.js +5 -0
- package/src/i18n/components/NotificationBell.js +5 -0
- package/src/i18n/components/NumberInput.js +5 -0
- package/src/i18n/components/Pagination.js +5 -0
- package/src/i18n/components/PasswordInput.js +5 -0
- package/src/i18n/components/Popconfirm.js +5 -0
- package/src/i18n/components/PricingCard.js +5 -0
- package/src/i18n/components/RamadanTracker.js +5 -0
- package/src/i18n/components/RangeCalendar.js +5 -0
- package/src/i18n/components/RangeSlider.js +5 -0
- package/src/i18n/components/Rating.js +5 -0
- package/src/i18n/components/RichEditor.js +5 -0
- package/src/i18n/components/Screensaver.js +5 -0
- package/src/i18n/components/SearchInput.js +5 -0
- package/src/i18n/components/SideNav.js +5 -0
- package/src/i18n/components/Snackbar.js +5 -0
- package/src/i18n/components/Sortable.js +5 -0
- package/src/i18n/components/Spinner.js +5 -0
- package/src/i18n/components/StatusPage.js +5 -0
- package/src/i18n/components/StepDialog.js +5 -0
- package/src/i18n/components/StepForm.js +5 -0
- package/src/i18n/components/TabTiles.js +5 -0
- package/src/i18n/components/Tag.js +5 -0
- package/src/i18n/components/TopNav.js +5 -0
- package/src/i18n/components/Tour.js +5 -0
- package/src/i18n/components/TourCard.js +5 -0
- package/src/i18n/components/TourPopover.js +5 -0
- package/src/i18n/components/TreeSelect.js +5 -0
- package/src/i18n/components/Validation.js +5 -0
- package/src/i18n/components/index.js +130 -0
- package/src/i18n/index.js +8 -6
- package/types/i18n.d.ts +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [0.4.0](https://github.com/ziyarago/ui/compare/v0.3.0...v0.4.0) (2026-10-21)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* per-component translations (en, ar, ru free; 13 more languages in Pro pack), screenshots in README ([5fc8d9f](https://github.com/ziyarago/ui/commit/5fc8d9ffd85be7cacf42c5413561159d287d0316))
|
|
6
|
+
|
|
1
7
|
## [0.3.0](https://github.com/ziyarago/ui/compare/v0.2.0...v0.3.0) (2026-10-21)
|
|
2
8
|
|
|
3
9
|
### Features
|
package/README.md
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
# @ziyarago/ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@ziyarago/ui) [](LICENSE) [](https://ziyara.vip)
|
|
4
|
+
|
|
5
|
+
<p align="center"><a href="https://ziyara.vip"><img src="https://raw.githubusercontent.com/ziyarago/ui/main/docs/images/hero-dark.png" alt="ziyarago/ui: glass design system for Vue, Nuxt and React" width="100%"></a></p>
|
|
6
|
+
|
|
7
|
+
Glass design system for Vue 3, Nuxt and React: 182 components, 17 page templates, OKLCH tokens, dark and light, RTL and Arabic, 16 interface languages. One framework-neutral core with thin adapters. Built for Umrah and Hajj products, usable anywhere.
|
|
8
|
+
|
|
9
|
+
<table>
|
|
10
|
+
<tr>
|
|
11
|
+
<td width="50%"><img src="https://raw.githubusercontent.com/ziyarago/ui/main/docs/images/hero-light.png" alt="Light theme"><br><sub>Light and dark themes</sub></td>
|
|
12
|
+
<td width="50%"><img src="https://raw.githubusercontent.com/ziyarago/ui/main/docs/images/hero-rtl.png" alt="Arabic RTL"><br><sub>Arabic, RTL mirrored</sub></td>
|
|
13
|
+
</tr>
|
|
14
|
+
<tr>
|
|
15
|
+
<td><img src="https://raw.githubusercontent.com/ziyarago/ui/main/docs/images/components.png" alt="Component catalog"><br><sub>182 components with props and live examples</sub></td>
|
|
16
|
+
<td><img src="https://raw.githubusercontent.com/ziyarago/ui/main/docs/images/templates.png" alt="Page templates"><br><sub>17 page templates</sub></td>
|
|
17
|
+
</tr>
|
|
18
|
+
<tr>
|
|
19
|
+
<td><img src="https://raw.githubusercontent.com/ziyarago/ui/main/docs/images/button-docs.png" alt="Props docs"><br><sub>Every prop and variant documented</sub></td>
|
|
20
|
+
<td><img src="https://raw.githubusercontent.com/ziyarago/ui/main/docs/images/backgrounds.png" alt="Animated backgrounds"><br><sub>Animated backgrounds and gradient cards</sub></td>
|
|
21
|
+
</tr>
|
|
22
|
+
</table>
|
|
23
|
+
|
|
24
|
+
Files:
|
|
4
25
|
|
|
5
26
|
```
|
|
6
|
-
src/core/ components
|
|
27
|
+
src/core/ components*.js, pages.js: 182 components and 17 page templates as { state, slots, render(ctx) }; no framework imports
|
|
7
28
|
src/vue/ Vue 3 adapter (UiButton, UiAppShell … + install(app))
|
|
8
29
|
src/react/ React adapter (same names)
|
|
9
30
|
src/theme.js useUiTheme(): theme · palette · dir → <html> attributes, persisted
|
package/dist/llms-full.txt
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @ziyarago/ui
|
|
2
2
|
|
|
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,
|
|
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, 182 components and 17 page templates.
|
|
4
4
|
|
|
5
|
-
Version 0.
|
|
5
|
+
Version 0.4.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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @ziyarago/ui
|
|
2
2
|
|
|
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,
|
|
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, 182 components and 17 page templates.
|
|
4
4
|
|
|
5
|
-
Version 0.
|
|
5
|
+
Version 0.4.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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziyarago/ui",
|
|
3
|
-
"version": "0.
|
|
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,
|
|
3
|
+
"version": "0.4.0",
|
|
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, 182 components and 17 page templates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui",
|
|
7
7
|
"design-system",
|
package/src/core/components.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Each component: { state?, render({ h, props, children, slots, state, set }) }.
|
|
3
3
|
// h(tag, props, ...children) is supplied by the Vue / React adapter. No hooks inside.
|
|
4
4
|
import { trap } from './dialog.js';
|
|
5
|
+
import { t } from '../i18n/index.js';
|
|
5
6
|
export const cx = (...a) => a.filter(Boolean).join(' ');
|
|
6
7
|
// Copies the listed props that are set; used to forward native attributes and listeners to the real <input>/<textarea>.
|
|
7
8
|
export const pick = (p, keys) => { const o = {}; for (const k of keys) if (p[k] !== undefined) o[k] = p[k]; return o; };
|
|
@@ -78,22 +79,22 @@ export const components = {
|
|
|
78
79
|
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); } },
|
|
79
80
|
CodeInput: { render: ({ h, props: p }) => { const n = p.length || 6, v = p.modelValue || '';
|
|
80
81
|
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] || '')),
|
|
81
|
-
h('input', { value: v, inputMode: 'numeric', autoComplete: 'one-time-code', maxLength: n, 'aria-label': p.label || '
|
|
82
|
+
h('input', { value: v, inputMode: 'numeric', autoComplete: 'one-time-code', maxLength: n, 'aria-label': p.label || t('CodeInput', 'label'), onInput: e => { const x = e.target.value.replace(/\D/g, '').slice(0, n); p.onChange && p.onChange(x); if (x.length === n && v.length !== n) p.onComplete && p.onComplete(x); } })); } },
|
|
82
83
|
Stepper: { render: ({ h, props: p }) => h('div', { class: 'zg-stepper' }, ...(p.steps || []).flatMap((s, i) => [i ? h('span', { key: 'b' + i, class: 'zg-step__bar' }) : null, h('span', { key: i, class: cx('zg-step', i < p.current && 'zg-step--done', i === p.current && 'zg-step--cur') }, h('span', { class: 'zg-step__n' }, i < p.current ? icon(h, 'check', 14) : i + 1), s)])) },
|
|
83
84
|
Card: { slots: ['header', 'footer'], render: ({ h, props: p, children, slots }) => h('section', { class: cx('zg-card', 'zg-glass', p.variant === 'inverse' && 'zg-card--inverse', p.variant === 'gradient' && 'zg-card--grad', p.variant === 'gradient' && 'zg-card--t-' + (p.tone || 'brand'), p.variant === 'gradient' && p.intensity === 'vivid' && 'zg-card--vivid', p.variant === 'outline' && 'zg-card--outline', p.padding === 'none' && 'zg-card--flush', p.interactive && 'zg-card--click'), 'data-theme': p.variant === 'inverse' ? 'dark' : (p.variant === 'gradient' && p.intensity === 'vivid' ? (['brand', 'warning', undefined].includes(p.tone) ? 'light' : 'dark') : undefined), onClick: p.onClick, tabIndex: p.interactive && p.onClick ? 0 : undefined, onKeydown: p.interactive && p.onClick ? e => { if (e.key === 'Enter' && e.target === e.currentTarget) p.onClick(e); } : undefined },
|
|
84
85
|
slots.header ? h('div', { class: 'zg-card__head' }, slots.header) : (p.title || slots.actions) ? 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), slots.actions || null) : null, ...children, slots.footer ? h('div', { class: 'zg-card__foot' }, slots.footer) : null) },
|
|
85
86
|
Modal: { slots: ['title', 'subtitle'], render: ({ h, props: p, children, slots }) => { const foot = slots.footer || p.footer, sub = slots.subtitle || p.subtitle, ttl = slots.title || p.title; return !p.open ? null : h('div', { class: 'zg-modal-scrim', onClick: p.closeOnBackdrop === false ? undefined : p.onClose }, h('div', { class: 'zg-modal', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.title, style: p.width ? { maxWidth: p.width } : undefined, ref: trap({ onClose: p.closeOnEsc === false ? undefined : p.onClose }), onClick: e => e.stopPropagation() },
|
|
86
|
-
h('div', { class: 'zg-modal__head' }, sub ? h('div', { style: { flex: 1, minWidth: 0 } }, h('h3', null, ttl), h('div', { class: 'zg-modal__sub' }, sub)) : h('h3', null, ttl), p.closable === false ? null : h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': '
|
|
87
|
+
h('div', { class: 'zg-modal__head' }, sub ? h('div', { style: { flex: 1, minWidth: 0 } }, h('h3', null, ttl), h('div', { class: 'zg-modal__sub' }, sub)) : h('h3', null, ttl), p.closable === false ? null : h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('Modal', 'close'), onClick: p.onClose }, icon(h, 'x'))), h('div', { class: 'zg-modal__body' }, ...children), foot ? h('div', { class: 'zg-modal__foot' }, foot) : null)); } },
|
|
87
88
|
Drawer: { render: ({ h, props: p, children, slots }) => { const foot = slots.footer || p.footer; return !p.open ? null : h('div', { class: cx('zg-modal-scrim', 'zg-drawer-scrim', 'zg-drawer-scrim--' + (p.side || 'end')), onClick: p.onClose }, h('div', { class: 'zg-modal zg-drawer', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.title, ref: trap({ onClose: p.onClose }), onClick: e => e.stopPropagation() },
|
|
88
|
-
h('div', { class: 'zg-modal__head' }, h('h3', null, p.title), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': '
|
|
89
|
+
h('div', { class: 'zg-modal__head' }, h('h3', null, p.title), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('Drawer', 'close'), onClick: p.onClose }, icon(h, 'x'))), h('div', { class: 'zg-modal__body' }, ...children), foot ? h('div', { class: 'zg-modal__foot' }, foot) : null)); } },
|
|
89
90
|
Toast: { render: ({ h, props: p, children }) => h('div', { class: cx('zg-toast', 'zg-toast--' + (p.tone || 'info')), role: 'status' }, h('span', { class: 'zg-toast__icon' }, icon(h, { success: 'check', danger: 'circle-alert', warning: 'triangle-alert' }[p.tone] || 'info', 18)), h('div', null, p.title ? h('b', null, p.title) : null, h('span', null, ...children))) },
|
|
90
|
-
Breadcrumbs: { render: ({ h, props: p }) => h('nav', { class: 'zg-crumbs', 'aria-label': '
|
|
91
|
+
Breadcrumbs: { render: ({ h, props: p }) => h('nav', { class: 'zg-crumbs', 'aria-label': t('Breadcrumbs', 'label') }, ...(p.items || []).flatMap((it, i, a) => [i ? (p.separator === 'slash' || p.separator === 'dot' ? h('span', { key: 's' + i, class: 'zg-crumbs__sep', 'aria-hidden': 'true' }, p.separator === 'slash' ? '/' : '\u00b7') : icon(h, 'chevron-right', 14)) : null, i === a.length - 1 ? h('b', { key: i, 'aria-current': 'page' }, it.label) : h('a', { key: i, href: it.href || '#' }, it.label)])) },
|
|
91
92
|
Pagination: { render: ({ h, props: p }) => { const total = Math.max(1, Math.floor(+p.pages) || 1), cur = Math.min(total, Math.max(1, +p.page || 1)), s = Math.max(0, p.siblings == null ? 1 : Math.floor(+p.siblings) || 0), go = n => p.onChange && n >= 1 && n <= total && n !== p.page && p.onChange(n);
|
|
92
93
|
const set = new Set([1, total]); for (let i = cur - s; i <= cur + s; i++) if (i >= 1 && i <= total) set.add(i);
|
|
93
94
|
const nums = [...set].sort((a, b) => a - b), items = []; nums.forEach((n, i) => { const prev = nums[i - 1]; if (prev && n - prev === 2) items.push(prev + 1); else if (prev && n - prev > 2) items.push('…'); items.push(n); });
|
|
94
|
-
return h('nav', { class: 'zg-pager', 'aria-label': p.label || 'Pagination' }, p.hideArrows ? null : h('button', { key: 'prev', type: 'button', class: 'zg-pager__nav', 'aria-label': (p.labels && p.labels.prev) || '
|
|
95
|
-
...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) || '
|
|
96
|
-
p.hideArrows ? null : h('button', { key: 'next', type: 'button', class: 'zg-pager__nav', 'aria-label': (p.labels && p.labels.next) || '
|
|
95
|
+
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
|
+
...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
|
+
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))); } },
|
|
97
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)) + '%' } }))) },
|
|
98
99
|
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)';
|
|
99
100
|
const r = (s - th) / 2 - (ticks ? 12 : 0), cir = 2 * Math.PI * r, tk = [];
|
|
@@ -105,14 +106,14 @@ export const components = {
|
|
|
105
106
|
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) },
|
|
106
107
|
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));
|
|
107
108
|
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);
|
|
108
|
-
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) || '
|
|
109
|
-
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) || '
|
|
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
|
+
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 || '—')))])))); } },
|
|
110
111
|
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) },
|
|
111
112
|
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) },
|
|
112
113
|
Timeline: { render: ({ h, props: p }) => h('div', { class: 'zg-timeline' }, ...(p.items || []).map((it, i, a) => h('div', { key: i, class: 'zg-tl' }, h('div', { class: 'zg-tl__rail' }, h('span', { class: 'zg-tl__dot' }), i < a.length - 1 ? h('span', { class: 'zg-tl__line' }) : null), h('div', { class: 'zg-tl__body' }, h('b', null, it.title), it.text ? h('span', null, it.text) : null, h('span', { class: 'zg-tl__time' }, it.time))))) },
|
|
113
|
-
TopNav: { render: ({ h, props: p, slots }) => h('header', { class: 'zg-topnav' }, h('div', { class: 'zg-topnav__brand' }, p.brand), h('nav', { class: 'zg-topnav__items', 'aria-label': '
|
|
114
|
+
TopNav: { render: ({ h, props: p, slots }) => h('header', { class: 'zg-topnav' }, h('div', { class: 'zg-topnav__brand' }, p.brand), h('nav', { class: 'zg-topnav__items', 'aria-label': t('TopNav', 'label') }, ...(p.items || []).map(it => nav(h, it, p.value, p.onChange))), h('div', { class: 'zg-topnav__actions' }, slots.actions || p.actions || null)) },
|
|
114
115
|
SideNav: { render: ({ h, props: p, slots }) => h('aside', { class: cx('zg-sidenav', p.collapsed && 'zg-sidenav--collapsed') }, h('div', { class: 'zg-sidenav__brand' }, p.brand), ...(p.items || []).map(it => it.group ? h('div', { key: it.group, class: 'zg-sidenav__group', style: p.collapsed ? { display: 'none' } : undefined }, it.group) : nav(h, it, p.value, p.onChange, p.collapsed)), h('div', { class: 'zg-sidenav__sp' }), slots.footer || p.footer || null,
|
|
115
|
-
p.onToggle ? h('button', { type: 'button', class: 'zg-navitem', onClick: p.onToggle, 'aria-label': '
|
|
116
|
+
p.onToggle ? h('button', { type: 'button', class: 'zg-navitem', onClick: p.onToggle, 'aria-label': t('SideNav', 'toggle') }, icon(h, p.collapsed ? 'panel-left-open' : 'panel-left-close', 18), h('span', null, p.collapseLabel || t('SideNav', 'collapse'))) : null) },
|
|
116
117
|
AppShell: { state: { collapsed: false }, render: ({ C, h, props: p, children, slots, state, set }) => {
|
|
117
118
|
const brand = p.brand, top = (p.nav || 'top') === 'top', col = p.collapsed ?? state.collapsed, toggle = () => p.onCollapse ? p.onCollapse(!col) : set({ collapsed: !state.collapsed });
|
|
118
119
|
return h('div', { class: cx('zg-root', 'zg-shell', top && 'zg-shell--top', p.frame && 'zg-shell--frame'), style: { background: 'var(--zg-bg)' } },
|
|
@@ -124,9 +125,9 @@ export const components = {
|
|
|
124
125
|
HotelCard: { render: ({ h, props: p }) => h('article', { class: 'zg-hotel zg-dm' }, h('div', { class: 'zg-hotel__img', style: { backgroundImage: p.image ? `url(${p.image})` : undefined } }, h('div', { class: 'zg-hotel__tags' }, ...(p.tags || []).map(t => h('span', { key: t, class: 'zg-badge zg-badge--brand' }, t)))), h('div', { class: 'zg-hotel__body' },
|
|
125
126
|
h('div', { class: 'zg-hotel__row' }, h('div', { style: { minWidth: 0 } }, h('b', { style: { fontSize: dzf(17) } }, p.name), h('div', { class: 'zg-hint' }, '★'.repeat(p.stars || 0), ' · ', p.distance)), h('span', { class: 'zg-badge zg-badge--success', style: { marginInlineStart: 'auto' } }, p.rating)),
|
|
126
127
|
h('div', { class: 'zg-hotel__row', style: { alignItems: 'center' } }, h('span', { class: 'zg-hint' }, 'SAR / ', p.night || 'night'), h('div', { class: 'zg-hotel__price' }, h('div', { class: 'zg-num', style: { fontSize: dzf(26), fontWeight: 300 } }, fmt.num(p.price))))) ) },
|
|
127
|
-
TourCard: { render: ({ C, h, props: p, slots }) => { const seats = Number(p.seats) || 0, booked = Number(p.booked) || 0; return h('article', { class: 'zg-card zg-glass zg-dm' }, h('div', { class: 'zg-card__head' }, h('div', { style: { flex: 1 } }, h('h3', { class: 'zg-card__title' }, p.name), h('div', { class: 'zg-card__sub' }, [p.city, p.sessions != null && p.sessions !== '' ? p.sessions + ' sessions' : null].filter(Boolean).join(' · '))), h('span', { class: 'zg-badge zg-badge--info' }, '360°')), h(C.Progress, { value: seats > 0 ? Math.min(100, booked / seats * 100) : 0, label: '
|
|
128
|
+
TourCard: { render: ({ C, h, props: p, slots }) => { const seats = Number(p.seats) || 0, booked = Number(p.booked) || 0; return h('article', { class: 'zg-card zg-glass zg-dm' }, h('div', { class: 'zg-card__head' }, h('div', { style: { flex: 1 } }, h('h3', { class: 'zg-card__title' }, p.name), h('div', { class: 'zg-card__sub' }, [p.city, p.sessions != null && p.sessions !== '' ? p.sessions + ' sessions' : null].filter(Boolean).join(' · '))), h('span', { class: 'zg-badge zg-badge--info' }, '360°')), h(C.Progress, { value: seats > 0 ? Math.min(100, booked / seats * 100) : 0, label: t('TourCard', 'seats'), meta: `${booked}/${seats}` }), slots.actions || null); } },
|
|
128
129
|
PrayerTimes: { render: ({ h, props: p }) => h('div', { class: 'zg-pt zg-dm' }, ...(p.items || []).map((it, i) => { it = it || {}; return h('div', { key: i, class: 'zg-pt__i', 'aria-current': i === p.next ? 'true' : undefined }, it.name, h('b', null, it.time)); })) },
|
|
129
|
-
RamadanTracker: { render: ({ h, props: p }) => { const day = Number(p.day) || 0; return h('div', { class: 'zg-rt zg-dm', role: 'img', 'aria-label': day ?
|
|
130
|
-
CallRow: { render: ({ C, h, props: p }) => h('div', { class: 'zg-call zg-dm' }, h(C.Avatar, { name: p.name, size: dzp(p, 36) }), h('div', { style: { minWidth: 0 } }, h('b', { style: { fontSize: dzf(14) } }, p.name), h('div', { class: 'zg-hint' }, [p.status, fmt.dur(p.duration, p.durUnits)].filter(Boolean).join(' · '))), h(C.IconButton, { icon: 'play', label: (p.labels && p.labels.play) || '
|
|
130
|
+
RamadanTracker: { render: ({ h, props: p }) => { const day = Number(p.day) || 0; return h('div', { class: 'zg-rt zg-dm', role: 'img', 'aria-label': day ? t('RamadanTracker', 'day', { day }) : t('RamadanTracker', 'label') }, ...Array.from({ length: 30 }, (_, i) => { const n = i + 1; return h('i', { key: n, 'data-s': n === day ? 'cur' : n < day ? 'past' : n > 20 ? 'last' : undefined, 'data-odd': n > 20 && n % 2 ? '' : undefined, title: String(n) }); })); } },
|
|
131
|
+
CallRow: { render: ({ C, h, props: p }) => h('div', { class: 'zg-call zg-dm' }, h(C.Avatar, { name: p.name, size: dzp(p, 36) }), h('div', { style: { minWidth: 0 } }, h('b', { style: { fontSize: dzf(14) } }, p.name), h('div', { class: 'zg-hint' }, [p.status, fmt.dur(p.duration, p.durUnits)].filter(Boolean).join(' · '))), h(C.IconButton, { icon: 'play', label: (p.labels && p.labels.play) || t('CallRow', 'play'), size: dzb(p), onClick: p.onPlay })) },
|
|
131
132
|
};
|
|
132
133
|
export { fmt };
|
package/src/core/components2.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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 } from './components.js';
|
|
3
3
|
import { trap } from './dialog.js';
|
|
4
|
-
import { t } from '../i18n/index.js';
|
|
4
|
+
import { t, getUiLocale, intlLocale } from '../i18n/index.js';
|
|
5
|
+
const dloc = p => p.locale || (getUiLocale() === 'en' ? 'en-GB' : intlLocale());
|
|
5
6
|
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
6
7
|
const num = v => Number.isFinite(+v) && v !== null && v !== '' ? +v : 0;
|
|
7
8
|
const arr = v => Array.isArray(v) ? v : [];
|
|
@@ -33,31 +34,31 @@ const sel = (h, o, on) => ({ 'aria-selected': String(on), role: 'option' });
|
|
|
33
34
|
|
|
34
35
|
export const more = {
|
|
35
36
|
/* ── Surfaces ── */
|
|
36
|
-
Alert: { render: ({ h, props: p, children, slots }) => h('div', { class: cx('zg-alert', 'zg-alert--' + (p.tone || 'info'), p.banner && 'zg-banner'), role: 'alert' }, p.hideIcon ? null : h('span', { class: 'zg-alert__i' }, slots.icon || icon(h, TONE[p.tone] || 'info', 18)), h('div', { class: 'zg-alert__b' }, (slots.title || p.title) ? h('b', null, slots.title || p.title) : null, h('span', null, ...children), slots.actions ? h('div', { class: 'zg-alert__act' }, slots.actions) : null), p.onClose ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': '
|
|
37
|
+
Alert: { render: ({ h, props: p, children, slots }) => h('div', { class: cx('zg-alert', 'zg-alert--' + (p.tone || 'info'), p.banner && 'zg-banner'), role: 'alert' }, p.hideIcon ? null : h('span', { class: 'zg-alert__i' }, slots.icon || icon(h, TONE[p.tone] || 'info', 18)), h('div', { class: 'zg-alert__b' }, (slots.title || p.title) ? h('b', null, slots.title || p.title) : null, h('span', null, ...children), slots.actions ? h('div', { class: 'zg-alert__act' }, slots.actions) : null), p.onClose ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('Alert', 'close'), onClick: p.onClose }, icon(h, 'x')) : null) },
|
|
37
38
|
Banner: { render: ({ C, h, props: p, children }) => h(C.Alert, { ...p, banner: true }, ...children) },
|
|
38
39
|
Accordion: { state: { open: null }, render: ({ h, props: p, state, set }) => h('div', { class: 'zg-acc' }, ...(p.items || []).map(it => { const o = state.open === it.id; return h('div', { key: it.id, class: 'zg-acc__i', 'data-open': String(o) }, h('button', { type: 'button', 'aria-expanded': String(o), class: 'zg-acc__h', onClick: () => set({ open: o ? null : it.id }) }, it.title, icon(h, 'chevron-down', 16)), o ? h('div', { class: 'zg-acc__b' }, it.body) : null); })) },
|
|
39
40
|
Skeleton: { render: ({ h, props: p }) => h('div', { class: 'zg-skelw' }, ...Array.from({ length: p.lines || 1 }, (_, i) => h('span', { key: i, class: 'zg-skel zg-skel--' + (p.variant || 'text'), style: { width: p.variant === 'circle' ? ({ xs: 24, sm: 32, md: 40, lg: 56, xl: 72 }[p.size] || p.size || 40) + 'px' : (i === (p.lines || 1) - 1 && (p.lines || 1) > 1 ? '60%' : p.width || '100%'), height: p.height || (p.variant === 'circle' ? ({ xs: 24, sm: 32, md: 40, lg: 56, xl: 72 }[p.size] || p.size || 40) + 'px' : undefined) } }))) },
|
|
40
|
-
Spinner: { render: ({ h, props: p }) => h('span', { class: 'zg-spin', role: 'status', 'aria-label': '
|
|
41
|
+
Spinner: { render: ({ h, props: p }) => h('span', { class: 'zg-spin', role: 'status', 'aria-label': t('Spinner', 'loading'), style: { fontSize: ({ xs: 14, sm: 16, md: 20, lg: 28, xl: 36 }[p.size] || p.size || 20) + 'px' } }) },
|
|
41
42
|
Divider: { render: ({ h, props: p }) => h('div', { class: 'zg-div', role: 'separator' }, p.label ? h('span', null, p.label) : null) },
|
|
42
43
|
EmptyState: { render: ({ h, props: p, children }) => h('div', { class: 'zg-es' }, p.image ? h('span', { class: 'zg-es__img', style: { backgroundImage: `url(${p.image})` } }) : h('span', { class: 'zg-es__ico' }, icon(h, p.icon || 'inbox', 28)), h('b', null, p.title), p.description ? h('span', null, p.description) : null, children.length ? h('div', { class: 'zg-es__a' }, ...children) : null) },
|
|
43
|
-
ErrorPage: { render: ({ C, h, props: p, children }) => h('div', { class: 'zg-es zg-es--page' }, h('span', { class: 'zg-num', style: { fontSize: 'clamp(80px,14vw,160px)', lineHeight: 1, color: 'var(--zg-brand-text)' } }, p.code || '404'), h('b', { style: { fontSize: '24px' } }, p.title || '
|
|
44
|
-
DataState: { render: ({ C, h, props: p, children }) => ({ ready: () => h('div', null, ...children), loading: () => h('div', { class: 'zg-es' }, h(C.Spinner, { size: 28 }), h('b', null, p.title || '
|
|
44
|
+
ErrorPage: { render: ({ C, h, props: p, children }) => h('div', { class: 'zg-es zg-es--page' }, h('span', { class: 'zg-num', style: { fontSize: 'clamp(80px,14vw,160px)', lineHeight: 1, color: 'var(--zg-brand-text)' } }, p.code || '404'), h('b', { style: { fontSize: '24px' } }, p.title || t('ErrorPage', 'title')), h('span', null, p.description || t('ErrorPage', 'description')), h('div', { class: 'zg-es__a' }, ...children)) },
|
|
45
|
+
DataState: { render: ({ C, h, props: p, children }) => ({ ready: () => h('div', null, ...children), loading: () => h('div', { class: 'zg-es' }, h(C.Spinner, { size: 28 }), h('b', null, p.title || t('DataState', 'loading'))), empty: () => h(C.EmptyState, { title: p.title || t('DataState', 'empty'), description: p.description, icon: 'inbox' }), error: () => h(C.EmptyState, { title: p.title || t('DataState', 'error'), description: p.description, icon: 'circle-alert' }, h(C.Button, { onClick: p.onRetry }, p.retryLabel || t('DataState', 'retry'))), offline: () => h(C.EmptyState, { title: p.title || t('DataState', 'offline'), description: p.description || t('DataState', 'offlineDesc'), icon: 'wifi-off' }, h(C.Button, { onClick: p.onRetry }, p.retryLabel || t('DataState', 'tryAgain'))) }[p.state || 'ready'])() },
|
|
45
46
|
LoadingOverlay: { render: ({ C, h, props: p, children }) => h('div', { class: 'zg-lo' }, ...children, p.loading ? h('div', { class: 'zg-lo__o' }, h(C.Spinner, { size: 28 }), p.label ? h('span', null, p.label) : null) : null) },
|
|
46
|
-
ConfirmDialog: { render: ({ C, h, props: p, children }) => h(C.Modal, { open: p.open, size: p.size, title: p.title, onClose: p.onCancel, footer: [h(C.Button, { key: 'c', variant: 'ghost', onClick: p.onCancel }, p.cancelLabel || '
|
|
47
|
-
Popconfirm: { state: { open: false }, render: ({ C, h, props: p, state, set }) => h('span', { class: 'zg-dd' }, h(C.Button, { size: p.size || 'sm', variant: p.variant || 'danger', onClick: () => set({ open: !state.open }) }, p.label), state.open ? h('div', { class: 'zg-pop zg-glass', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, h('b', { style: { fontSize: '14px' } }, p.title), h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' } }, h(C.Button, { size: 'xs', variant: 'ghost', onClick: () => set({ open: false }) }, p.cancelLabel || '
|
|
47
|
+
ConfirmDialog: { render: ({ C, h, props: p, children }) => h(C.Modal, { open: p.open, size: p.size, title: p.title, onClose: p.onCancel, footer: [h(C.Button, { key: 'c', variant: 'ghost', onClick: p.onCancel }, p.cancelLabel || t('ConfirmDialog', 'cancel')), h(C.Button, { key: 'k', variant: p.tone === 'danger' ? 'danger' : 'primary', loading: p.loading, onClick: p.onConfirm }, p.confirmLabel || t('ConfirmDialog', 'confirm'))] }, h('span', { style: { color: 'var(--zg-text-2)', lineHeight: 1.6 } }, p.description, ...children)) },
|
|
48
|
+
Popconfirm: { state: { open: false }, render: ({ C, h, props: p, state, set }) => h('span', { class: 'zg-dd' }, h(C.Button, { size: p.size || 'sm', variant: p.variant || 'danger', onClick: () => set({ open: !state.open }) }, p.label), state.open ? h('div', { class: 'zg-pop zg-glass', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, h('b', { style: { fontSize: '14px' } }, p.title), h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' } }, h(C.Button, { size: 'xs', variant: 'ghost', onClick: () => set({ open: false }) }, p.cancelLabel || t('Popconfirm', 'no')), h(C.Button, { size: 'xs', variant: 'primary', onClick: () => { set({ open: false }); p.onConfirm && p.onConfirm(); } }, p.confirmLabel || t('Popconfirm', 'yes')))) : null) },
|
|
48
49
|
Carousel: { state: { i: 0 }, render: ({ h, props: p, state, set }) => { const items = arr(p.items).map(x => x || {}), n = items.length, i = idx(p, state, n), it = items[i] || {}, L = p.labels || {};
|
|
49
50
|
const to = k => { if (!n) return; k = ((k % n) + n) % n; if (k === i) return; if (p.index === undefined || p.index === null) set({ i: k }); p.onChange && p.onChange(k); };
|
|
50
51
|
const ms = p.autoplay === true ? 5000 : n > 1 && num(p.autoplay) > 0 ? num(p.autoplay) : 0;
|
|
51
|
-
return h('div', { class: 'zg-car', role: 'region', 'aria-roledescription': 'carousel', 'aria-label': p.label || undefined, ref: el => autoplay(el, n > 1 ? ms : 0, () => to(i + 1)), onKeyDown: e => { if (e.key === 'ArrowLeft') to(i - 1); else if (e.key === 'ArrowRight') to(i + 1); } }, h('div', { class: 'zg-car__s', 'aria-live': ms ? 'off' : 'polite', style: { backgroundImage: it.image ? `url(${it.image})` : undefined } }, h('div', { class: 'zg-car__c' }, it.title ? h('b', null, it.title) : null, it.sub ? h('span', null, it.sub) : null)), n > 1 ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-car__p', 'aria-label': L.prev || '
|
|
52
|
+
return h('div', { class: 'zg-car', role: 'region', 'aria-roledescription': 'carousel', 'aria-label': p.label || undefined, ref: el => autoplay(el, n > 1 ? ms : 0, () => to(i + 1)), onKeyDown: e => { if (e.key === 'ArrowLeft') to(i - 1); else if (e.key === 'ArrowRight') to(i + 1); } }, h('div', { class: 'zg-car__s', 'aria-live': ms ? 'off' : 'polite', style: { backgroundImage: it.image ? `url(${it.image})` : undefined } }, h('div', { class: 'zg-car__c' }, it.title ? h('b', null, it.title) : null, it.sub ? h('span', null, it.sub) : null)), n > 1 ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-car__p', 'aria-label': L.prev || t('Carousel', 'prev'), onClick: () => to(i - 1) }, icon(h, 'chevron-left')) : null, n > 1 ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-car__n', 'aria-label': L.next || t('Carousel', 'next'), onClick: () => to(i + 1) }, icon(h, 'chevron-right')) : null, n > 1 ? h('div', { class: 'zg-car__d' }, ...items.map((_, k) => h('button', { key: k, type: 'button', 'aria-label': (L.slide || t('Carousel', 'slide')) + ' ' + (k + 1), 'aria-current': String(k === i), onClick: () => to(k) }))) : null); } },
|
|
52
53
|
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 || {};
|
|
53
54
|
const to = k => { if (k === i) return; if (p.index === undefined || p.index === null) set({ i: k }); p.onChange && p.onChange(k); };
|
|
54
|
-
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 || '
|
|
55
|
+
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); } },
|
|
55
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)) },
|
|
56
|
-
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 || '
|
|
57
|
-
IntegrationCard: { render: ({ C, h, props: p }) => { const st = ['connected', 'error'].includes(p.status) ? p.status : 'off', tone = { connected: 'success', error: 'danger' }[st], L = { connected: '
|
|
57
|
+
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
|
+
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 || {}) };
|
|
58
59
|
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)); } },
|
|
59
|
-
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 || '
|
|
60
|
-
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) || '
|
|
60
|
+
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
|
+
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)))) },
|
|
61
62
|
/* ── Actions ── */
|
|
62
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: () => { 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) },
|
|
63
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))) },
|
|
@@ -70,64 +71,64 @@ export const more = {
|
|
|
70
71
|
h('span', { role: 'status', 'aria-live': 'polite', style: { position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', clip: 'rect(0 0 0 0)', whiteSpace: 'nowrap' } }, msg)); } },
|
|
71
72
|
CommandBar: { render: ({ h, props: p }) => h('div', { class: cx('zg-cb', 'zg-glass') }, ...(p.items || []).map((it, i) => it.separator ? h('span', { key: i, class: 'zg-cb__s' }) : h('button', { key: i, type: 'button', class: 'zg-ibtn zg-ibtn--ghost zg-ibtn--sm', title: it.label, 'aria-label': it.label, disabled: it.disabled, onClick: () => p.onAction && p.onAction(it.id) }, icon(h, it.icon || 'circle')))) },
|
|
72
73
|
CommandPalette: { state: { q: '', a: 0 }, render: ({ h, props: p, state, set }) => { if (!p.open) return null; const f = (p.commands || []).filter(c => !state.q || (c.label + ' ' + (c.keywords || '')).toLowerCase().includes(state.q.toLowerCase())); const ai = f.length ? clamp(state.a, 0, f.length - 1) : -1; const run = c => { if (!c) return; p.onRun && p.onRun(c.id); p.onClose && p.onClose(); };
|
|
73
|
-
return h('div', { class: 'zg-modal-scrim', style: { alignItems: 'flex-start', paddingTop: '12vh' }, onClick: p.onClose }, h('div', { class: 'zg-modal', style: { maxWidth: '560px' }, role: 'dialog', 'aria-modal': 'true', 'aria-label': '
|
|
74
|
-
h('div', { class: 'zg-modal__head' }, icon(h, 'search', 18), h('input', { value: state.q, autoFocus: true, 'data-autofocus': '', role: 'combobox', 'aria-expanded': 'true', 'aria-controls': 'zg-cp-list', 'aria-activedescendant': ai >= 0 ? 'zg-cp-opt-' + ai : undefined, 'aria-label': p.placeholder || '
|
|
75
|
-
h('div', { class: 'zg-modal__body', id: 'zg-cp-list', role: 'listbox', style: { padding: '8px', gap: '2px' } }, ...(f.length ? f.map((c, i) => h('button', { key: c.id, type: 'button', id: 'zg-cp-opt-' + i, role: 'option', tabIndex: -1, 'aria-selected': String(i === ai), class: cx('zg-menu__i', i === ai && 'is-active'), ref: i === ai ? (el => { if (el && el.scrollIntoView) el.scrollIntoView({ block: 'nearest' }); }) : undefined, onMouseMove: () => { if (i !== ai) set({ a: i }); }, onClick: () => run(c) }, c.icon ? icon(h, c.icon, 16) : null, c.label, c.shortcut ? h('span', { class: 'zg-kbd' }, c.shortcut) : null)) : [h('div', { class: 'zg-empty' }, '
|
|
74
|
+
return h('div', { class: 'zg-modal-scrim', style: { alignItems: 'flex-start', paddingTop: '12vh' }, onClick: p.onClose }, h('div', { class: 'zg-modal', style: { maxWidth: '560px' }, role: 'dialog', 'aria-modal': 'true', 'aria-label': t('CommandPalette', 'label'), ref: trap({ onClose: p.onClose }), onClick: e => e.stopPropagation() },
|
|
75
|
+
h('div', { class: 'zg-modal__head' }, icon(h, 'search', 18), h('input', { value: state.q, autoFocus: true, 'data-autofocus': '', role: 'combobox', 'aria-expanded': 'true', 'aria-controls': 'zg-cp-list', 'aria-activedescendant': ai >= 0 ? 'zg-cp-opt-' + ai : undefined, 'aria-label': p.placeholder || t('CommandPalette', 'placeholder'), placeholder: p.placeholder || t('CommandPalette', 'placeholder'), onInput: e => set({ q: e.target.value, a: 0 }), onKeyDown: e => { if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); if (f.length) set({ a: (ai + (e.key === 'ArrowDown' ? 1 : -1) + f.length) % f.length }); } else if (e.key === 'Enter') { e.preventDefault(); run(f[ai]); } }, style: { flex: 1, border: 0, outline: 0, background: 'none', color: 'var(--zg-text-1)', font: '500 17px var(--zg-font)' } }), h('span', { class: 'zg-kbd' }, 'Esc')),
|
|
76
|
+
h('div', { class: 'zg-modal__body', id: 'zg-cp-list', role: 'listbox', style: { padding: '8px', gap: '2px' } }, ...(f.length ? f.map((c, i) => h('button', { key: c.id, type: 'button', id: 'zg-cp-opt-' + i, role: 'option', tabIndex: -1, 'aria-selected': String(i === ai), class: cx('zg-menu__i', i === ai && 'is-active'), ref: i === ai ? (el => { if (el && el.scrollIntoView) el.scrollIntoView({ block: 'nearest' }); }) : undefined, onMouseMove: () => { if (i !== ai) set({ a: i }); }, onClick: () => run(c) }, c.icon ? icon(h, c.icon, 16) : null, c.label, c.shortcut ? h('span', { class: 'zg-kbd' }, c.shortcut) : null)) : [h('div', { class: 'zg-empty' }, t('CommandPalette', 'empty'))])))); } },
|
|
76
77
|
NotificationBell: { state: { open: false, seen: {}, all: false }, render: ({ C, h, props: p, state, set }) => { const ctl = !!(p.onRead || p.onMarkAllRead), isRead = i => !!i.read || (!ctl && (state.all || !!state.seen[i.id])), un = (p.items || []).filter(i => !isRead(i)).length;
|
|
77
|
-
return h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: 'zg-ibtn', 'aria-label': '
|
|
78
|
-
state.open ? h('div', { class: 'zg-pop zg-glass', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })), style: { width: '320px', padding: '8px' } }, un ? h('button', { key: '_all', type: 'button', class: 'zg-menu__i', style: { justifyContent: 'flex-end', minHeight: '32px', color: 'var(--zg-brand-text)' }, onClick: () => { if (!ctl) set({ all: true }); p.onMarkAllRead && p.onMarkAllRead(); } }, '
|
|
78
|
+
return h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: 'zg-ibtn', 'aria-label': t('NotificationBell', 'label'), 'aria-expanded': String(state.open), onClick: () => set({ open: !state.open }) }, icon(h, 'bell'), un ? h('span', { class: 'zg-bell__n' }, un) : null),
|
|
79
|
+
state.open ? h('div', { class: 'zg-pop zg-glass', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })), style: { width: '320px', padding: '8px' } }, un ? h('button', { key: '_all', type: 'button', class: 'zg-menu__i', style: { justifyContent: 'flex-end', minHeight: '32px', color: 'var(--zg-brand-text)' }, onClick: () => { if (!ctl) set({ all: true }); p.onMarkAllRead && p.onMarkAllRead(); } }, t('NotificationBell', 'markAllRead')) : null, ...(p.items || []).map(it => h('div', { key: it.id, class: 'zg-menu__i', role: 'button', tabIndex: 0, style: { alignItems: 'flex-start', opacity: isRead(it) ? .6 : 1 }, onClick: () => { if (!isRead(it)) { if (!ctl) set({ seen: { ...state.seen, [it.id]: true } }); p.onRead && p.onRead(it.id); } p.onSelect && p.onSelect(it); }, onKeyDown: e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.currentTarget.click(); } } }, h('span', { style: { display: 'flex', flexDirection: 'column', gap: '2px', flex: 1 } }, h('b', { style: { fontSize: '14px' } }, it.title), h('span', { class: 'zg-hint' }, it.message), h('span', { class: 'zg-hint' }, it.time)), !isRead(it) ? h('span', { class: 'zg-tl__dot', style: { margin: '6px 4px' } }) : null))) : null); } },
|
|
79
80
|
ToggleGroup: { render: ({ h, props: p }) => { const cur = p.multiple ? (p.modelValue || []) : [p.modelValue]; return h('div', { class: 'zg-seg', role: 'group' }, ...(p.options || []).map(o => h('button', { key: o.value, type: 'button', 'aria-pressed': String(cur.includes(o.value)), title: o.label, onClick: () => p.onChange && p.onChange(p.multiple ? (cur.includes(o.value) ? cur.filter(v => v !== o.value) : [...cur, o.value]) : (p.allowEmpty && cur.includes(o.value) ? null : o.value)) }, o.icon ? icon(h, o.icon, 16) : null, p.iconOnly ? null : o.label))); } },
|
|
80
81
|
Fab: { render: ({ h, props: p }) => h('button', { type: 'button', class: 'zg-fab', 'aria-label': p.label || p.icon || 'plus', onClick: p.onClick }, icon(h, p.icon || 'plus', 20), p.label ? h('span', null, p.label) : null) },
|
|
81
82
|
/* ── Forms ── */
|
|
82
83
|
Textarea: { render: ({ h, props: p }) => lab(h, p, [h('textarea', { ...pick(p, ['name', 'required', 'autocomplete', 'spellcheck', 'autofocus', 'minlength', 'onBlur', 'onFocus', 'onKeydown', 'onKeyDown', 'onKeyup', 'onKeyUp']), class: cx('zg-ta', p.error && 'zg-input--error', p.resize === false && 'zg-ta--fixed', p.autoResize && 'zg-ta--auto'), rows: p.rows || 4, value: p.modelValue ?? '', placeholder: p.placeholder, maxLength: p.maxlength, disabled: p.disabled, readOnly: p.readonly, onInput: e => { p.onChange && p.onChange(e.target.value); p.onInput && p.onInput(e); } }),
|
|
83
84
|
p.showCount ? h('span', { class: 'zg-hint zg-ta__count' }, String((p.modelValue ?? '').length) + (p.maxlength ? ' / ' + p.maxlength : '')) : null]) },
|
|
84
85
|
NumberInput: { state: { draft: null }, render: ({ C, h, props: p, state, set: put }) => { const v = p.modelValue ?? null, st = p.step || 1, lo = p.min ?? -Infinity, hi = p.max ?? Infinity, parse = s => { const t = String(s).trim().replace(',', '.'); if (t === '') return null; const n = Number(t); return Number.isFinite(n) ? n : NaN; }, emit = n => p.onChange && p.onChange(n), bump = d => { put({ draft: null }); emit(clamp(Math.round(((v ?? 0) + d) * 1e10) / 1e10, lo, hi)); };
|
|
85
|
-
return lab(h, p, h('span', { class: 'zg-input zg-num-in' }, h(C.IconButton, { icon: 'minus', label: '
|
|
86
|
-
PasswordInput: { state: { show: false }, render: ({ C, h, props: p, state, set }) => lab(h, p, field(h, p, icon(h, 'lock-keyhole', 18), h('input', { type: state.show ? 'text' : 'password', value: p.modelValue ?? '', placeholder: p.placeholder, autoComplete: p.autocomplete || 'current-password', onInput: e => p.onChange && p.onChange(e.target.value) }), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': state.show ? '
|
|
87
|
-
SearchInput: { render: ({ h, props: p }) => h('span', { class: 'zg-input', style: { '--h': 'var(--zg-h-md)' } }, icon(h, 'search', 16), h('input', { type: 'search', value: p.modelValue ?? '', placeholder: p.placeholder || '
|
|
86
|
+
return lab(h, p, h('span', { class: 'zg-input zg-num-in' }, h(C.IconButton, { icon: 'minus', label: t('NumberInput', 'decrease'), size: 'sm', onClick: () => bump(-st) }), h('input', { value: state.draft ?? (v ?? ''), inputMode: 'decimal', placeholder: p.placeholder, style: { textAlign: 'center' }, onInput: e => { const raw = e.target.value, n = parse(raw); put({ draft: raw }); if (n === null || !Number.isNaN(n)) emit(n); }, onBlur: e => { const n = parse(e.target.value); put({ draft: null }); if (n === null) { if (v !== null) emit(null); } else if (!Number.isNaN(n)) { const c = clamp(n, lo, hi); if (c !== v) emit(c); } } }), h(C.IconButton, { icon: 'plus', label: t('NumberInput', 'increase'), size: 'sm', onClick: () => bump(st) }))); } },
|
|
87
|
+
PasswordInput: { state: { show: false }, render: ({ C, h, props: p, state, set }) => lab(h, p, field(h, p, icon(h, 'lock-keyhole', 18), h('input', { type: state.show ? 'text' : 'password', value: p.modelValue ?? '', placeholder: p.placeholder, autoComplete: p.autocomplete || 'current-password', onInput: e => p.onChange && p.onChange(e.target.value) }), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': state.show ? t('PasswordInput', 'hide') : t('PasswordInput', 'show'), onClick: () => set({ show: !state.show }) }, icon(h, state.show ? 'eye-off' : 'eye', 16)))) },
|
|
88
|
+
SearchInput: { render: ({ h, props: p }) => h('span', { class: 'zg-input', style: { '--h': 'var(--zg-h-md)' } }, icon(h, 'search', 16), h('input', { type: 'search', value: p.modelValue ?? '', placeholder: p.placeholder || t('SearchInput', 'placeholder'), onInput: e => p.onChange && p.onChange(e.target.value), onKeyDown: e => e.key === 'Enter' && p.onSearch && p.onSearch(p.modelValue) }), p.modelValue ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('SearchInput', 'clear'), onClick: () => p.onChange && p.onChange('') }, icon(h, 'x', 14)) : null) },
|
|
88
89
|
RadioGroup: { render: ({ h, props: p }) => h('div', { class: 'zg-field', role: 'radiogroup', 'aria-disabled': p.disabled ? 'true' : undefined, style: { gap: '10px' } }, p.label ? h('span', { class: 'zg-label' }, p.label) : null,
|
|
89
90
|
h('div', { class: cx('zg-rg', p.orientation === 'horizontal' && 'zg-rg--row') }, ...(p.options || []).map(o => h('button', { key: o.value, type: 'button', role: 'radio', class: 'zg-check', 'aria-checked': String(o.value === p.modelValue), disabled: p.disabled || o.disabled, 'aria-disabled': p.disabled || o.disabled ? 'true' : undefined, onClick: () => !(p.disabled || o.disabled) && p.onChange && p.onChange(o.value) }, h('span', { class: 'zg-check__box zg-radio' }, o.value === p.modelValue ? h('i') : null), h('span', null, o.label, o.hint ? h('span', { class: 'zg-hint', style: { display: 'block' } }, o.hint) : null)))),
|
|
90
91
|
p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null) },
|
|
91
92
|
Slider: { render: ({ h, props: p }) => { const mn = p.min ?? 0, mx = p.max ?? 100, v = p.modelValue ?? mn, pct = (v - mn) / (mx - mn) * 100;
|
|
92
93
|
return lab(h, p, h('span', { style: { display: 'flex', alignItems: 'center', gap: '12px' } }, h('input', { type: 'range', class: 'zg-range', min: mn, max: mx, step: p.step || 1, value: v, style: { '--p': pct + '%' }, onInput: e => p.onChange && p.onChange(Number(e.target.value)) }), p.showValue !== false ? h('span', { class: 'zg-mono', style: { minWidth: '48px', textAlign: 'end' } }, p.formatValue ? p.formatValue(v) : v) : null)); } },
|
|
93
|
-
Rating: { render: ({ h, props: p }) => h('div', { class: 'zg-rate', role: 'radiogroup', 'aria-label': p.label || 'Rating' }, ...Array.from({ length: p.max || 5 }, (_, i) => h('button', { key: i, type: 'button', disabled: p.readonly, 'aria-label': (i + 1)
|
|
94
|
+
Rating: { render: ({ h, props: p }) => h('div', { class: 'zg-rate', role: 'radiogroup', 'aria-label': p.label || t('Rating', 'label') }, ...Array.from({ length: p.max || 5 }, (_, i) => h('button', { key: i, type: 'button', disabled: p.readonly, 'aria-label': t('Rating', 'stars', { n: i + 1 }), 'data-on': String(i < (p.modelValue || 0)), onClick: () => p.onChange && p.onChange(i + 1) }, icon(h, 'star', ({ xs: 14, sm: 18, md: 20, lg: 26, xl: 32 })[p.size] || p.size || 20)))) },
|
|
94
95
|
ChipInput: { state: { q: '' }, render: ({ h, props: p, state, set }) => { const v = p.modelValue || [];
|
|
95
96
|
const add = t => { t = t.trim(); if (t && !v.includes(t) && (!p.maxItems || v.length < p.maxItems)) p.onChange && p.onChange([...v, t]); set({ q: '' }); };
|
|
96
|
-
return lab(h, p, h('span', { class: 'zg-input zg-chipin' }, ...v.map(
|
|
97
|
-
InlineEdit: { state: { edit: false, d: '' }, render: ({ C, h, props: p, state, set }) => state.edit ? h('span', { class: 'zg-input', style: { '--h': 'var(--zg-h-md)' } }, h('input', { value: state.d, autoFocus: true, onInput: e => set({ d: e.target.value }), onKeyDown: e => { if (e.key === 'Enter') { p.onChange && p.onChange(state.d); set({ edit: false }); } if (e.key === 'Escape') set({ edit: false }); } }), h(C.IconButton, { icon: 'check', label: '
|
|
97
|
+
return lab(h, p, h('span', { class: 'zg-input zg-chipin' }, ...v.map(tg => h('span', { key: tg, class: 'zg-tag' }, tg, h('button', { type: 'button', 'aria-label': t('ChipInput', 'remove', { name: tg }), onClick: () => p.onChange && p.onChange(v.filter(x => x !== tg)) }, icon(h, 'x', 12)))), h('input', { value: state.q, placeholder: v.length ? '' : p.placeholder, onInput: e => set({ q: e.target.value }), onKeyDown: e => { if (e.key === 'Enter' || e.key === ',') { e.preventDefault(); add(state.q); } else if (e.key === 'Backspace' && !state.q && v.length) p.onChange && p.onChange(v.slice(0, -1)); } }))); } },
|
|
98
|
+
InlineEdit: { state: { edit: false, d: '' }, render: ({ C, h, props: p, state, set }) => state.edit ? h('span', { class: 'zg-input', style: { '--h': 'var(--zg-h-md)' } }, h('input', { value: state.d, autoFocus: true, onInput: e => set({ d: e.target.value }), onKeyDown: e => { if (e.key === 'Enter') { p.onChange && p.onChange(state.d); set({ edit: false }); } if (e.key === 'Escape') set({ edit: false }); } }), h(C.IconButton, { icon: 'check', label: t('InlineEdit', 'save'), size: 'sm', onClick: () => { p.onChange && p.onChange(state.d); set({ edit: false }); } })) : h('button', { type: 'button', class: 'zg-inline', onClick: () => set({ edit: true, d: p.modelValue || '' }) }, p.modelValue || h('span', { class: 'zg-hint' }, p.emptyText || t('InlineEdit', 'edit')), icon(h, 'pencil', 14)) },
|
|
98
99
|
ColorPicker: { render: ({ h, props: p }) => h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-chips' }, ...(p.presets || ['#BC926A', '#5FAACC', '#3FD13F', '#E26E5F', '#E8B964', '#A98BD6', '#1B1919']).map(c => h('button', { key: c, type: 'button', 'aria-label': c, 'aria-pressed': String(c === p.modelValue), class: 'zg-swatch', style: { background: c }, onClick: () => p.onChange && p.onChange(c) })))) },
|
|
99
100
|
FileUpload: { state: { drag: false }, render: ({ h, props: p, state, set }) => { const ac = String(p.accept || '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean), okType = f => !ac.length || ac.some(a => a[0] === '.' ? f.name.toLowerCase().endsWith(a) : a.endsWith('/*') ? (f.type || '').toLowerCase().startsWith(a.slice(0, -1)) : (f.type || '').toLowerCase() === a), rej = (f, r) => p.onReject && p.onReject(f, r),
|
|
100
101
|
take = list => { const ok = []; for (const f of list) { if (!okType(f)) rej(f, 'type'); else if (p.maxSize && f.size > p.maxSize) rej(f, 'size'); else ok.push(f); } if (!p.multiple) ok.slice(1).forEach(f => rej(f, 'count')); const out = p.multiple ? ok : ok.slice(0, 1); if (out.length && p.onChange) p.onChange(out); }, pick = e => { const i = e.currentTarget.querySelector('input'); if (i) i.click(); };
|
|
101
|
-
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 || '
|
|
102
|
+
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); } },
|
|
102
103
|
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())));
|
|
103
|
-
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 || '
|
|
104
|
+
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)]),
|
|
104
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)); } },
|
|
105
106
|
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]);
|
|
106
|
-
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 ?
|
|
107
|
+
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)),
|
|
107
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)); } },
|
|
108
109
|
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);
|
|
109
110
|
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)),
|
|
110
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)); } },
|
|
111
112
|
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 || {};
|
|
112
113
|
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); } },
|
|
113
|
-
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 = p
|
|
114
|
+
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();
|
|
114
115
|
const wd = Array.from({ length: 7 }, (_, i) => new Intl.DateTimeFormat(loc, { weekday: 'narrow' }).format(new Date(2023, 0, 1 + ((i + ws) % 7))));
|
|
115
116
|
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))); };
|
|
116
|
-
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': '
|
|
117
|
+
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))),
|
|
117
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); }))); } },
|
|
118
|
-
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(p
|
|
119
|
+
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))),
|
|
119
120
|
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)) },
|
|
120
121
|
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 }));
|
|
121
122
|
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } }, h('div', { style: { flex: 1 } }, h(C.Select, { options: hs, disabled: p.disabled, modelValue: H, onChange: v => p.onChange && p.onChange(v + ':' + M) })), h('b', null, ':'), h('div', { style: { flex: 1 } }, h(C.Select, { options: ms, disabled: p.disabled, modelValue: M, onChange: v => p.onChange && p.onChange(H + ':' + v) })))); } },
|
|
122
123
|
FormField: { render: ({ h, props: p, children }) => h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label, p.required ? h('span', { style: { color: 'var(--zg-red)' } }, ' *') : null) : null, ...children, p.error ? h('span', { class: 'zg-hint zg-hint--error' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null) },
|
|
123
|
-
LoginForm: { state: { e: '', pw: '', rm: true }, render: ({ C, h, props: p, state, set }) => { const L = { email: '
|
|
124
|
+
LoginForm: { state: { e: '', pw: '', rm: true }, render: ({ C, h, props: p, state, set }) => { const L = { email: t('LoginForm', 'email'), password: t('LoginForm', 'password'), remember: t('LoginForm', 'remember'), forgot: t('LoginForm', 'forgot'), submit: t('LoginForm', 'submit'), ...(p.labels || {}) }; return h('form', { class: 'zg-field', style: { gap: '14px' }, onSubmit: ev => { ev.preventDefault(); p.onSubmit && p.onSubmit({ email: state.e, password: state.pw, remember: state.rm }); } },
|
|
124
125
|
p.error ? h(C.Alert, { tone: 'danger' }, p.error) : null, h(C.TextField, { label: L.email, icon: 'user-round', modelValue: state.e, onChange: v => set({ e: v }) }), h(C.PasswordInput, { label: L.password, modelValue: state.pw, onChange: v => set({ pw: v }) }),
|
|
125
126
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } }, h(C.Checkbox, { label: L.remember, modelValue: state.rm, onChange: v => set({ rm: v }) }), p.showForgot !== false ? h('a', { href: '#', onClick: e => { e.preventDefault(); p.onForgot && p.onForgot(); }, style: { color: 'var(--zg-brand-text)', fontWeight: 600, fontSize: '14px' } }, L.forgot) : null),
|
|
126
127
|
h('button', { type: 'submit', class: 'zg-btn zg-btn--primary zg-btn--lg zg-btn--block', disabled: p.loading }, p.loading ? h('span', { class: 'zg-spin' }) : null, p.submitLabel || L.submit)); } },
|
|
127
128
|
StepForm: { state: { i: 0 }, render: ({ C, h, props: p, state, set }) => { const st = p.steps || [], i = clamp(state.i, 0, st.length - 1);
|
|
128
|
-
return h('div', { class: 'zg-field', style: { gap: '18px' } }, h(C.Stepper, { steps: st.map(s => s.label), current: i }), h('div', null, st[i] && st[i].content), h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' } }, h(C.Button, { size: p.size, variant: 'ghost', disabled: i === 0, onClick: () => set({ i: i - 1 }) }, '
|
|
129
|
+
return h('div', { class: 'zg-field', style: { gap: '18px' } }, h(C.Stepper, { steps: st.map(s => s.label), current: i }), h('div', null, st[i] && st[i].content), h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' } }, h(C.Button, { size: p.size, variant: 'ghost', disabled: i === 0, onClick: () => set({ i: i - 1 }) }, t('StepForm', 'back')), h(C.Button, { size: p.size, variant: 'primary', onClick: () => i === st.length - 1 ? p.onFinish && p.onFinish() : set({ i: i + 1 }) }, i === st.length - 1 ? (p.finishLabel || t('StepForm', 'finish')) : t('StepForm', 'next')))); } },
|
|
129
130
|
/* ── Data ── */
|
|
130
|
-
Tag: { render: ({ h, props: p, children }) => h('span', { class: 'zg-tag' }, ...children, (p.onRemove || p.onClose) ? h('button', { type: 'button', 'aria-label': '
|
|
131
|
+
Tag: { render: ({ h, props: p, children }) => h('span', { class: 'zg-tag' }, ...children, (p.onRemove || p.onClose) ? h('button', { type: 'button', 'aria-label': t('Tag', 'remove'), onClick: p.onRemove || p.onClose }, icon(h, 'x', 12)) : null) },
|
|
131
132
|
Kbd: { render: ({ h, children }) => h('kbd', { class: 'zg-kbd' }, ...children) },
|
|
132
133
|
StatsCard: { render: ({ h, props: p }) => h('div', { class: 'zg-kpi' }, h('div', { style: { display: 'flex', alignItems: 'center', gap: '10px' } }, p.icon ? h('span', { class: 'zg-toast__icon' }, icon(h, p.icon, 18)) : null, h('span', { class: 'zg-kpi__label' }, p.label)), h('span', { class: 'zg-kpi__value zg-num' }, p.value), p.delta ? h('span', { class: cx('zg-delta', p.trend === 'down' ? 'zg-delta--down' : 'zg-delta--up') }, (p.trend === 'down' ? '▼ ' : '▲ ') + p.delta) : null) },
|
|
133
134
|
KpiInline: { render: ({ h, props: p }) => h('span', { class: 'zg-kpii' }, h('span', { class: 'zg-hint' }, p.label), h('b', { class: 'zg-num zg-kpii__v', style: { fontSize: '20px', fontWeight: 300 } }, p.value), p.delta ? h('span', { class: cx('zg-delta', String(p.delta).startsWith('-') ? 'zg-delta--down' : 'zg-delta--up') }, p.delta) : null) },
|
|
@@ -148,19 +149,19 @@ export const more = {
|
|
|
148
149
|
SettingsSection: { render: ({ h, props: p, children }) => h('section', { class: 'zg-setsec' }, h('div', null, h('b', { style: { fontSize: '16px' } }, p.title), p.description ? h('p', { class: 'zg-hint', style: { margin: '4px 0 0', fontSize: '13px' } }, p.description) : null), h('div', { class: 'zg-field', style: { gap: '14px' } }, ...children)) },
|
|
149
150
|
ScrollArea: { render: ({ h, props: p, children }) => h('div', { class: 'zg-scroll', style: { maxHeight: p.maxHeight || '240px' } }, ...children) },
|
|
150
151
|
/* ── CRM ── */
|
|
151
|
-
DealCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-lead zg-dm' }, h('div', { class: 'zg-lead__row' }, h('b', { style: { fontSize: dzf(14), flex: 1 } }, p.title), p.stage ? h(C.Badge, { tone: p.tone || 'info' }, p.stage) : null), h('div', { class: 'zg-num', style: { fontSize: dzf(26), fontWeight: 300 } }, p.amount), h(C.Progress, { value: p.probability ?? 0, label: '
|
|
152
|
+
DealCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-lead zg-dm' }, h('div', { class: 'zg-lead__row' }, h('b', { style: { fontSize: dzf(14), flex: 1 } }, p.title), p.stage ? h(C.Badge, { tone: p.tone || 'info' }, p.stage) : null), h('div', { class: 'zg-num', style: { fontSize: dzf(26), fontWeight: 300 } }, p.amount), h(C.Progress, { value: p.probability ?? 0, label: t('DealCard', 'probability'), meta: (p.probability ?? 0) + '%' }), h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.owner, size: dzp(p, 24) }), h('span', { class: 'zg-hint' }, p.owner), h('span', { class: 'zg-hint', style: { marginInlineStart: 'auto' } }, p.due))) },
|
|
152
153
|
PipelineBoard: { state: { over: null }, render: ({ C, h, props: p, state, set }) => { const mv = p.onMove, stages = arr(p.stages).filter(Boolean);
|
|
153
154
|
const drop = (e, to) => { e.preventDefault(); const d = dragging; dragging = null; if (state.over !== null) set({ over: null }); if (d && d.from !== to) mv({ id: d.id, from: d.from, to }); };
|
|
154
155
|
return h('div', { style: { display: 'flex', gap: '14px', overflowX: 'auto', paddingBottom: '4px' } }, ...stages.map((s, si) => { const cards = arr(s.cards).filter(Boolean), sid = s.id ?? si;
|
|
155
156
|
const col = h(C.KanbanColumn, { key: sid, title: s.title, count: cards.length, size: p.size }, ...cards.map((c, i) => { const deal = h(C.DealCard, { key: c.id ?? i, size: p.size, ...c });
|
|
156
157
|
return mv ? h('div', { key: c.id ?? i, class: 'zg-drag', draggable: 'true', onDragStart: e => { dragging = { id: c.id ?? i, from: sid }; if (e.dataTransfer) { e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', String(c.id ?? i)); } }, onDragEnd: () => { dragging = null; if (state.over !== null) set({ over: null }); } }, deal) : deal; }));
|
|
157
158
|
return mv ? h('div', { key: sid, class: 'zg-drop', 'data-over': String(state.over === sid && !!dragging && dragging.from !== sid), onDragOver: e => { if (!dragging) return; e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = 'move'; if (state.over !== sid) set({ over: sid }); }, onDragLeave: e => { if (!e.currentTarget.contains(e.relatedTarget) && state.over === sid) set({ over: null }); }, onDrop: e => drop(e, sid) }, col) : col; })); } },
|
|
158
|
-
ContactPanel: { 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, 56) }), h('div', null, h('b', { style: { fontSize: dzf(18) } }, p.name), h('div', { class: 'zg-hint' }, p.role))), h(C.Descriptions, { items: [{ label: '
|
|
159
|
+
ContactPanel: { 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, 56) }), h('div', null, h('b', { style: { fontSize: dzf(18) } }, p.name), h('div', { class: 'zg-hint' }, p.role))), h(C.Descriptions, { items: [{ label: t('ContactPanel', 'phone'), value: p.phone }, { label: t('ContactPanel', 'telegram'), value: p.telegram }, { label: t('ContactPanel', 'email'), value: p.email }, { label: t('ContactPanel', 'city'), value: p.city }] }), h('div', { style: { display: 'flex', gap: '8px' } }, h(C.Button, { variant: 'primary', size: 'sm', icon: 'phone', onClick: () => p.onCall && p.onCall() }, (p.labels && p.labels.call) || t('ContactPanel', 'call')), h(C.Button, { size: 'sm', icon: 'send', onClick: () => p.onMessage && p.onMessage() }, (p.labels && p.labels.message) || t('ContactPanel', 'message')))) },
|
|
159
160
|
SalesFunnel: { render: ({ h, props: p }) => { const st = arr(p.steps).map(s => s || {}), mx = Math.max(...st.map(s => num(s.value)), 1);
|
|
160
161
|
return h('div', { class: 'zg-field', style: { gap: '8px' } }, ...st.map((s, i) => h('div', { key: i, style: { display: 'grid', gridTemplateColumns: '120px minmax(0,1fr) 56px', gap: '12px', alignItems: 'center', fontSize: '14px' } }, h('span', null, s.label), h('span', { style: { display: 'flex', height: '32px', borderRadius: '12px', background: 'var(--zg-s2)' } }, h('span', { style: { width: (Math.max(0, num(s.value)) / mx * 100) + '%', borderRadius: '12px', background: i === st.length - 1 ? 'var(--zg-brand)' : 'color-mix(in oklch,var(--zg-sky) 40%,var(--zg-s2))' } })), h('b', { class: 'zg-mono', style: { textAlign: 'end' } }, fmt.num(s.value))))); } },
|
|
161
162
|
/* ── Hotels ── */
|
|
162
163
|
BookingCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-lead zg-dm' }, h('div', { class: 'zg-lead__row' }, h('b', { class: 'zg-mono', style: { fontSize: dzf(14), flex: 1 } }, p.code), h(C.Badge, { tone: { Confirmed: 'success', Pending: 'warning', Cancelled: 'danger' }[p.status], dot: true }, p.status)), h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.guest, size: dzp(p, 32) }), h('div', null, h('b', { style: { fontSize: dzf(14) } }, p.guest), h('div', { class: 'zg-hint' }, p.hotel))), h('div', { class: 'zg-lead__row zg-hint' }, icon(h, 'calendar', dzp(p, 14)), p.dates, h('span', { class: 'zg-num', style: { marginInlineStart: 'auto', fontSize: dzf(16), fontWeight: 600, color: 'var(--zg-text-1)' } }, fmt.sar(p.amount)))) },
|
|
163
|
-
GuestCard: { 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, 48) }), h('div', { style: { flex: 1 } }, h('b', { style: { fontSize: dzf(16) } }, p.name), h('div', { class: 'zg-hint' }, p.country, ' · ',
|
|
164
|
+
GuestCard: { 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, 48) }), h('div', { style: { flex: 1 } }, h('b', { style: { fontSize: dzf(16) } }, p.name), h('div', { class: 'zg-hint' }, p.country, ' · ', t('GuestCard', 'nights', { n: p.nights }))), p.vip ? h(C.Badge, { tone: 'brand' }, 'VIP') : null), h('div', { class: 'zg-chips' }, ...(p.tags || []).map(t => h('span', { key: t, class: 'zg-tag' }, t)))) },
|
|
164
165
|
RoomGrid: { render: ({ h, props: p }) => { const T = { ...ROOM_TONES, ...(p.statusTones || {}) }; return h('div', { class: 'zg-rooms zg-dm' }, ...arr(p.rooms).filter(Boolean).map((r, i) => { const tone = TONE_OK.includes(T[r.status]) ? T[r.status] : undefined; return h('button', { key: r.no ?? i, type: 'button', class: cx('zg-room', tone && 'zg-t-' + tone), 'data-s': r.status, 'data-tone': tone, onClick: () => p.onSelect && p.onSelect(r.no) }, h('b', { class: 'zg-mono' }, r.no), h('span', null, r.type)); })); } },
|
|
165
166
|
RoomCalendar: { render: ({ h, props: p }) => { const D = p.days || 14; return h('div', { class: 'zg-rc zg-dm', style: { '--d': D } }, h('span'), ...Array.from({ length: D }, (_, i) => h('span', { key: 'd' + i, class: 'zg-rc__h' }, i + 1)), ...arr(p.rooms).filter(Boolean).flatMap(r => [h('b', { key: r.no, class: 'zg-mono' }, r.no), ...Array.from({ length: D }, (_, i) => { const b = arr(r.bookings).find(x => i >= x.from && i < x.from + x.nights); return h('i', { key: r.no + '_' + i, class: 'zg-rc__c', 'data-b': b ? (i === b.from ? 'start' : 'mid') : undefined, title: b ? b.guest : undefined }); })])); } },
|
|
166
167
|
OccupancyChart: { render: ({ h, props: p }) => h('div', { class: 'zg-occ' }, ...arr(p.values).map((v, i) => { v = clamp(num(v), 0, 100); return h('div', { key: i, class: 'zg-occ__c' }, h('span', { class: 'zg-hint zg-mono' }, v + '%'), h('i', { style: { height: v + '%', background: v > 90 ? 'var(--zg-brand)' : 'color-mix(in oklch,var(--zg-sky) 55%,var(--zg-s3))' } }), h('span', { class: 'zg-hint' }, arr(p.labels)[i] || '')); })) },
|