@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.
Files changed (82) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +23 -2
  3. package/dist/llms-full.txt +2 -2
  4. package/dist/llms.txt +2 -2
  5. package/dist/manifest.json +1 -1
  6. package/package.json +2 -2
  7. package/src/core/components.js +15 -14
  8. package/src/core/components2.js +37 -36
  9. package/src/core/components3.js +8 -7
  10. package/src/core/components4.js +23 -22
  11. package/src/core/components6.js +8 -7
  12. package/src/core/components7.js +10 -9
  13. package/src/core/index.js +1 -1
  14. package/src/i18n/components/ActionCard.js +5 -0
  15. package/src/i18n/components/Actionsheet.js +5 -0
  16. package/src/i18n/components/Alert.js +5 -0
  17. package/src/i18n/components/Breadcrumbs.js +5 -0
  18. package/src/i18n/components/Calendar.js +5 -0
  19. package/src/i18n/components/CallCard.js +5 -0
  20. package/src/i18n/components/CallRow.js +5 -0
  21. package/src/i18n/components/Carousel.js +5 -0
  22. package/src/i18n/components/ChartCard.js +5 -0
  23. package/src/i18n/components/ChatAi.js +5 -0
  24. package/src/i18n/components/Chip.js +5 -0
  25. package/src/i18n/components/ChipInput.js +5 -0
  26. package/src/i18n/components/CodeInput.js +5 -0
  27. package/src/i18n/components/Combobox.js +5 -0
  28. package/src/i18n/components/CommandPalette.js +5 -0
  29. package/src/i18n/components/ConfirmDialog.js +5 -0
  30. package/src/i18n/components/ContactPanel.js +5 -0
  31. package/src/i18n/components/CopyButton.js +2 -0
  32. package/src/i18n/components/DataGroup.js +5 -0
  33. package/src/i18n/components/DataState.js +5 -0
  34. package/src/i18n/components/DataTable.js +5 -0
  35. package/src/i18n/components/DatePicker.js +5 -0
  36. package/src/i18n/components/DealCard.js +5 -0
  37. package/src/i18n/components/Drawer.js +5 -0
  38. package/src/i18n/components/ErrorPage.js +5 -0
  39. package/src/i18n/components/FileUpload.js +5 -0
  40. package/src/i18n/components/FullscreenDialog.js +5 -0
  41. package/src/i18n/components/Gallery.js +5 -0
  42. package/src/i18n/components/GuestCard.js +5 -0
  43. package/src/i18n/components/ImageUpload.js +5 -0
  44. package/src/i18n/components/ImageViewer.js +5 -0
  45. package/src/i18n/components/IncomingCall.js +5 -0
  46. package/src/i18n/components/InlineEdit.js +5 -0
  47. package/src/i18n/components/IntegrationCard.js +5 -0
  48. package/src/i18n/components/LoginForm.js +5 -0
  49. package/src/i18n/components/Map.js +5 -0
  50. package/src/i18n/components/Modal.js +5 -0
  51. package/src/i18n/components/MultiSelect.js +5 -0
  52. package/src/i18n/components/NotificationBell.js +5 -0
  53. package/src/i18n/components/NumberInput.js +5 -0
  54. package/src/i18n/components/Pagination.js +5 -0
  55. package/src/i18n/components/PasswordInput.js +5 -0
  56. package/src/i18n/components/Popconfirm.js +5 -0
  57. package/src/i18n/components/PricingCard.js +5 -0
  58. package/src/i18n/components/RamadanTracker.js +5 -0
  59. package/src/i18n/components/RangeCalendar.js +5 -0
  60. package/src/i18n/components/RangeSlider.js +5 -0
  61. package/src/i18n/components/Rating.js +5 -0
  62. package/src/i18n/components/RichEditor.js +5 -0
  63. package/src/i18n/components/Screensaver.js +5 -0
  64. package/src/i18n/components/SearchInput.js +5 -0
  65. package/src/i18n/components/SideNav.js +5 -0
  66. package/src/i18n/components/Snackbar.js +5 -0
  67. package/src/i18n/components/Sortable.js +5 -0
  68. package/src/i18n/components/Spinner.js +5 -0
  69. package/src/i18n/components/StatusPage.js +5 -0
  70. package/src/i18n/components/StepDialog.js +5 -0
  71. package/src/i18n/components/StepForm.js +5 -0
  72. package/src/i18n/components/TabTiles.js +5 -0
  73. package/src/i18n/components/Tag.js +5 -0
  74. package/src/i18n/components/TopNav.js +5 -0
  75. package/src/i18n/components/Tour.js +5 -0
  76. package/src/i18n/components/TourCard.js +5 -0
  77. package/src/i18n/components/TourPopover.js +5 -0
  78. package/src/i18n/components/TreeSelect.js +5 -0
  79. package/src/i18n/components/Validation.js +5 -0
  80. package/src/i18n/components/index.js +130 -0
  81. package/src/i18n/index.js +8 -6
  82. package/types/i18n.d.ts +4 -2
@@ -1,11 +1,12 @@
1
1
  // @ziyarago/ui · components, part 3 — card patterns, tonal variants, shells for auth/profile/status pages.
2
2
  import { icon, cx, fmt, ddRef } from './components.js';
3
+ import { t } from '../i18n/index.js';
3
4
  const TONES = ['brand', 'sky', 'success', 'warning', 'danger', 'neutral'];
4
5
  const tc = t => 'zg-t-' + (TONES.includes(t) ? t : 'brand');
5
6
  const delta = (h, d) => d ? h('span', { class: cx('zg-delta', String(d).trim().startsWith('-') ? 'zg-delta--down' : 'zg-delta--up') }, (String(d).trim().startsWith('-') ? '▼ ' : '▲ ') + String(d).replace(/^[+-]/, '')) : null;
6
7
 
7
8
  export const more3 = {
8
- Chip: { render: ({ h, props: p, children }) => h('span', { class: cx('zg-chip2', tc(p.tone), 'zg-chip2--' + (p.variant || 'tonal'), p.size && 'zg-chip2--' + p.size, p.pill && 'zg-chip2--pill') }, p.icon ? icon(h, p.icon, 14) : null, ...children, (p.onClose || p.onRemove) ? h('button', { type: 'button', 'aria-label': 'Remove', onClick: p.onClose || p.onRemove }, icon(h, 'x', 12)) : null) },
9
+ Chip: { render: ({ h, props: p, children }) => h('span', { class: cx('zg-chip2', tc(p.tone), 'zg-chip2--' + (p.variant || 'tonal'), p.size && 'zg-chip2--' + p.size, p.pill && 'zg-chip2--pill') }, p.icon ? icon(h, p.icon, 14) : null, ...children, (p.onClose || p.onRemove) ? h('button', { type: 'button', 'aria-label': t('Chip', 'remove'), onClick: p.onClose || p.onRemove }, icon(h, 'x', 12)) : null) },
9
10
  IconTile: { render: ({ h, props: p }) => h('span', { class: cx('zg-tile', tc(p.tone), p.shape === 'circle' && 'zg-tile--circle'), style: { '--s': ({ xs: 28, sm: 32, md: 40, lg: 48, xl: 56 }[p.size] || p.size || 40) + 'px' } }, icon(h, p.icon || 'circle', Math.round(({ xs: 28, sm: 32, md: 40, lg: 48, xl: 56 }[p.size] || p.size || 40) * .5))) },
10
11
  StatusIcon: { render: ({ h, props: p }) => h('span', { class: cx('zg-tile zg-tile--circle zg-tip', tc(p.tone)), 'data-tip': p.tip, tabIndex: 0, style: { '--s': ({ xs: 24, sm: 30, md: 36, lg: 44, xl: 52 }[p.size] || p.size || 36) + 'px' } }, icon(h, p.icon || 'circle', Math.round(({ xs: 24, sm: 30, md: 36, lg: 44, xl: 52 }[p.size] || p.size || 36) * .44))) },
11
12
  UserCell: { render: ({ C, h, props: p }) => h('span', { class: 'zg-usercell' }, h(C.Avatar, { name: p.name, src: p.src, size: { xs: 24, sm: 30, md: 36, lg: 44, xl: 56 }[p.size] || p.size || 36 }), h('span', null, h('b', null, p.name), p.sub ? h('span', { class: 'zg-hint' }, p.sub) : null)) },
@@ -19,17 +20,17 @@ export const more3 = {
19
20
  SegBar: { render: ({ h, props: p }) => { const s = (Array.isArray(p.segments) ? p.segments : []).filter(Boolean).map(x => ({ ...x, value: Math.max(0, Number(x.value) || 0) })), t = s.reduce((a, x) => a + x.value, 0) || 1, col = ['var(--zg-brand)', 'var(--zg-sky)', 'var(--zg-green)', 'var(--zg-amber)', 'var(--zg-red)'];
20
21
  return h('div', { class: 'zg-field', style: { gap: '12px' } }, h('div', { class: 'zg-segbar' }, ...s.map((x, i) => h('i', { key: i, title: x.label, style: { width: (x.value / t * 100) + '%', background: x.color || col[i % 5] } }))), h('div', { class: 'zg-chips' }, ...s.map((x, i) => h('span', { key: i, class: 'zg-hint', style: { display: 'inline-flex', alignItems: 'center', gap: '6px' } }, h('i', { style: { width: '8px', height: '8px', borderRadius: '50%', background: x.color || col[i % 5] } }), x.label + ' · ' + x.value)))); } },
21
22
  HeroBanner: { render: ({ C, h, props: p, children }) => h('section', { class: 'zg-hero zg-glass' }, h('div', { class: 'zg-hero__t' }, h('b', { style: { fontSize: '20px', fontWeight: 500 } }, p.title), p.text ? h('span', { style: { color: 'var(--zg-text-2)', lineHeight: 1.5 } }, p.text) : null, p.value ? h('span', { class: 'zg-num', style: { fontSize: '36px', fontWeight: 300 } }, p.value) : null, ...children, p.cta ? h(C.Button, { variant: 'primary', size: 'sm', onClick: p.onCta }, p.cta) : null), p.image ? h('span', { class: 'zg-hero__img', style: { backgroundImage: `url(${p.image})` } }) : null) },
22
- TabTiles: { render: ({ h, props: p }) => h('div', { class: 'zg-tt' }, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', class: 'zg-tt__i', 'aria-pressed': String(it.value === p.value), onClick: () => p.onChange && p.onChange(it.value) }, icon(h, it.icon || 'circle', 20), h('span', null, it.label))), p.addable ? h('button', { type: 'button', class: 'zg-tt__i zg-tt__add', 'aria-label': 'Add', onClick: p.onAdd }, icon(h, 'plus', 20)) : null) },
23
+ TabTiles: { render: ({ h, props: p }) => h('div', { class: 'zg-tt' }, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', class: 'zg-tt__i', 'aria-pressed': String(it.value === p.value), onClick: () => p.onChange && p.onChange(it.value) }, icon(h, it.icon || 'circle', 20), h('span', null, it.label))), p.addable ? h('button', { type: 'button', class: 'zg-tt__i zg-tt__add', 'aria-label': t('TabTiles', 'add'), onClick: p.onAdd }, icon(h, 'plus', 20)) : null) },
23
24
  PillTabs: { render: ({ h, props: p }) => h('div', { class: cx('zg-pt', p.vertical && 'zg-pt--v'), role: 'tablist' }, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', role: 'tab', 'aria-selected': String(it.value === p.value), onClick: () => p.onChange && p.onChange(it.value) }, it.icon ? icon(h, it.icon, 16) : null, it.label))) },
24
25
  PillSelect: { state: { open: false }, render: ({ h, props: p, state, set }) => { const opts = p.options || [], v = p.modelValue !== undefined ? p.modelValue : p.value, open = state.open && !p.disabled; return h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: 'zg-btn zg-btn--tonal zg-btn--sm', 'aria-haspopup': 'listbox', 'aria-expanded': String(open), disabled: p.disabled, onClick: () => set({ open: !state.open }) }, (opts.find(o => o.value === v) || {}).label || p.placeholder, icon(h, 'chevron-down', 14)), open ? h('div', { class: 'zg-menu', role: 'listbox', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...opts.map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-menu__i', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === v), onClick: () => { set({ open: false }); p.onChange && p.onChange(o.value); } }, o.label))) : null); } },
25
26
  ChartCard: { state: { open: false }, render: ({ h, props: p, state, set, children }) => h('section', { class: 'zg-card zg-glass' }, h('div', { class: 'zg-card__head' }, h('div', { style: { flex: 1, minWidth: 0 } }, h('h3', { class: 'zg-card__title' }, p.title), p.subtitle ? h('div', { class: 'zg-card__sub' }, p.subtitle) : null),
26
- p.menu ? h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': 'More', 'aria-haspopup': 'menu', onClick: () => set({ open: !state.open }) }, icon(h, 'ellipsis-vertical')), state.open ? h('div', { class: 'zg-menu', role: 'menu', style: { insetInlineStart: 'auto', insetInlineEnd: 0 } }, ...p.menu.map(m => h('button', { key: m, type: 'button', role: 'menuitem', class: cx('zg-menu__i', m === 'Delete' && 'zg-menu__i--danger'), onClick: () => { set({ open: false }); p.onMenu && p.onMenu(m); } }, m))) : null) : null), ...children) },
27
- ActionCard: { state: { collapsed: false, loading: false, removed: false }, render: ({ C, h, props: p, state, set, children }) => { const col = p.collapsed !== undefined ? !!p.collapsed && p.collapsed !== 'false' : state.collapsed, busy = p.loading !== undefined ? !!p.loading && p.loading !== 'false' : state.loading; return state.removed ? null : h('section', { class: 'zg-card zg-glass zg-lo' }, h('div', { class: 'zg-card__head' }, h('div', { style: { flex: 1 } }, h('h3', { class: 'zg-card__title' }, p.title), p.subtitle ? h('div', { class: 'zg-card__sub' }, p.subtitle) : null), h('div', { style: { display: 'flex', gap: '2px' } }, h(C.IconButton, { icon: col ? 'chevron-down' : 'chevron-up', label: col ? 'Expand' : 'Collapse', size: 'sm', ghost: true, onClick: () => { set({ collapsed: !col }); p.onCollapse && p.onCollapse(!col); } }), h(C.IconButton, { icon: 'refresh-cw', label: 'Refresh', size: 'sm', ghost: true, onClick: () => { if (p.loading === undefined) { set({ loading: true }); setTimeout(() => set({ loading: false }), 1800); } p.onRefresh && p.onRefresh(); } }), h(C.IconButton, { icon: 'x', label: 'Remove', size: 'sm', ghost: true, onClick: () => { set({ removed: true }); p.onRemove && p.onRemove(); } }))),
27
+ p.menu ? h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('ChartCard', 'more'), 'aria-haspopup': 'menu', onClick: () => set({ open: !state.open }) }, icon(h, 'ellipsis-vertical')), state.open ? h('div', { class: 'zg-menu', role: 'menu', style: { insetInlineStart: 'auto', insetInlineEnd: 0 } }, ...p.menu.map(m => h('button', { key: m, type: 'button', role: 'menuitem', class: cx('zg-menu__i', m === 'Delete' && 'zg-menu__i--danger'), onClick: () => { set({ open: false }); p.onMenu && p.onMenu(m); } }, m))) : null) : null), ...children) },
28
+ ActionCard: { state: { collapsed: false, loading: false, removed: false }, render: ({ C, h, props: p, state, set, children }) => { const col = p.collapsed !== undefined ? !!p.collapsed && p.collapsed !== 'false' : state.collapsed, busy = p.loading !== undefined ? !!p.loading && p.loading !== 'false' : state.loading; return state.removed ? null : h('section', { class: 'zg-card zg-glass zg-lo' }, h('div', { class: 'zg-card__head' }, h('div', { style: { flex: 1 } }, h('h3', { class: 'zg-card__title' }, p.title), p.subtitle ? h('div', { class: 'zg-card__sub' }, p.subtitle) : null), h('div', { style: { display: 'flex', gap: '2px' } }, h(C.IconButton, { icon: col ? 'chevron-down' : 'chevron-up', label: col ? t('ActionCard', 'expand') : t('ActionCard', 'collapse'), size: 'sm', ghost: true, onClick: () => { set({ collapsed: !col }); p.onCollapse && p.onCollapse(!col); } }), h(C.IconButton, { icon: 'refresh-cw', label: t('ActionCard', 'refresh'), size: 'sm', ghost: true, onClick: () => { if (p.loading === undefined) { set({ loading: true }); setTimeout(() => set({ loading: false }), 1800); } p.onRefresh && p.onRefresh(); } }), h(C.IconButton, { icon: 'x', label: t('ActionCard', 'remove'), size: 'sm', ghost: true, onClick: () => { set({ removed: true }); p.onRemove && p.onRemove(); } }))),
28
29
  col ? null : h('div', null, ...children), busy ? h('div', { class: 'zg-lo__o' }, h(C.Spinner, { size: 28 })) : null); } },
29
- Snackbar: { render: ({ h, props: p, children }) => h('div', { class: cx('zg-snack', p.variant === 'tonal' && 'zg-snack--tonal', tc(p.tone)), role: 'status' }, h('span', { style: { flex: 1 } }, ...children), p.action ? h('button', { type: 'button', onClick: p.onAction, style: { color: p.tone === 'success' ? 'var(--zg-green)' : 'var(--zg-red)' } }, p.action) : null, p.onClose ? h('button', { type: 'button', onClick: p.onClose, 'aria-label': 'Close' }, icon(h, 'x', 14)) : null) },
30
- TourPopover: { render: ({ C, h, props: p }) => h('div', { class: 'zg-tour' }, h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, h('b', { style: { flex: 1, fontSize: '16px' } }, p.title), h('button', { type: 'button', 'aria-label': 'Close', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', onClick: p.onClose }, icon(h, 'x', 14))), h('span', { style: { fontSize: '14px', lineHeight: 1.5 } }, p.text), h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } }, h('span', { class: 'zg-hint zg-mono' }, p.step || ''), h('div', { style: { display: 'flex', gap: '8px' } }, p.onBack ? h(C.Button, { size: 'sm', onClick: p.onBack }, 'Back') : null, h(C.Button, { size: 'sm', variant: 'primary', onClick: p.onNext }, p.last ? 'Finish' : 'Next')))) },
30
+ Snackbar: { render: ({ h, props: p, children }) => h('div', { class: cx('zg-snack', p.variant === 'tonal' && 'zg-snack--tonal', tc(p.tone)), role: 'status' }, h('span', { style: { flex: 1 } }, ...children), p.action ? h('button', { type: 'button', onClick: p.onAction, style: { color: p.tone === 'success' ? 'var(--zg-green)' : 'var(--zg-red)' } }, p.action) : null, p.onClose ? h('button', { type: 'button', onClick: p.onClose, 'aria-label': t('Snackbar', 'close') }, icon(h, 'x', 14)) : null) },
31
+ TourPopover: { render: ({ C, h, props: p }) => h('div', { class: 'zg-tour' }, h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, h('b', { style: { flex: 1, fontSize: '16px' } }, p.title), h('button', { type: 'button', 'aria-label': t('TourPopover', 'close'), class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', onClick: p.onClose }, icon(h, 'x', 14))), h('span', { style: { fontSize: '14px', lineHeight: 1.5 } }, p.text), h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } }, h('span', { class: 'zg-hint zg-mono' }, p.step || ''), h('div', { style: { display: 'flex', gap: '8px' } }, p.onBack ? h(C.Button, { size: 'sm', onClick: p.onBack }, t('TourPopover', 'back')) : null, h(C.Button, { size: 'sm', variant: 'primary', onClick: p.onNext }, p.last ? t('TourPopover', 'finish') : t('TourPopover', 'next'))))) },
31
32
  ProfileHeader: { render: ({ C, h, props: p, children }) => h('section', { class: 'zg-card zg-glass zg-prof' }, h('div', { class: 'zg-prof__cover', style: { backgroundImage: p.cover ? `url(${p.cover})` : undefined } }), h('div', { class: 'zg-prof__row' }, h('span', { class: 'zg-prof__av' }, h(C.Avatar, { name: p.name, src: p.src, size: 104 })), h('div', { style: { flex: 1, minWidth: '200px', display: 'flex', flexDirection: 'column', gap: '8px' } }, h('b', { style: { fontSize: '22px', fontWeight: 500 } }, p.name), h('div', { class: 'zg-chips', style: { color: 'var(--zg-text-3)', fontSize: '13px' } }, ...(p.meta || []).map(m => h('span', { key: m.label, style: { display: 'inline-flex', gap: '6px', alignItems: 'center' } }, icon(h, m.icon, 14), m.label)))), ...children)) },
32
33
  AuthSplit: { render: ({ h, props: p, children }) => h('div', { class: 'zg-root zg-split2' }, h('aside', { class: 'zg-split2__a' }, p.aside), h('main', { class: 'zg-split2__m' }, h('div', { style: { width: '100%', maxWidth: '420px', display: 'flex', flexDirection: 'column', gap: '20px' } }, p.logo || null, h('div', null, h('h1', { style: { margin: 0, font: '300 28px var(--zg-font)', letterSpacing: '-.03em' } }, p.title), p.subtitle ? h('p', { style: { margin: '6px 0 0', color: 'var(--zg-text-2)', lineHeight: 1.5 } }, p.subtitle) : null), ...children))) },
33
- StatusPage: { state: { email: '' }, render: ({ C, h, props: p, children, state, set }) => { const K = { 404: ['404', 'Page not found', 'The page may have moved. Check the address or go back.', 'Back to home'], 401: ['401', 'Not authorized', 'You need permission to open this page.', 'Back to home'], maintenance: ['', 'Under maintenance', 'We are updating the service. It will be back shortly.', 'Back to home'], soon: ['', 'Coming soon', 'We are working on it. Leave your email and we will tell you first.', 'Notify me'] }[p.kind || '404'];
34
+ StatusPage: { state: { email: '' }, render: ({ C, h, props: p, children, state, set }) => { const K = { 404: ['404', t('StatusPage', 'nfTitle'), t('StatusPage', 'nfDesc'), t('StatusPage', 'home')], 401: ['401', t('StatusPage', 'authTitle'), t('StatusPage', 'authDesc'), t('StatusPage', 'home')], maintenance: ['', t('StatusPage', 'mtTitle'), t('StatusPage', 'mtDesc'), t('StatusPage', 'home')], soon: ['', t('StatusPage', 'soonTitle'), t('StatusPage', 'soonDesc'), t('StatusPage', 'notify')] }[p.kind || '404'];
34
35
  return h('div', { class: 'zg-es zg-es--page' }, p.codeNode || (K[0] ? h('span', { class: 'zg-num', style: { fontSize: 'clamp(80px,14vw,150px)', lineHeight: 1, color: 'var(--zg-brand-text)' } }, K[0]) : h('span', { class: 'zg-es__ico' }, icon(h, p.kind === 'soon' ? 'rocket' : 'wrench', 28))), p.messageNode || [h('b', { key: 't', style: { fontSize: '24px' } }, p.title || K[1]), h('span', { key: 'd' }, p.description || K[2])], p.actionNode || (p.kind === 'soon' ? h('div', { style: { display: 'flex', gap: '8px', flexWrap: 'wrap', justifyContent: 'center', width: '100%', maxWidth: '420px' } }, h('div', { style: { flex: '1 1 200px' } }, h(C.TextField, { placeholder: p.emailLabel || 'name@email.com', icon: 'mail', modelValue: state.email, onChange: v => set({ email: v }) })), h(C.Button, { variant: 'primary', onClick: () => p.onNotify && p.onNotify(state.email) }, p.notifyLabel || K[3])) : h(C.Button, { variant: 'primary', onClick: p.onAction }, p.actionLabel || K[3])), ...children); } }
35
36
  };
@@ -1,4 +1,5 @@
1
1
  // @ziyarago/ui · components, part 4 — gaps closed after the Vuexy audit.
2
+ import { t } from '../i18n/index.js';
2
3
  import { icon, cx, fmt } from './components.js';
3
4
  import { trap } from './dialog.js';
4
5
  const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -11,18 +12,18 @@ const sanitizeHtml = html => { if (typeof document === 'undefined') return html;
11
12
  walk(t.content); return t.innerHTML; };
12
13
 
13
14
  export const rules = {
14
- required: () => v => (v == null || String(v).trim() === '') ? 'This field is required' : true,
15
- email: () => v => !v || /^\S+@\S+\.\S+$/.test(v) || 'Enter a valid email',
16
- url: () => v => !v || /^https?:\/\/\S+\.\S+/.test(v) || 'Enter a valid URL',
17
- digits: n => v => !v || new RegExp(`^\\d{${n}}$`).test(v) || `Exactly ${n} digits`,
18
- alpha: () => v => !v || /^[\p{L}\s]+$/u.test(v) || 'Letters only',
19
- alphaDash: () => v => !v || /^[\p{L}\d_-]+$/u.test(v) || 'Letters, digits, dash and underscore',
20
- minLength: n => v => !v || String(v).length >= n || `At least ${n} characters`,
21
- maxLength: n => v => !v || String(v).length <= n || `At most ${n} characters`,
22
- range: (a, b) => v => v == null || v === '' || (Number(v) >= a && Number(v) <= b) || `Between ${a} and ${b}`,
23
- regex: (re, msg) => v => !v || re.test(v) || msg || 'Invalid format',
24
- confirmed: get => v => v === get() || 'Values do not match',
25
- password: () => v => !v || (/\d/.test(v) && /[A-ZА-Я]/.test(v) && String(v).length >= 8) || '8+ characters, a digit and a capital letter'
15
+ required: () => v => (v == null || String(v).trim() === '') ? t('Validation', 'required') : true,
16
+ email: () => v => !v || /^\S+@\S+\.\S+$/.test(v) || t('Validation', 'email'),
17
+ url: () => v => !v || /^https?:\/\/\S+\.\S+/.test(v) || t('Validation', 'url'),
18
+ digits: n => v => !v || new RegExp(`^\\d{${n}}$`).test(v) || t('Validation', 'digits', { n }),
19
+ alpha: () => v => !v || /^[\p{L}\s]+$/u.test(v) || t('Validation', 'alpha'),
20
+ alphaDash: () => v => !v || /^[\p{L}\d_-]+$/u.test(v) || t('Validation', 'alphaDash'),
21
+ minLength: n => v => !v || String(v).length >= n || t('Validation', 'minLength', { n }),
22
+ maxLength: n => v => !v || String(v).length <= n || t('Validation', 'maxLength', { n }),
23
+ range: (a, b) => v => v == null || v === '' || (Number(v) >= a && Number(v) <= b) || t('Validation', 'range', { a, b }),
24
+ regex: (re, msg) => v => !v || re.test(v) || msg || t('Validation', 'invalid'),
25
+ confirmed: get => v => v === get() || t('Validation', 'confirmed'),
26
+ password: () => v => !v || (/\d/.test(v) && /[A-ZА-Я]/.test(v) && String(v).length >= 8) || t('Validation', 'password')
26
27
  };
27
28
 
28
29
  export const more4 = {
@@ -33,26 +34,26 @@ export const more4 = {
33
34
  return h(Comp, { ...p, modelValue: val, error: p.error || err, onChange: v => { set({ v, touched: true }); p.onChange && p.onChange(v); } }); } },
34
35
  Footer: { render: ({ h, props: p, children }) => h('footer', { class: 'zg-foot' }, h('span', { style: { color: 'var(--zg-text-3)', fontSize: '13px' } }, p.copyright || '© 2026 ZiyaraGo'), h('nav', { style: { display: 'flex', gap: '16px', flexWrap: 'wrap' } }, ...(p.links || []).map(l => h('a', { key: l.label, href: l.href || '#', style: { color: 'var(--zg-brand-text)', fontSize: '13px', fontWeight: 600 } }, l.label))), ...children) },
35
36
  DataGroup: { state: { open: {} , grp: {} }, render: ({ C, h, props: p, state, set }) => { const cols = p.columns || [];
36
- const row = r => { const o = !!state.open[r.id]; return [h('tr', { key: r.id, 'aria-selected': o ? 'true' : undefined }, p.expand ? h('td', { style: { width: '44px' } }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-expanded': String(o), 'aria-label': 'Expand', onClick: () => set({ open: { ...state.open, [r.id]: !o } }) }, icon(h, o ? 'chevron-down' : 'chevron-right', 16))) : null, ...cols.map(c => h('td', { key: c.key, class: c.num ? 'num' : undefined }, c.render ? c.render(r[c.key], r) : r[c.key]))), o && p.expand ? h('tr', { key: r.id + 'x' }, h('td', { colSpan: cols.length + 1, style: { background: 'var(--zg-s1)', padding: '16px 20px' } }, p.expand(r))) : null]; };
37
+ const row = r => { const o = !!state.open[r.id]; return [h('tr', { key: r.id, 'aria-selected': o ? 'true' : undefined }, p.expand ? h('td', { style: { width: '44px' } }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-expanded': String(o), 'aria-label': t('DataGroup', 'expand'), onClick: () => set({ open: { ...state.open, [r.id]: !o } }) }, icon(h, o ? 'chevron-down' : 'chevron-right', 16))) : null, ...cols.map(c => h('td', { key: c.key, class: c.num ? 'num' : undefined }, c.render ? c.render(r[c.key], r) : r[c.key]))), o && p.expand ? h('tr', { key: r.id + 'x' }, h('td', { colSpan: cols.length + 1, style: { background: 'var(--zg-s1)', padding: '16px 20px' } }, p.expand(r))) : null]; };
37
38
  return h('div', { class: 'zg-table-wrap' }, h('table', { class: 'zg-table' }, h('thead', null, h('tr', null, p.expand ? h('th') : null, ...cols.map(c => h('th', { key: c.key, class: c.num ? 'num' : undefined }, c.label)))),
38
39
  h('tbody', null, ...(p.groups || []).flatMap(g => { const c = !!state.grp[g.label]; return [h('tr', { key: 'g' + g.label }, h('td', { colSpan: cols.length + 1, style: { padding: 0 } }, h('button', { type: 'button', class: 'zg-dg__g', onClick: () => set({ grp: { ...state.grp, [g.label]: !c } }), style: { all: 'unset', boxSizing: 'border-box', display: 'flex', alignItems: 'center', gap: '8px', width: '100%', padding: '10px 16px', cursor: 'pointer', fontWeight: 700, fontSize: '13px', background: 'var(--zg-s1)' } }, icon(h, c ? 'chevron-right' : 'chevron-down', 14), g.label + ' (' + g.rows.length + ')'))), ...(c ? [] : g.rows.flatMap(row))]; })))); } },
39
40
  OptionCard: { render: ({ h, props: p }) => h('button', { type: 'button', role: p.multiple ? 'checkbox' : 'radio', 'aria-checked': String(!!p.checked), class: 'zg-opt', onClick: p.onClick }, p.image ? h('span', { class: 'zg-opt__img', style: { backgroundImage: `url(${p.image})` } }) : p.icon ? h('span', { class: 'zg-tile zg-t-brand', style: { '--s': '40px' } }, icon(h, p.icon, 20)) : null, h('span', { style: { display: 'flex', flexDirection: 'column', gap: '2px', flex: 1, textAlign: 'start' } }, h('b', { style: { fontSize: '15px' } }, p.title), p.desc ? h('span', { class: 'zg-hint' }, p.desc) : null), p.price ? h('b', { class: 'zg-num', style: { fontSize: '16px' } }, p.price) : null, h('span', { class: cx('zg-check__box', !p.multiple && 'zg-radio'), style: { background: p.checked ? 'var(--zg-brand)' : 'transparent' } }, p.checked ? (p.multiple ? icon(h, 'check', 12) : h('i')) : null)) },
40
41
  OptionCards: { render: ({ C, h, props: p }) => { const cur = p.multiple ? (p.modelValue || []) : p.modelValue;
41
42
  return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { style: { display: 'grid', gridTemplateColumns: `repeat(auto-fit,minmax(min(100%,${p.min || 220}px),1fr))`, gap: '10px' } }, ...(p.options || []).map(o => h(C.OptionCard, { ...o, multiple: p.multiple, checked: p.multiple ? cur.includes(o.value) : cur === o.value, onClick: () => p.onChange && p.onChange(p.multiple ? (cur.includes(o.value) ? cur.filter(x => x !== o.value) : [...cur, o.value]) : o.value) })))); } },
42
43
  RichEditor: { render: ({ h, props: p }) => { let ed; const san = x => p.sanitize ? sanitizeHtml(x) : x, emit = () => { if (!ed) return; const x = san(ed.innerHTML); ed.__zg = x; p.onChange && p.onChange(x); }; const cmd = (c, v) => () => { if (!ed) return; ed.focus(); try { document.execCommand(c, false, v); } catch (e) {} emit(); };
43
- const B = [['bold', 'bold', 'Bold'], ['italic', 'italic', 'Italic'], ['underline', 'underline', 'Underline'], ['strikeThrough', 'strikethrough', 'Strike'], null, ['justifyLeft', 'align-left', 'Left'], ['justifyCenter', 'align-center', 'Center'], ['justifyRight', 'align-right', 'Right'], null, ['insertUnorderedList', 'list', 'Bullets'], ['insertOrderedList', 'list-ordered', 'Numbers'], null, ['formatBlock', 'quote', 'Quote', 'blockquote'], ['formatBlock', 'code', 'Code block', 'pre']];
44
- const area = h('div', { class: 'zg-rte__c', contentEditable: 'true', role: 'textbox', 'aria-multiline': 'true', 'aria-label': p.label || 'Editor', 'data-ph': p.placeholder || 'Write something…', ref: el => { if (!el) return; ed = el; const want = p.modelValue ?? ''; if (el.__zg !== want && el.innerHTML !== want) el.innerHTML = san(want); el.__zg = want; }, onFocus: e => { ed = e.currentTarget; }, onInput: e => { ed = e.currentTarget; emit(); } });
44
+ const B = [['bold', 'bold', t('RichEditor', 'bold')], ['italic', 'italic', t('RichEditor', 'italic')], ['underline', 'underline', t('RichEditor', 'underline')], ['strikeThrough', 'strikethrough', t('RichEditor', 'strike')], null, ['justifyLeft', 'align-left', t('RichEditor', 'left')], ['justifyCenter', 'align-center', t('RichEditor', 'center')], ['justifyRight', 'align-right', t('RichEditor', 'right')], null, ['insertUnorderedList', 'list', t('RichEditor', 'bullets')], ['insertOrderedList', 'list-ordered', t('RichEditor', 'numbers')], null, ['formatBlock', 'quote', t('RichEditor', 'quote'), 'blockquote'], ['formatBlock', 'code', t('RichEditor', 'codeBlock'), 'pre']];
45
+ const area = h('div', { class: 'zg-rte__c', contentEditable: 'true', role: 'textbox', 'aria-multiline': 'true', 'aria-label': p.label || t('RichEditor', 'editor'), 'data-ph': p.placeholder || t('RichEditor', 'placeholder'), ref: el => { if (!el) return; ed = el; const want = p.modelValue ?? ''; if (el.__zg !== want && el.innerHTML !== want) el.innerHTML = san(want); el.__zg = want; }, onFocus: e => { ed = e.currentTarget; }, onInput: e => { ed = e.currentTarget; emit(); } });
45
46
  return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-rte' }, h('div', { class: 'zg-rte__t' }, ...B.map((b, i) => b ? h('button', { key: b[0] + i, type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', title: b[2], 'aria-label': b[2], onMouseDown: e => e.preventDefault(), onClick: cmd(b[0], b[3]) }, icon(h, b[1], 16)) : h('span', { key: 's' + i, class: 'zg-cb__s' }))), area)); } },
46
47
  RangeSlider: { render: ({ h, props: p }) => { const mn = p.min ?? 0, mx = p.max ?? 100, [a, b] = p.modelValue || [mn, mx], pa = (a - mn) / (mx - mn) * 100, pb = (b - mn) / (mx - mn) * 100, f = p.formatValue || (v => v);
47
- const inp = (v, k) => h('input', { type: 'range', class: 'zg-range2', min: mn, max: mx, step: p.step || 1, value: v, 'aria-label': k ? 'Max' : 'Min', onInput: e => { const x = Number(e.target.value); p.onChange && p.onChange(k ? [a, Math.max(x, a)] : [Math.min(x, b), b]); } });
48
+ const inp = (v, k) => h('input', { type: 'range', class: 'zg-range2', min: mn, max: mx, step: p.step || 1, value: v, 'aria-label': k ? t('RangeSlider', 'max') : t('RangeSlider', 'min'), onInput: e => { const x = Number(e.target.value); p.onChange && p.onChange(k ? [a, Math.max(x, a)] : [Math.min(x, b), b]); } });
48
49
  return h('div', { class: 'zg-field' }, h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, p.label ? h('span', { class: 'zg-label' }, p.label) : h('span'), h('span', { class: 'zg-mono', style: { fontSize: '13px' } }, f(a) + ' – ' + f(b))), h('div', { class: 'zg-range2w', style: { '--a': pa + '%', '--b': pb + '%' } }, h('i'), inp(a, 0), inp(b, 1))); } },
49
50
  RangeCalendar: { state: { from: null, to: null, hover: null, ym: null }, render: ({ h, props: p, state, set }) => { const loc = p.locale || 'en-GB', ctl = p.modelValue !== undefined, mv = ctl ? (p.modelValue || []) : [], day = v => { const d = pd(v); return d ? new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime() : null; }, f = ctl ? day(mv[0]) : state.from, tt = ctl ? day(mv[1]) : state.to, b = state.ym ? new Date(state.ym) : (f != null ? new Date(f) : (pd(p.start) || new Date())), y = b.getFullYear(), m = b.getMonth(), ws = p.weekStartsOn ?? 1, off = (new Date(y, m, 1).getDay() - ws + 7) % 7, dn = new Date(y, m + 1, 0).getDate();
50
- const wd = Array.from({ length: 7 }, (_, i) => new Intl.DateTimeFormat(loc, { weekday: 'narrow' }).format(new Date(2023, 0, 1 + ((i + ws) % 7)))), t = d => new Date(y, m, d).getTime(), e = tt ?? (f && state.hover && state.hover > f ? state.hover : null);
51
- const pick = d => { const x = t(d); if (f == null || tt != null || x < f) { if (ctl) { set({ hover: null }); p.onChange && p.onChange([new Date(x), null]); } else set({ from: x, to: null, hover: null }); } else { if (!ctl) set({ to: x }); p.onChange && p.onChange([new Date(f), new Date(x)]); } };
52
- 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': 'Previous', 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': 'Next', onClick: () => set({ ym: new Date(y, m + 1, 1).getTime() }) }, icon(h, 'chevron-right', 16))),
53
- 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, x = t(d), edge = x === f || x === e, mid = f && e && x > f && x < e; return h('button', { key: d, type: 'button', class: 'zg-cal__d', 'aria-pressed': String(!!edge), 'data-mid': mid ? '' : undefined, onMouseEnter: () => f != null && tt == null && set({ hover: x }), onClick: () => pick(d) }, d); }))); } },
51
+ const wd = Array.from({ length: 7 }, (_, i) => new Intl.DateTimeFormat(loc, { weekday: 'narrow' }).format(new Date(2023, 0, 1 + ((i + ws) % 7)))), td = d => new Date(y, m, d).getTime(), e = tt ?? (f && state.hover && state.hover > f ? state.hover : null);
52
+ const pick = d => { const x = td(d); if (f == null || tt != null || x < f) { if (ctl) { set({ hover: null }); p.onChange && p.onChange([new Date(x), null]); } else set({ from: x, to: null, hover: null }); } else { if (!ctl) set({ to: x }); p.onChange && p.onChange([new Date(f), new Date(x)]); } };
53
+ 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('RangeCalendar', '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('RangeCalendar', 'next'), onClick: () => set({ ym: new Date(y, m + 1, 1).getTime() }) }, icon(h, 'chevron-right', 16))),
54
+ 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, x = td(d), edge = x === f || x === e, mid = f && e && x > f && x < e; return h('button', { key: d, type: 'button', class: 'zg-cal__d', 'aria-pressed': String(!!edge), 'data-mid': mid ? '' : undefined, onMouseEnter: () => f != null && tt == null && set({ hover: x }), onClick: () => pick(d) }, d); }))); } },
54
55
  StepDialog: { render: ({ C, h, props: p, children, slots }) => !p.open ? null : h('div', { class: 'zg-modal-scrim', onClick: p.onClose }, h('div', { class: 'zg-modal zg-stepdlg', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.title, ref: trap({ onClose: p.onClose }), onClick: e => e.stopPropagation() },
55
56
  h('aside', { class: 'zg-stepdlg__s' }, ...(p.steps || []).map((s, i) => h('div', { key: i, class: cx('zg-stepdlg__i', i < p.current && 'is-done', i === p.current && 'is-cur') }, h('span', { class: 'zg-step__n' }, i < p.current ? icon(h, 'check', 14) : i + 1), h('span', { style: { display: 'flex', flexDirection: 'column' } }, h('b', { style: { fontSize: '14px' } }, s.title), s.sub ? h('span', { class: 'zg-hint' }, s.sub) : null)))),
56
- h('div', { style: { flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column' } }, 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': 'Close', onClick: p.onClose }, icon(h, 'x'))), h('div', { class: 'zg-modal__body', style: { flex: 1 } }, ...children), h('div', { class: 'zg-modal__foot', style: { justifyContent: 'space-between' } }, h(C.Button, { disabled: p.current === 0, onClick: p.onBack }, 'Previous'), h(C.Button, { variant: 'primary', onClick: p.onNext }, p.current === (p.steps || []).length - 1 ? 'Submit' : 'Next'))))) },
57
- FullscreenDialog: { render: ({ C, h, props: p, children }) => !p.open ? null : h('div', { class: 'zg-fsd', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.title, ref: trap({ onClose: p.onClose }) }, h('div', { class: 'zg-fsd__h' }, h(C.IconButton, { icon: 'x', label: 'Close', onClick: p.onClose }), h('b', { style: { flex: 1, fontSize: '18px' } }, p.title), h(C.Button, { variant: 'inverse', onClick: p.onSave }, p.saveLabel || 'Save')), h('div', { class: 'zg-fsd__b' }, ...children)) }
57
+ h('div', { style: { flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column' } }, 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('StepDialog', 'close'), onClick: p.onClose }, icon(h, 'x'))), h('div', { class: 'zg-modal__body', style: { flex: 1 } }, ...children), h('div', { class: 'zg-modal__foot', style: { justifyContent: 'space-between' } }, h(C.Button, { disabled: p.current === 0, onClick: p.onBack }, t('StepDialog', 'previous')), h(C.Button, { variant: 'primary', onClick: p.onNext }, p.current === (p.steps || []).length - 1 ? t('StepDialog', 'submit') : t('StepDialog', 'next')))))) },
58
+ FullscreenDialog: { render: ({ C, h, props: p, children }) => !p.open ? null : h('div', { class: 'zg-fsd', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.title, ref: trap({ onClose: p.onClose }) }, h('div', { class: 'zg-fsd__h' }, h(C.IconButton, { icon: 'x', label: t('FullscreenDialog', 'close'), onClick: p.onClose }), h('b', { style: { flex: 1, fontSize: '18px' } }, p.title), h(C.Button, { variant: 'inverse', onClick: p.onSave }, p.saveLabel || t('FullscreenDialog', 'save'))), h('div', { class: 'zg-fsd__b' }, ...children)) }
58
59
  };
@@ -1,4 +1,5 @@
1
1
  // @ziyarago/ui · components, part 6 — last catalog gaps: TreeSelect, ContextMenu, Sortable, Map, Screensaver, ImageUpload, Tour.
2
+ import { t } from '../i18n/index.js';
2
3
  import { icon, cx, ddRef } from './components.js';
3
4
  import { trap, life } from './dialog.js';
4
5
  const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -10,7 +11,7 @@ export const more6 = {
10
11
  if (q && !all.filter(x => x.label.toLowerCase().includes(q)).some(x => x.id === n.id || (n.children || []).some(function f(c) { return c.id === x.id || (c.children || []).some(f); }))) return null;
11
12
  return h('div', { key: n.id }, h('button', { type: 'button', role: 'treeitem', disabled: n.disabled, 'aria-disabled': n.disabled ? 'true' : undefined, class: cx('zg-tree__n', n.id === p.modelValue && 'zg-tree__n--on'), style: { paddingInlineStart: 8 + d * 18 + 'px', width: '100%' }, onClick: () => { if (has && !q) set({ ex: { ...state.ex, [n.id]: !o } }); if (!has || p.selectGroups) { set({ open: false, q: '' }); p.onChange && p.onChange(n.id); } } }, has ? icon(h, o ? 'chevron-down' : 'chevron-right', 14) : h('span', { style: { width: '14px' } }), n.label), has && o ? n.children.map(c => node(c, d + 1)) : null); };
12
13
  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': 'tree', '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)),
13
- state.open && !p.disabled ? h('div', { class: 'zg-select__list', role: 'tree', 'data-zg-dd': '', ref: ddRef(() => set({ open: false, q: '' })), style: { padding: '8px' } }, h('span', { class: 'zg-input', style: { '--h': '36px', marginBottom: '6px' } }, icon(h, 'search', 14), h('input', { value: state.q, placeholder: 'Search', onInput: e => set({ q: e.target.value }) })), ...(p.nodes || []).map(n => node(n, 0))) : null)); } },
14
+ state.open && !p.disabled ? h('div', { class: 'zg-select__list', role: 'tree', 'data-zg-dd': '', ref: ddRef(() => set({ open: false, q: '' })), style: { padding: '8px' } }, h('span', { class: 'zg-input', style: { '--h': '36px', marginBottom: '6px' } }, icon(h, 'search', 14), h('input', { value: state.q, placeholder: t('TreeSelect', 'search'), onInput: e => set({ q: e.target.value }) })), ...(p.nodes || []).map(n => node(n, 0))) : null)); } },
14
15
  ContextMenu: { state: { x: 0, y: 0, open: false }, render: ({ h, props: p, state, set, children }) => {
15
16
  const close = () => set({ open: false });
16
17
  return h('div', { class: 'zg-ctx', onContextmenu: e => { e.preventDefault(); set({ open: true, x: e.clientX, y: e.clientY }); } }, ...children,
@@ -19,7 +20,7 @@ export const more6 = {
19
20
  const drop = (to, f) => { const from = f === undefined ? state.drag : f; set({ drag: null, over: null }); if (from == null || from === to) return; const a = items.slice(); const [m] = a.splice(from, 1); a.splice(to, 0, m); p.onChange && p.onChange(a); };
20
21
  return h('div', { class: 'zg-sort', role: 'list' }, ...items.map((it, i) => h('div', { key: it.id ?? i, role: 'listitem', class: cx('zg-sort__i', state.over === i && state.drag !== i && 'is-over', state.drag === i && 'is-drag'), draggable: 'true', onDragstart: e => { e.dataTransfer.effectAllowed = 'move'; try { e.dataTransfer.setData('text/plain', String(i)); } catch (x) {} set({ drag: i }); }, onDragover: e => { e.preventDefault(); if (state.over !== i) set({ over: i }); }, onDrop: e => { e.preventDefault(); drop(i); }, onDragend: () => set({ drag: null, over: null }) },
21
22
  h('span', { class: 'zg-sort__h', 'aria-hidden': 'true' }, icon(h, 'grip-vertical', 16)), h('div', { style: { flex: 1, minWidth: 0 } }, p.render ? p.render(it, i) : h('b', null, it.label)),
22
- h('span', { style: { display: 'flex', gap: '2px' } }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': 'Move up', disabled: i === 0, onClick: () => drop(i - 1, i) }, icon(h, 'chevron-up', 14)), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': 'Move down', disabled: i === items.length - 1, onClick: () => drop(i + 1, i) }, icon(h, 'chevron-down', 14)))))); } },
23
+ h('span', { style: { display: 'flex', gap: '2px' } }, h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('Sortable', 'moveUp'), disabled: i === 0, onClick: () => drop(i - 1, i) }, icon(h, 'chevron-up', 14)), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('Sortable', 'moveDown'), disabled: i === items.length - 1, onClick: () => drop(i + 1, i) }, icon(h, 'chevron-down', 14)))))); } },
23
24
  Map: { state: { lon: null, lat: null, z: null, dk: null, w: null }, render: ({ h, props: p, state, set }) => {
24
25
  const W = state.w || p.width || 640, H = p.height || 360, z = state.z ?? p.zoom ?? 14, lat0 = state.lat ?? p.lat ?? 21.4225, lon0 = state.lon ?? p.lon ?? 39.8262, n = 2 ** z;
25
26
  const mpx = (lat, lon, nn) => [(lon + 180) / 360 * nn * 256, (1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * nn * 256], px = (lat, lon) => mpx(lat, lon, n);
@@ -35,23 +36,23 @@ export const more6 = {
35
36
  el.addEventListener('pointermove', mv); el.addEventListener('pointerup', up); el.addEventListener('pointercancel', up); };
36
37
  const mk = (p.markers || []).map((m, i) => { const [mx, my] = px(m.lat, m.lon); return h('button', { key: 'm' + i, type: 'button', class: cx('zg-pin', m.tone && 'zg-t-' + m.tone), title: m.label, style: { left: (mx - x0) + 'px', top: (my - y0) + 'px' }, onClick: () => p.onSelect && p.onSelect(m) }, icon(h, m.icon || 'map-pin', 16)); });
37
38
  const stop = e => e.stopPropagation(), zb = (dz, k, d, ic) => h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm', 'aria-label': (p.labels && p.labels[k]) || d, onPointerdown: stop, onDblclick: stop, onClick: () => zoomAt(dz, 0, 0) }, icon(h, ic, 16));
38
- return h('div', { class: 'zg-map', role: 'application', 'aria-label': p.label || 'Map', style: { height: H + 'px' }, onPointerdown: pan, onDblclick: e => { const [ox, oy] = rel(e, e.currentTarget); zoomAt(1, ox, oy); },
39
+ return h('div', { class: 'zg-map', role: 'application', 'aria-label': p.label || t('Map', 'label'), style: { height: H + 'px' }, onPointerdown: pan, onDblclick: e => { const [ox, oy] = rel(e, e.currentTarget); zoomAt(1, ox, oy); },
39
40
  ref: el => { if (!el) return; el.__zgw = e => { e.preventDefault(); const t = Date.now(); if (t - (el.__zgt || 0) < 180) return; el.__zgt = t; const [ox, oy] = rel(e, el); zoomAt(e.deltaY < 0 ? 1 : -1, ox, oy); };
40
41
  if (!el.__zgi) { el.__zgi = 1; el.addEventListener('wheel', e => el.__zgw(e), { passive: false }); if (typeof ResizeObserver !== 'undefined') new ResizeObserver(() => { const w = Math.round(el.clientWidth); if (w && w !== el.__zgl) { el.__zgl = w; set({ w }); } }).observe(el); }
41
42
  if (p.theme == null && state.dk == null) set({ dk: !!el.closest('[data-theme="dark"]') }); } }, ...tiles, ...mk,
42
- h('div', { class: 'zg-map__z' }, zb(1, 'zoomIn', 'Zoom in', 'plus'), zb(-1, 'zoomOut', 'Zoom out', 'minus')),
43
+ h('div', { class: 'zg-map__z' }, zb(1, 'zoomIn', t('Map', 'zoomIn'), 'plus'), zb(-1, 'zoomOut', t('Map', 'zoomOut'), 'minus')),
43
44
  h('span', { class: 'zg-map__c' }, '© OpenStreetMap · CARTO')); } },
44
45
  ImageUpload: { state: { src: null }, render: ({ h, props: p, state, set }) => { const src = state.src || p.modelValue, round = p.shape === 'circle';
45
- return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('label', { class: 'zg-imgup', style: { aspectRatio: p.aspect || '1/1', maxWidth: (({ xs: 112, sm: 128, md: 160, lg: 192, xl: 224 })[p.size] || p.size || 160) + 'px', borderRadius: round ? '50%' : '24px', backgroundImage: src ? `url(${src})` : undefined } }, src ? null : h('span', { class: 'zg-imgup__e' }, icon(h, 'image-up', 24), h('span', { class: 'zg-hint' }, p.hint || 'Upload')), h('span', { class: 'zg-imgup__o' }, icon(h, 'camera', 18)),
46
+ return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('label', { class: 'zg-imgup', style: { aspectRatio: p.aspect || '1/1', maxWidth: (({ xs: 112, sm: 128, md: 160, lg: 192, xl: 224 })[p.size] || p.size || 160) + 'px', borderRadius: round ? '50%' : '24px', backgroundImage: src ? `url(${src})` : undefined } }, src ? null : h('span', { class: 'zg-imgup__e' }, icon(h, 'image-up', 24), h('span', { class: 'zg-hint' }, p.hint || t('ImageUpload', 'upload'))), h('span', { class: 'zg-imgup__o' }, icon(h, 'camera', 18)),
46
47
  h('input', { type: 'file', accept: 'image/*', style: { display: 'none' }, onChange: e => { const f = e.target.files[0]; if (!f) return; const r = new FileReader(); r.onload = () => { set({ src: r.result }); p.onChange && p.onChange(r.result, f); }; r.readAsDataURL(f); } }))); } },
47
48
  Screensaver: { state: { t: 0 }, render: ({ h, props: p, state, set }) => { if (!p.open) return p.idleMs > 0 ? h('span', { hidden: true, 'aria-hidden': 'true', ref: life({ idleMs: p.idleMs, onIdle: p.onIdle, mount: (el, get) => { let tm; const arm = () => { clearTimeout(tm); tm = setTimeout(() => { const o = get(); o.onIdle && o.onIdle(); }, get().idleMs); }, evs = ['pointermove', 'pointerdown', 'keydown', 'wheel', 'touchstart']; evs.forEach(e => document.addEventListener(e, arm, { passive: true })); arm(); return () => { clearTimeout(tm); evs.forEach(e => document.removeEventListener(e, arm)); }; } }) }) : null;
48
49
  const d = new Date(state.t || 0), pad = n => String(n).padStart(2, '0'), ph = (d.getSeconds() % 40) / 40;
49
- return h('div', { class: 'zg-saver', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Screensaver', tabIndex: 0, ref: trap({ onClose: p.onClose, onKey: e => { if (e.key === 'Tab') return false; p.onClose && p.onClose(); return true; }, mount: el => { const id = setInterval(() => set({ t: Date.now() }), 1000); set({ t: Date.now() }); return () => clearInterval(id); } }), onClick: p.onClose }, h('span', { class: 'zg-saver__g', style: { left: (10 + ph * 60) + '%', top: (20 + Math.sin(ph * 6.28) * 18) + '%' } }),
50
+ return h('div', { class: 'zg-saver', role: 'dialog', 'aria-modal': 'true', 'aria-label': t('Screensaver', 'label'), tabIndex: 0, ref: trap({ onClose: p.onClose, onKey: e => { if (e.key === 'Tab') return false; p.onClose && p.onClose(); return true; }, mount: el => { const id = setInterval(() => set({ t: Date.now() }), 1000); set({ t: Date.now() }); return () => clearInterval(id); } }), onClick: p.onClose }, h('span', { class: 'zg-saver__g', style: { left: (10 + ph * 60) + '%', top: (20 + Math.sin(ph * 6.28) * 18) + '%' } }),
50
51
  h('div', { class: 'zg-saver__c' }, h('span', { class: 'zg-saver__m', style: { backgroundImage: 'url(assets/brand/ziyarago-mark.svg)' } }), h('b', { class: 'zg-num', style: { fontSize: 'clamp(64px,12vw,160px)', fontWeight: 200, letterSpacing: '-.04em', lineHeight: 1 } }, state.t ? pad(d.getHours()) + ':' + pad(d.getMinutes()) : '--:--'), h('span', { style: { color: 'var(--zg-text-2)', fontSize: '18px' } }, state.t ? new Intl.DateTimeFormat(p.locale || 'en-GB', { weekday: 'long', day: 'numeric', month: 'long' }).format(d) : '\u00a0'), p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null)); } },
51
52
  Tour: { state: { i: 0, r: null }, render: ({ C, h, props: p, state, set }) => { if (!p.open) return null; const st = p.steps || [], i = clamp(p.step != null ? p.step : state.i, 0, Math.max(0, st.length - 1)), s = st[i] || {};
52
53
  const measure = () => { if (state.r || state.busy || !s.target) return; const el0 = document.querySelector(s.target); if (!el0) return; state.busy = true; setTimeout(() => { const el = document.querySelector(s.target) || el0, q = el.getBoundingClientRect(); if (q.top < 80 || q.bottom > innerHeight - 220) { let a = el.parentElement; while (a && !(/(auto|scroll)/.test(getComputedStyle(a).overflowY) && a.scrollHeight > a.clientHeight + 4)) a = a.parentElement; const dy = q.top - innerHeight / 3; if (a) a.scrollTop += dy; else window.scrollTo(0, window.scrollY + dy); } setTimeout(() => { state.busy = false; set({ r: (document.querySelector(s.target) || el).getBoundingClientRect() }); }, 160); }, 0); };
53
54
  const r = state.r, pad = 8, below = r ? r.bottom + 12 + 190 < innerHeight : true;
54
55
  const go = d => { const n = clamp(i + d, 0, st.length - 1); if (n === i) return; set({ i: n, r: null }); p.onStep && p.onStep(n); }, done = () => { set({ i: 0, r: null }); p.onClose && p.onClose(); };
55
- return h('div', { class: 'zg-tourov', role: 'dialog', 'aria-label': 'Tour', ref: el => { if (el) measure(); trap({ focus: false, onClose: done, mount: () => () => set({ i: 0, r: null }), onKey: e => { const rtl = document.documentElement.dir === 'rtl', k = e.key === 'ArrowRight' ? (rtl ? -1 : 1) : e.key === 'ArrowLeft' ? (rtl ? 1 : -1) : 0; if (k) { e.preventDefault(); go(k); return true; } } })(el); } }, r ? h('span', { class: 'zg-tourov__cut', style: { left: (r.left - pad) + 'px', top: (r.top - pad) + 'px', width: (r.width + pad * 2) + 'px', height: (r.height + pad * 2) + 'px' } }) : h('span', { class: 'zg-tourov__full' }),
56
+ return h('div', { class: 'zg-tourov', role: 'dialog', 'aria-label': t('Tour', 'label'), ref: el => { if (el) measure(); trap({ focus: false, onClose: done, mount: () => () => set({ i: 0, r: null }), onKey: e => { const rtl = document.documentElement.dir === 'rtl', k = e.key === 'ArrowRight' ? (rtl ? -1 : 1) : e.key === 'ArrowLeft' ? (rtl ? 1 : -1) : 0; if (k) { e.preventDefault(); go(k); return true; } } })(el); } }, r ? h('span', { class: 'zg-tourov__cut', style: { left: (r.left - pad) + 'px', top: (r.top - pad) + 'px', width: (r.width + pad * 2) + 'px', height: (r.height + pad * 2) + 'px' } }) : h('span', { class: 'zg-tourov__full' }),
56
57
  h('div', { style: { position: 'fixed', zIndex: 2, left: r ? clamp(r.left, 12, innerWidth - 392) + 'px' : '50%', top: r ? (below ? r.bottom + 12 : Math.max(12, r.top - 12 - 190)) + 'px' : '40%', transform: r ? 'none' : 'translateX(-50%)' } }, h(C.TourPopover, { title: s.title, text: s.text, step: (i + 1) + ' / ' + st.length, last: i === st.length - 1, onBack: i ? () => go(-1) : null, onNext: () => i === st.length - 1 ? done() : go(1), onClose: done }))); } }
57
58
  };
@@ -1,4 +1,5 @@
1
1
  // @ziyarago/ui · components, part 7 — gluestack parity: typography, layout primitives, Link, Pressable, Icon, Portal, sheets, ImageViewer, ChatAi, LiquidGlass.
2
+ import { t } from '../i18n/index.js';
2
3
  import { icon, cx } from './components.js';
3
4
  import { trap } from './dialog.js';
4
5
  const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -22,21 +23,21 @@ export const more7 = {
22
23
  h('span', { class: 'zg-sheet__grab', 'aria-hidden': 'true' }), p.title ? h('h3', { class: 'zg-sheet__t' }, p.title) : null, h('div', { class: 'zg-sheet__b' }, ...children), (slots.footer || p.footer) ? h('div', { class: 'zg-sheet__f' }, slots.footer || p.footer) : null)) },
23
24
  Actionsheet: { render: ({ C, h, props: p }) => h(C.BottomSheet, { open: p.open, size: p.size, onClose: p.onClose, title: p.title },
24
25
  h('div', { class: 'zg-as' }, ...(p.items || []).map((it, i) => h('button', { key: i, type: 'button', class: cx('zg-as__i', it.tone && 'zg-as__i--' + it.tone), disabled: it.disabled, onClick: () => { it.onClick && it.onClick(); p.onSelect && p.onSelect(it); p.onClose && p.onClose(); } }, it.icon ? icon(h, it.icon, 18) : null, h('span', null, it.label)))),
25
- h('button', { type: 'button', class: 'zg-as__i zg-as__i--cancel', onClick: p.onClose }, p.cancelLabel || 'Cancel')) },
26
+ h('button', { type: 'button', class: 'zg-as__i zg-as__i--cancel', onClick: p.onClose }, p.cancelLabel || t('Actionsheet', 'cancel'))) },
26
27
  ImageViewer: { state: { i: null }, render: ({ h, props: p, state, set }) => {
27
28
  if (!p.open) return null;
28
29
  const im = p.images || [], i = clamp(state.i ?? p.index ?? 0, 0, Math.max(0, im.length - 1)), go = d => set({ i: (i + d + im.length) % im.length });
29
- return h('div', { class: 'zg-iv', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.alt || 'Image viewer', onClick: p.onClose },
30
- h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--ghost zg-iv__x', 'aria-label': 'Close', onClick: p.onClose }, icon(h, 'x', 20)),
31
- im.length > 1 ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--ghost zg-iv__p', 'aria-label': 'Previous', onClick: e => { e.stopPropagation(); go(-1); } }, icon(h, 'chevron-left', 22)) : null,
30
+ return h('div', { class: 'zg-iv', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.alt || t('ImageViewer', 'label'), onClick: p.onClose },
31
+ h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--ghost zg-iv__x', 'aria-label': t('ImageViewer', 'close'), onClick: p.onClose }, icon(h, 'x', 20)),
32
+ im.length > 1 ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--ghost zg-iv__p', 'aria-label': t('ImageViewer', 'previous'), onClick: e => { e.stopPropagation(); go(-1); } }, icon(h, 'chevron-left', 22)) : null,
32
33
  h('img', { class: 'zg-iv__img', src: im[i], alt: p.alt || '', onClick: e => e.stopPropagation() }),
33
- im.length > 1 ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--ghost zg-iv__n', 'aria-label': 'Next', onClick: e => { e.stopPropagation(); go(1); } }, icon(h, 'chevron-right', 22)) : null,
34
+ im.length > 1 ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--ghost zg-iv__n', 'aria-label': t('ImageViewer', 'next'), onClick: e => { e.stopPropagation(); go(1); } }, icon(h, 'chevron-right', 22)) : null,
34
35
  h('span', { class: 'zg-badge zg-iv__c' }, `${i + 1} / ${im.length}`)); } },
35
36
  ChatAi: { state: { q: '' }, render: ({ h, props: p, state, set }) => {
36
37
  const send = () => { const t = state.q.trim(); if (!t || p.loading) return; set({ q: '' }); p.onSend && p.onSend(t); };
37
- return h('section', { class: 'zg-chat zg-glass', 'aria-label': p.title || 'Assistant' },
38
+ return h('section', { class: 'zg-chat zg-glass', 'aria-label': p.title || t('ChatAi', 'assistant') },
38
39
  p.title ? h('div', { class: 'zg-chat__h' }, h('b', null, p.title), p.subtitle ? h('span', { class: 'zg-hint' }, p.subtitle) : null) : null,
39
- h('div', { class: 'zg-chat__m', role: 'log', 'aria-live': 'polite' }, ...(p.messages || []).map((m, i) => h('div', { key: i, class: cx('zg-chat__b', 'zg-chat__b--' + (m.role === 'user' ? 'user' : 'ai')) }, m.text)), p.loading ? h('div', { class: 'zg-chat__b zg-chat__b--ai zg-chat__dots', 'aria-label': 'Typing' }, h('i'), h('i'), h('i')) : null),
40
- h('div', { class: 'zg-chat__f' }, h('input', { class: 'zg-chat__in', value: state.q, placeholder: p.placeholder || 'Ask anything…', 'aria-label': 'Message', onInput: e => set({ q: e.target.value }), onKeydown: e => { if (e.key === 'Enter') { e.preventDefault(); send(); } } }),
41
- h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--brand', 'aria-label': 'Send', disabled: p.loading || !state.q.trim(), onClick: send }, icon(h, 'send', 18)))); } },
40
+ h('div', { class: 'zg-chat__m', role: 'log', 'aria-live': 'polite' }, ...(p.messages || []).map((m, i) => h('div', { key: i, class: cx('zg-chat__b', 'zg-chat__b--' + (m.role === 'user' ? 'user' : 'ai')) }, m.text)), p.loading ? h('div', { class: 'zg-chat__b zg-chat__b--ai zg-chat__dots', 'aria-label': t('ChatAi', 'typing') }, h('i'), h('i'), h('i')) : null),
41
+ h('div', { class: 'zg-chat__f' }, h('input', { class: 'zg-chat__in', value: state.q, placeholder: p.placeholder || t('ChatAi', 'placeholder'), 'aria-label': t('ChatAi', 'message'), onInput: e => set({ q: e.target.value }), onKeydown: e => { if (e.key === 'Enter') { e.preventDefault(); send(); } } }),
42
+ h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--brand', 'aria-label': t('ChatAi', 'send'), disabled: p.loading || !state.q.trim(), onClick: send }, icon(h, 'send', 18)))); } },
42
43
  };
package/src/core/index.js CHANGED
@@ -7,5 +7,5 @@ import { more6 } from './components6.js';
7
7
  import { more7 } from './components7.js';
8
8
  export const components = { ...base, ...more, ...more3, ...more4, ...more6, ...more7, ...pages };
9
9
  export { fmt, setIconBase, rules };
10
- export { LOCALES, t, setUiLocale, getUiLocale, setUiMessages, isRtlLocale, subscribeUiLocale, getUiVersion, defineMessages } from '../i18n/index.js';
10
+ export { LOCALES, FREE_LOCALES, t, setUiLocale, getUiLocale, setUiMessages, isRtlLocale, subscribeUiLocale, getUiVersion, defineMessages } from '../i18n/index.js';
11
11
  import '../i18n/components/index.js';
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { expand: 'Expand', collapse: 'Collapse', refresh: 'Refresh', remove: 'Remove' },
3
+ ar: { expand: 'توسيع', collapse: 'طي', refresh: 'تحديث', remove: 'إزالة' },
4
+ ru: { expand: 'Развернуть', collapse: 'Свернуть', refresh: 'Обновить', remove: 'Удалить' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { cancel: 'Cancel' },
3
+ ar: { cancel: 'إلغاء' },
4
+ ru: { cancel: 'Отмена' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { close: 'Close' },
3
+ ar: { close: 'إغلاق' },
4
+ ru: { close: 'Закрыть' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { label: 'Breadcrumb' },
3
+ ar: { label: 'مسار التنقل' },
4
+ ru: { label: 'Хлебные крошки' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { prev: 'Previous month', next: 'Next month' },
3
+ ar: { prev: 'الشهر السابق', next: 'الشهر التالي' },
4
+ ru: { prev: 'Предыдущий месяц', next: 'Следующий месяц' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { play: 'Play' },
3
+ ar: { play: 'تشغيل' },
4
+ ru: { play: 'Воспроизвести' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { play: 'Play' },
3
+ ar: { play: 'تشغيل' },
4
+ ru: { play: 'Воспроизвести' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { prev: 'Previous', next: 'Next', slide: 'Slide' },
3
+ ar: { prev: 'السابق', next: 'التالي', slide: 'شريحة' },
4
+ ru: { prev: 'Предыдущий', next: 'Следующий', slide: 'Слайд' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { more: 'More' },
3
+ ar: { more: 'المزيد' },
4
+ ru: { more: 'Ещё' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { assistant: 'Assistant', placeholder: 'Ask anything…', typing: 'Typing', message: 'Message', send: 'Send' },
3
+ ar: { assistant: 'المساعد', placeholder: 'اسأل أي شيء…', typing: 'يكتب', message: 'رسالة', send: 'إرسال' },
4
+ ru: { assistant: 'Ассистент', placeholder: 'Спросите что угодно…', typing: 'Печатает', message: 'Сообщение', send: 'Отправить' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { remove: 'Remove' },
3
+ ar: { remove: 'إزالة' },
4
+ ru: { remove: 'Удалить' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { remove: 'Remove {name}' },
3
+ ar: { remove: 'إزالة {name}' },
4
+ ru: { remove: 'Удалить {name}' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { label: 'Code' },
3
+ ar: { label: 'الرمز' },
4
+ ru: { label: 'Код' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { placeholder: 'Search…', clear: 'Clear' },
3
+ ar: { placeholder: 'ابحث…', clear: 'مسح' },
4
+ ru: { placeholder: 'Поиск…', clear: 'Очистить' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { label: 'Command palette', placeholder: 'Type a command…', empty: 'No commands' },
3
+ ar: { label: 'لوحة الأوامر', placeholder: 'اكتب أمرًا…', empty: 'لا توجد أوامر' },
4
+ ru: { label: 'Палитра команд', placeholder: 'Введите команду…', empty: 'Нет команд' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { cancel: 'Cancel', confirm: 'Confirm' },
3
+ ar: { cancel: 'إلغاء', confirm: 'تأكيد' },
4
+ ru: { cancel: 'Отмена', confirm: 'Подтвердить' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { phone: 'Phone', telegram: 'Telegram', email: 'Email', city: 'City', call: 'Call', message: 'Message' },
3
+ ar: { phone: 'الهاتف', telegram: 'Telegram', email: 'البريد الإلكتروني', city: 'المدينة', call: 'اتصال', message: 'رسالة' },
4
+ ru: { phone: 'Телефон', telegram: 'Telegram', email: 'Email', city: 'Город', call: 'Позвонить', message: 'Написать' },
5
+ };
@@ -1,3 +1,5 @@
1
1
  export default {
2
2
  en: { copy: 'Copy', copied: 'Copied!', failed: 'Copy failed' },
3
+ ar: { copy: 'نسخ', copied: 'تم النسخ!', failed: 'فشل النسخ' },
4
+ ru: { copy: 'Копировать', copied: 'Скопировано!', failed: 'Не удалось скопировать' },
3
5
  };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { expand: 'Expand' },
3
+ ar: { expand: 'توسيع' },
4
+ ru: { expand: 'Развернуть' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { loading: 'Loading…', empty: 'Nothing here yet', error: 'Something went wrong', retry: 'Retry', offline: 'No internet', offlineDesc: 'Saved guides still work.', tryAgain: 'Try again' },
3
+ ar: { loading: 'جارٍ التحميل…', empty: 'لا يوجد شيء هنا بعد', error: 'حدث خطأ ما', retry: 'إعادة المحاولة', offline: 'لا يوجد اتصال بالإنترنت', offlineDesc: 'الأدلة المحفوظة ما زالت تعمل.', tryAgain: 'حاول مرة أخرى' },
4
+ ru: { loading: 'Загрузка…', empty: 'Пока здесь пусто', error: 'Что-то пошло не так', retry: 'Повторить', offline: 'Нет интернета', offlineDesc: 'Сохранённые путеводители по-прежнему работают.', tryAgain: 'Повторить попытку' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { selectAll: 'Select all', selectRow: 'Select row' },
3
+ ar: { selectAll: 'تحديد الكل', selectRow: 'تحديد الصف' },
4
+ ru: { selectAll: 'Выбрать все', selectRow: 'Выбрать строку' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { placeholder: 'Select date', clear: 'Clear' },
3
+ ar: { placeholder: 'اختر التاريخ', clear: 'مسح' },
4
+ ru: { placeholder: 'Выберите дату', clear: 'Очистить' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { probability: 'Probability' },
3
+ ar: { probability: 'الاحتمال' },
4
+ ru: { probability: 'Вероятность' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { close: 'Close' },
3
+ ar: { close: 'إغلاق' },
4
+ ru: { close: 'Закрыть' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { title: 'Page not found', description: 'The page may have moved. Check the address or go back.' },
3
+ ar: { title: 'الصفحة غير موجودة', description: 'ربما تم نقل الصفحة. تحقق من العنوان أو ارجع.' },
4
+ ru: { title: 'Страница не найдена', description: 'Возможно, страница была перемещена. Проверьте адрес или вернитесь назад.' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { title: 'Drop files here or browse', hint: 'PDF, JPG up to 10 MB' },
3
+ ar: { title: 'أفلت الملفات هنا أو تصفح', hint: 'PDF وJPG حتى 10 ميغابايت' },
4
+ ru: { title: 'Перетащите файлы сюда или выберите', hint: 'PDF, JPG до 10 МБ' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { close: 'Close', save: 'Save' },
3
+ ar: { close: 'إغلاق', save: 'حفظ' },
4
+ ru: { close: 'Закрыть', save: 'Сохранить' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { photo: 'Photo' },
3
+ ar: { photo: 'صورة' },
4
+ ru: { photo: 'Фото' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { nights: '{n} nights' },
3
+ ar: { nights: '{n} ليالٍ' },
4
+ ru: { nights: '{n} ноч.' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { upload: 'Upload' },
3
+ ar: { upload: 'رفع' },
4
+ ru: { upload: 'Загрузить' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { label: 'Image viewer', close: 'Close', previous: 'Previous', next: 'Next' },
3
+ ar: { label: 'عارض الصور', close: 'إغلاق', previous: 'السابق', next: 'التالي' },
4
+ ru: { label: 'Просмотр изображений', close: 'Закрыть', previous: 'Назад', next: 'Далее' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { incoming: 'Incoming call', decline: 'Decline', answer: 'Answer' },
3
+ ar: { incoming: 'مكالمة واردة', decline: 'رفض', answer: 'رد' },
4
+ ru: { incoming: 'Входящий звонок', decline: 'Отклонить', answer: 'Ответить' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { save: 'Save', edit: 'Click to edit' },
3
+ ar: { save: 'حفظ', edit: 'انقر للتعديل' },
4
+ ru: { save: 'Сохранить', edit: 'Нажмите, чтобы изменить' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { connected: 'Connected', error: 'Error', off: 'Not linked', manage: 'Manage', reconnect: 'Reconnect', link: 'Link' },
3
+ ar: { connected: 'متصل', error: 'خطأ', off: 'غير مرتبط', manage: 'إدارة', reconnect: 'إعادة الاتصال', link: 'ربط' },
4
+ ru: { connected: 'Подключено', error: 'Ошибка', off: 'Не подключено', manage: 'Управлять', reconnect: 'Подключить снова', link: 'Подключить' },
5
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ en: { email: 'Email or phone', password: 'Password', remember: 'Remember device', forgot: 'Forgot password?', submit: 'Sign in' },
3
+ ar: { email: 'البريد الإلكتروني أو الهاتف', password: 'كلمة المرور', remember: 'تذكر هذا الجهاز', forgot: 'نسيت كلمة المرور؟', submit: 'تسجيل الدخول' },
4
+ ru: { email: 'Email или телефон', password: 'Пароль', remember: 'Запомнить устройство', forgot: 'Забыли пароль?', submit: 'Войти' },
5
+ };