@ziyarago/ui 0.0.0-stage → 0.1.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 +46 -0
- package/LICENSE +25 -0
- package/README.md +121 -2
- package/THIRD_PARTY_NOTICES.md +12 -0
- package/assets/3d/_sheet.png +0 -0
- package/assets/3d/archive.png +0 -0
- package/assets/3d/bag.png +0 -0
- package/assets/3d/cameraoff.png +0 -0
- package/assets/3d/comment.png +0 -0
- package/assets/3d/crowd.png +0 -0
- package/assets/3d/firstaid.png +0 -0
- package/assets/3d/flag.png +0 -0
- package/assets/3d/grain.png +0 -0
- package/assets/3d/guide.png +0 -0
- package/assets/3d/hotel.png +0 -0
- package/assets/3d/kaaba.png +0 -0
- package/assets/3d/logo.png +0 -0
- package/assets/3d/luggage.png +0 -0
- package/assets/3d/map.png +0 -0
- package/assets/3d/mosque.png +0 -0
- package/assets/3d/plane.png +0 -0
- package/assets/3d/profile.png +0 -0
- package/assets/3d/restaurant.png +0 -0
- package/assets/3d/ring360.png +0 -0
- package/assets/3d/shoes.png +0 -0
- package/assets/3d/star.png +0 -0
- package/assets/3d/store.png +0 -0
- package/assets/3d/taxi.png +0 -0
- package/assets/3d/threads.png +0 -0
- package/assets/3d/tour360.png +0 -0
- package/assets/3d/trash.png +0 -0
- package/assets/3d/umbrella.png +0 -0
- package/assets/3d/video.png +0 -0
- package/assets/3d/water.png +0 -0
- package/assets/brand/mark.png +0 -0
- package/assets/brand/ziyarago-logo-light.svg +21 -0
- package/assets/brand/ziyarago-logo.svg +21 -0
- package/assets/brand/ziyarago-mark-light.svg +13 -0
- package/assets/brand/ziyarago-mark.svg +13 -0
- package/assets/fonts/thmanyah/thmanyahsans-Black.otf +0 -0
- package/assets/fonts/thmanyah/thmanyahsans-Bold.otf +0 -0
- package/assets/fonts/thmanyah/thmanyahsans-Light.otf +0 -0
- package/assets/fonts/thmanyah/thmanyahsans-Medium.otf +0 -0
- package/assets/fonts/thmanyah/thmanyahsans-Regular.otf +0 -0
- package/assets/fonts/thmanyah/thmanyahserifdisplay-Black.otf +0 -0
- package/assets/fonts/thmanyah/thmanyahserifdisplay-Bold.otf +0 -0
- package/assets/fonts/thmanyah/thmanyahserifdisplay-Light.otf +0 -0
- package/assets/fonts/thmanyah/thmanyahserifdisplay-Medium.otf +0 -0
- package/assets/fonts/thmanyah/thmanyahserifdisplay-Regular.otf +0 -0
- package/assets/fonts/thmanyah/thmanyahseriftext-Black.otf +0 -0
- package/assets/fonts/thmanyah/thmanyahseriftext-Bold.otf +0 -0
- package/assets/fonts/thmanyah/thmanyahseriftext-Light.otf +0 -0
- package/assets/fonts/thmanyah/thmanyahseriftext-Medium.otf +0 -0
- package/assets/fonts/thmanyah/thmanyahseriftext-Regular.otf +0 -0
- package/dist/llms-full.txt +2293 -0
- package/dist/llms.txt +239 -0
- package/dist/manifest.json +10254 -0
- package/dist/style.css +1357 -0
- package/dist/tokens.css +48 -0
- package/package.json +118 -4
- package/src/catalog.js +4 -0
- package/src/core/components.js +124 -0
- package/src/core/components2.js +163 -0
- package/src/core/components3.js +35 -0
- package/src/core/components4.js +58 -0
- package/src/core/components6.js +48 -0
- package/src/core/components7.js +42 -0
- package/src/core/dialog.js +65 -0
- package/src/core/index.js +9 -0
- package/src/core/pages.js +51 -0
- package/src/meta/README.md +29 -0
- package/src/meta/actions.js +161 -0
- package/src/meta/data.js +327 -0
- package/src/meta/domain.js +212 -0
- package/src/meta/feedback.js +153 -0
- package/src/meta/forms.js +453 -0
- package/src/meta/groups.js +16 -0
- package/src/meta/index.js +14 -0
- package/src/meta/layout.js +159 -0
- package/src/meta/media.js +104 -0
- package/src/meta/navigation.js +99 -0
- package/src/meta/overlay.js +264 -0
- package/src/meta/pages.js +229 -0
- package/src/meta/templates.json +2422 -0
- package/src/meta/typography.js +58 -0
- package/src/nuxt/module.mjs +29 -0
- package/src/nuxt/plugin.mjs +9 -0
- package/src/react/index.js +31 -0
- package/src/styles/components.css +162 -0
- package/src/styles/components2.css +68 -0
- package/src/styles/components3.css +29 -0
- package/src/styles/components4.css +18 -0
- package/src/styles/components5.css +5 -0
- package/src/styles/components6.css +19 -0
- package/src/styles/components7.css +44 -0
- package/src/styles/components8.css +47 -0
- package/src/styles/effects.css +43 -0
- package/src/styles/fonts.css +17 -0
- package/src/styles/index.css +21 -0
- package/src/styles/sizes/actions.css +126 -0
- package/src/styles/sizes/data.css +161 -0
- package/src/styles/sizes/domain.css +35 -0
- package/src/styles/sizes/feedback.css +69 -0
- package/src/styles/sizes/forms.css +122 -0
- package/src/styles/sizes/layout.css +15 -0
- package/src/styles/sizes/media.css +28 -0
- package/src/styles/sizes/navigation.css +146 -0
- package/src/styles/sizes/overlay.css +79 -0
- package/src/styles/sizes/typography.css +12 -0
- package/src/styles/tokens.css +47 -0
- package/src/theme.js +52 -0
- package/src/vue/index.js +29 -0
- package/tokens/tokens.json +9 -0
- package/types/core.d.ts +4 -0
- package/types/nuxt.d.ts +11 -0
- package/types/react.d.ts +189 -0
- package/types/shared.d.ts +18 -0
- package/types/theme.d.ts +6 -0
- package/types/vue.d.ts +192 -0
package/dist/tokens.css
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* generated */
|
|
2
|
+
/* @ziyarago/ui · tokens (OKLCH). Theme: [data-theme=dark|light] · Palette: [data-palette=gold|tamam] · Direction: dir=rtl */
|
|
3
|
+
:root,[data-palette="gold"]{--zg-brand:oklch(0.676 0.084 67.4);--zg-brand-hover:oklch(0.73 0.09 67.4);--zg-sky:oklch(0.703 0.09 229.6)}
|
|
4
|
+
[data-palette="tamam"]{--zg-brand:oklch(0.77 0.23 142);--zg-brand-hover:oklch(0.82 0.23 142);--zg-sky:oklch(0.72 0.1 200)}
|
|
5
|
+
:root,[data-theme="dark"]{--zg-pop:oklch(0.262 0.004 17.4);
|
|
6
|
+
--zg-on-brand:oklch(0.2 0.02 60);
|
|
7
|
+
--zg-brand-soft:color-mix(in oklch,var(--zg-brand) 22%,transparent);
|
|
8
|
+
--zg-brand-glow:color-mix(in oklch,var(--zg-brand) 40%,transparent);
|
|
9
|
+
--zg-brand-text:color-mix(in oklch,var(--zg-brand) 70%,white);
|
|
10
|
+
--zg-sky-text:oklch(0.8 0.08 226);
|
|
11
|
+
--zg-bg:oklch(0.216 0.003 17.4);
|
|
12
|
+
--zg-s1:oklch(1 0 0 / .055);--zg-s2:oklch(1 0 0 / .085);--zg-s3:oklch(1 0 0 / .13);
|
|
13
|
+
--zg-glass:oklch(0.235 0.006 40 / .92);
|
|
14
|
+
--zg-stroke:oklch(1 0 0 / .08);
|
|
15
|
+
--zg-text-1:oklch(0.97 0.004 70);--zg-text-2:oklch(0.82 0.008 70);--zg-text-3:oklch(0.66 0.01 70);
|
|
16
|
+
--zg-inv-bg:oklch(0.96 0.004 70);--zg-inv-fg:oklch(0.2 0.01 60);
|
|
17
|
+
--zg-green:oklch(0.76 0.15 150);--zg-amber:oklch(0.8 0.14 80);--zg-red:oklch(0.7 0.18 25);
|
|
18
|
+
--zg-rim:inset 0 1px 0 oklch(1 0 0 / .16),inset 0 0 0 1px oklch(1 0 0 / .07),inset 0 -1px 0 oklch(0 0 0 / .25);
|
|
19
|
+
--zg-shadow-1:0 8px 24px oklch(0 0 0 / .28);--zg-shadow-2:0 24px 64px oklch(0 0 0 / .5);
|
|
20
|
+
}
|
|
21
|
+
[data-theme="light"]{--zg-pop:oklch(0.985 0.003 70);
|
|
22
|
+
--zg-on-brand:oklch(0.2 0.02 60);
|
|
23
|
+
--zg-brand-text:oklch(0.45 0.07 65);
|
|
24
|
+
--zg-sky-text:oklch(0.48 0.08 230);
|
|
25
|
+
--zg-bg:oklch(0.919 0.007 67.6);
|
|
26
|
+
--zg-s1:oklch(1 0 0 / .62);--zg-s2:oklch(1 0 0 / .74);--zg-s3:oklch(1 0 0 / .88);
|
|
27
|
+
--zg-glass:oklch(0.975 0.004 70 / .94);
|
|
28
|
+
--zg-stroke:oklch(0.3 0.02 60 / .15);
|
|
29
|
+
--zg-text-1:oklch(0.2 0.01 60);--zg-text-2:oklch(0.36 0.012 60);--zg-text-3:oklch(0.47 0.012 60);
|
|
30
|
+
--zg-inv-bg:oklch(0.22 0.01 60);--zg-inv-fg:oklch(0.97 0.004 70);
|
|
31
|
+
--zg-green:oklch(0.5 0.14 150);--zg-amber:oklch(0.55 0.13 70);--zg-red:oklch(0.52 0.19 25);
|
|
32
|
+
--zg-rim:inset 0 1px 0 oklch(1 0 0 / .95),inset 0 0 0 1px oklch(1 0 0 / .45),0 0 0 1px oklch(0.3 0.02 60 / .13);
|
|
33
|
+
--zg-shadow-1:0 8px 24px oklch(0.4 0.03 60 / .17);--zg-shadow-2:0 24px 64px oklch(0.4 0.03 60 / .22);
|
|
34
|
+
}
|
|
35
|
+
:root{--zg-z-bar:50;--zg-z-pop:60;--zg-z-modal:200;
|
|
36
|
+
--zg-font:'Manrope','Thmanyah Sans',system-ui,sans-serif;--zg-font-ar:'Thmanyah Sans',system-ui,sans-serif;--zg-font-ar-display:'Thmanyah Serif Display','Thmanyah Sans',serif;--zg-font-ar-text:'Thmanyah Serif Text','Thmanyah Sans',serif;--zg-font-mono:'JetBrains Mono',ui-monospace,monospace;
|
|
37
|
+
--zg-h-xs:28px;--zg-h-sm:32px;--zg-h-md:40px;--zg-h-lg:48px;--zg-h-xl:56px;
|
|
38
|
+
--zg-r-card:32px;--zg-r-tile:20px;--zg-ease:cubic-bezier(.2,.8,.2,1);
|
|
39
|
+
}
|
|
40
|
+
[dir="rtl"]{--zg-font:var(--zg-font-ar)}
|
|
41
|
+
|
|
42
|
+
/* Size scale. Adapters put data-size="xs|sm|md|lg|xl" on a component root when it has a size prop.
|
|
43
|
+
Component CSS must read these only inside its own [data-size] selector, never in default rules, so sizes do not leak into children. */
|
|
44
|
+
[data-size="xs"]{--zg-sz-h:28px;--zg-sz-fs:12px;--zg-sz-px:10px;--zg-sz-gap:6px;--zg-sz-icon:14px;--zg-sz-pad:10px}
|
|
45
|
+
[data-size="sm"]{--zg-sz-h:32px;--zg-sz-fs:13px;--zg-sz-px:12px;--zg-sz-gap:8px;--zg-sz-icon:16px;--zg-sz-pad:12px}
|
|
46
|
+
[data-size="md"]{--zg-sz-h:40px;--zg-sz-fs:14px;--zg-sz-px:16px;--zg-sz-gap:10px;--zg-sz-icon:18px;--zg-sz-pad:16px}
|
|
47
|
+
[data-size="lg"]{--zg-sz-h:48px;--zg-sz-fs:15px;--zg-sz-px:20px;--zg-sz-gap:12px;--zg-sz-icon:20px;--zg-sz-pad:20px}
|
|
48
|
+
[data-size="xl"]{--zg-sz-h:56px;--zg-sz-fs:16px;--zg-sz-px:24px;--zg-sz-gap:14px;--zg-sz-icon:22px;--zg-sz-pad:24px}
|
package/package.json
CHANGED
|
@@ -1,6 +1,120 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziyarago/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.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, 160+ components and 16 page templates.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ui",
|
|
7
|
+
"design-system",
|
|
8
|
+
"components",
|
|
9
|
+
"vue",
|
|
10
|
+
"react",
|
|
11
|
+
"glassmorphism",
|
|
12
|
+
"rtl",
|
|
13
|
+
"arabic",
|
|
14
|
+
"oklch",
|
|
15
|
+
"umrah",
|
|
16
|
+
"hajj",
|
|
17
|
+
"ziyarago",
|
|
18
|
+
"nuxt"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "ZiyaraGo <dev@ziyarago.com>",
|
|
23
|
+
"homepage": "https://ziyara.vip",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/ziyarago/ui.git"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/ziyarago/ui/issues"
|
|
30
|
+
},
|
|
31
|
+
"sideEffects": [
|
|
32
|
+
"**/*.css"
|
|
33
|
+
],
|
|
34
|
+
"main": "./src/core/index.js",
|
|
35
|
+
"module": "./src/core/index.js",
|
|
36
|
+
"types": "./types/core.d.ts",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./types/core.d.ts",
|
|
40
|
+
"default": "./src/core/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./vue": {
|
|
43
|
+
"types": "./types/vue.d.ts",
|
|
44
|
+
"default": "./src/vue/index.js"
|
|
45
|
+
},
|
|
46
|
+
"./react": {
|
|
47
|
+
"types": "./types/react.d.ts",
|
|
48
|
+
"default": "./src/react/index.js"
|
|
49
|
+
},
|
|
50
|
+
"./theme": {
|
|
51
|
+
"types": "./types/theme.d.ts",
|
|
52
|
+
"default": "./src/theme.js"
|
|
53
|
+
},
|
|
54
|
+
"./style.css": "./dist/style.css",
|
|
55
|
+
"./tokens.css": "./dist/tokens.css",
|
|
56
|
+
"./tokens.json": "./tokens/tokens.json",
|
|
57
|
+
"./assets/*": "./assets/*",
|
|
58
|
+
"./package.json": "./package.json",
|
|
59
|
+
"./nuxt": {
|
|
60
|
+
"types": "./types/nuxt.d.ts",
|
|
61
|
+
"default": "./src/nuxt/module.mjs"
|
|
62
|
+
},
|
|
63
|
+
"./manifest.json": "./dist/manifest.json",
|
|
64
|
+
"./llms.txt": "./dist/llms.txt",
|
|
65
|
+
"./llms-full.txt": "./dist/llms-full.txt",
|
|
66
|
+
"./meta": {
|
|
67
|
+
"default": "./src/meta/index.js"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"files": [
|
|
71
|
+
"dist",
|
|
72
|
+
"types",
|
|
73
|
+
"assets",
|
|
74
|
+
"tokens",
|
|
75
|
+
"README.md",
|
|
76
|
+
"LICENSE",
|
|
77
|
+
"CHANGELOG.md",
|
|
78
|
+
"THIRD_PARTY_NOTICES.md",
|
|
79
|
+
"src"
|
|
80
|
+
],
|
|
81
|
+
"scripts": {
|
|
82
|
+
"types": "node scripts/types.mjs",
|
|
83
|
+
"build": "node scripts/build.mjs && node scripts/types.mjs && node scripts/manifest.mjs",
|
|
84
|
+
"check": "node scripts/check.mjs && node scripts/check-meta.mjs",
|
|
85
|
+
"prepack": "npm run build && npm run check",
|
|
86
|
+
"pack:dry": "npm pack --dry-run"
|
|
87
|
+
},
|
|
88
|
+
"engines": {
|
|
89
|
+
"node": ">=18"
|
|
90
|
+
},
|
|
91
|
+
"peerDependencies": {
|
|
92
|
+
"@nuxt/kit": ">=3.0.0",
|
|
93
|
+
"react": ">=18",
|
|
94
|
+
"vue": ">=3.5"
|
|
95
|
+
},
|
|
96
|
+
"peerDependenciesMeta": {
|
|
97
|
+
"vue": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"react": {
|
|
101
|
+
"optional": true
|
|
102
|
+
},
|
|
103
|
+
"@nuxt/kit": {
|
|
104
|
+
"optional": true
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"publishConfig": {
|
|
108
|
+
"access": "public",
|
|
109
|
+
"provenance": true
|
|
110
|
+
},
|
|
111
|
+
"devDependencies": {
|
|
112
|
+
"@nuxt/kit": "^3.21.11",
|
|
113
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
114
|
+
"@semantic-release/exec": "^7.1.0",
|
|
115
|
+
"@semantic-release/git": "^10.0.1",
|
|
116
|
+
"conventional-changelog-conventionalcommits": "^9.3.1",
|
|
117
|
+
"semantic-release": "^24.2.9",
|
|
118
|
+
"vue": "^3.5.44"
|
|
119
|
+
}
|
|
120
|
+
}
|
package/src/catalog.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Source: ziyarago/glass-ui-kit src/data/componentCatalog.ts (ids + groups). Excluded by scope: AI, Restaurants, Logistics.
|
|
2
|
+
export const CATALOG = [["Accordion","Surfaces"],["AppShell","Layout"],["AuthLayout","Layout"],["Alert","Surfaces"],["Avatar","Data"],["Badge","Data"],["Breadcrumbs","Navigation"],["Button","Actions"],["Card","Surfaces"],["Checkbox","Forms"],["ChipInput","Forms"],["ConfirmDialog","Surfaces"],["ColorPicker","Forms"],["Combobox","Forms"],["CommandPalette","Actions"],["DatePicker","Forms"],["DashboardLayout","Layout"],["DataState","Surfaces"],["Drawer","Surfaces"],["Dropdown","Actions"],["EmptyState","Surfaces"],["FileUpload","Forms"],["Form","Forms"],["Kbd","Data"],["Input","Forms"],["NotificationBell","Actions"],["Menu","Actions"],["Modal","Surfaces"],["Navbar","Navigation"],["Pagination","Navigation"],["Page","Layout"],["MiniChart","Data"],["Progress","Surfaces"],["Radio","Forms"],["SegmentedControl","Forms"],["OtpInput","Forms"],["SearchInput","Forms"],["Select","Forms"],["Slider","Forms"],["Stepper","Navigation"],["Sidebar","Navigation"],["Skeleton","Surfaces"],["StatsCard","Data"],["KpiCard","Data"],["SubNav","Navigation"],["Switch","Forms"],["Table","Data"],["Tabs","Navigation"],["Textarea","Forms"],["Toast","Surfaces"],["Tooltip","Actions"],["CopyButton","Actions"],["Divider","Surfaces"],["Rating","Forms"],["Timeline","Data"],["GroupedSelect","Forms"],["HoverCard","Actions"],["LazyImage","Data"],["LoadingOverlay","Surfaces"],["LoginForm","Forms"],["Carousel","Surfaces"],["CommandBar","Actions"],["PhoneInput","Forms"],["TreeView","Data"],["TreeSelect","Forms"],["Banner","Surfaces"],["Code","Surfaces"],["ImageUpload","Forms"],["Spinner","Surfaces"],["MultiSelect","Forms"],["PasswordInput","Forms"],["ScrollArea","Layout"],["ToggleGroup","Actions"],["ContextMenu","Actions"],["InlineEdit","Forms"],["NumberInput","Forms"],["Popover","Actions"],["Tag","Data"],["TimePicker","Forms"],["Sortable","Data"],["CalendarView","Data"],["Fab","Actions"],["RichTextEditor","Forms"],["PricingCard","Surfaces"],["Kanban","Data"],["SettingsSection","Layout"],["SplitLayout","Layout"],["IntegrationCard","Surfaces"],["ErrorPage","Surfaces"],["IncomingCall","Surfaces"],["Screensaver","Surfaces"],["CallCard","Surfaces"],["AuthPages","Templates"],["SystemStatus","Data"],["CrmDashboard","Templates"],["TourBiDashboard","Templates"],["AdminBiDashboard","Templates"],["StepForm","Forms"],["Icons","Templates"],["Chart","Data"],["Gallery","Surfaces"],["Map","Data"],["Descriptions","Data"],["Popconfirm","Surfaces"],["ChatBubble","Surfaces"],["ConversationList","Surfaces"],["ChunkCard","Data"],["PillTab","Navigation"],["KpiInline","Data"],["ActionRail","Navigation"],["DealCard","CRM"],["PipelineBoard","CRM"],["LeadCard","CRM"],["ContactPanel","CRM"],["ActivityTimeline","CRM"],["SalesFunnel","CRM"],["BookingCard","Hotels"],["GuestCard","Hotels"],["RoomCalendar","Hotels"],["RoomGrid","Hotels"],["OccupancyChart","Hotels"]].map(([id, group]) => ({ id, group }));
|
|
3
|
+
// glass-ui-kit name → @ziyarago/ui name (when different)
|
|
4
|
+
export const ALIAS = { CrmDashboard: 'PageCrm', AuthPages: 'PageAuth', TourBiDashboard: 'PageDashboard', AdminBiDashboard: 'PageDashboard', Icons: 'IconTile', RichTextEditor: 'RichEditor', Input: 'TextField', KpiCard: 'KpiTile', Table: 'DataTable', Sidebar: 'SideNav', Navbar: 'TopNav', Page: 'PageHeader', DashboardLayout: 'AppShell', PillTab: 'SegmentedControl', ActivityTimeline: 'Timeline', Kanban: 'KanbanColumn', Radio: 'RadioGroup', OtpInput: 'CodeInput', Form: 'FormField', ImageUpload: 'FileUpload', Chart: 'MiniChart', Progress: 'Progress' };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// @ziyarago/ui · framework-neutral component definitions.
|
|
2
|
+
// Each component: { state?, render({ h, props, children, slots, state, set }) }.
|
|
3
|
+
// h(tag, props, ...children) is supplied by the Vue / React adapter. No hooks inside.
|
|
4
|
+
import { trap } from './dialog.js';
|
|
5
|
+
export const cx = (...a) => a.filter(Boolean).join(' ');
|
|
6
|
+
export const DZ = { xs: .82, sm: .91, md: 1, lg: 1.12, xl: 1.25 };
|
|
7
|
+
// Shared outside-click / Escape close for popups. Put `ref: ddRef(close)` on the popup element; its parent is the component root
|
|
8
|
+
// (trigger + popup). Listeners attach only in the browser, only while a popup is mounted, and detach when the last one unmounts.
|
|
9
|
+
const ddReg = [];
|
|
10
|
+
const ddPrune = () => { for (let i = ddReg.length - 1; i >= 0; i--) if (!ddReg[i].el.isConnected) ddReg.splice(i, 1); ddSync(); };
|
|
11
|
+
const ddDown = e => { ddPrune(); for (const r of ddReg.slice()) { const root = r.self ? r.el : (r.el.parentElement || r.el); if (!root.contains(e.target)) r.close(); } };
|
|
12
|
+
const ddKey = e => {
|
|
13
|
+
if (e.key !== 'Escape') return; ddPrune(); const r = ddReg[ddReg.length - 1]; if (!r) return;
|
|
14
|
+
const root = r.self ? r.el : (r.el.parentElement || r.el);
|
|
15
|
+
const t = [...root.querySelectorAll('button,input')].find(x => !r.el.contains(x));
|
|
16
|
+
const ae = document.activeElement; r.close(); if (t && !t.disabled && !(ae && root.contains(ae) && !r.el.contains(ae))) t.focus();
|
|
17
|
+
};
|
|
18
|
+
let ddOn = false;
|
|
19
|
+
const ddSync = () => {
|
|
20
|
+
if (typeof document === 'undefined') return;
|
|
21
|
+
if (ddReg.length && !ddOn) { document.addEventListener('pointerdown', ddDown, true); document.addEventListener('keydown', ddKey); ddOn = true; }
|
|
22
|
+
else if (!ddReg.length && ddOn) { document.removeEventListener('pointerdown', ddDown, true); document.removeEventListener('keydown', ddKey); ddOn = false; }
|
|
23
|
+
};
|
|
24
|
+
export const ddRef = (close, o = {}) => el => {
|
|
25
|
+
if (typeof document === 'undefined') return;
|
|
26
|
+
if (el) { const r = ddReg.find(x => x.el === el); if (r) { r.close = close; r.self = !!o.self; } else ddReg.push({ el, close, self: !!o.self }); ddSync(); }
|
|
27
|
+
else Promise.resolve().then(ddPrune);
|
|
28
|
+
};
|
|
29
|
+
export const dzp = (p, b) => Math.round(b * (DZ[p.size] || 1));
|
|
30
|
+
export const dzf = n => `calc(${n}px*var(--dz,1))`;
|
|
31
|
+
export const dzb = p => ({ xs: 'xs', sm: 'xs', md: 'sm', lg: 'md', xl: 'lg' }[p.size] || 'sm');
|
|
32
|
+
// Icons: Lucide SVGs fetched once, inlined as data-URI masks (works offline after first load; override the base to self-host).
|
|
33
|
+
export let ZG_ICON_BASE = 'https://unpkg.com/lucide-static@0.460.0/icons/';
|
|
34
|
+
export const setIconBase = b => { ZG_ICON_BASE = b.endsWith('/') ? b : b + '/'; };
|
|
35
|
+
const iconCache = {}, iconPending = {};
|
|
36
|
+
const iconUri = svg => 'url("data:image/svg+xml,' + encodeURIComponent(svg.replace(/stroke="currentColor"/g, 'stroke="black"').replace(/currentColor/g, 'black')) + '")';
|
|
37
|
+
const loadIcon = name => {
|
|
38
|
+
if (iconCache[name] || iconPending[name] || typeof document === 'undefined') return;
|
|
39
|
+
iconPending[name] = fetch(ZG_ICON_BASE + name + '.svg').then(r => r.text()).then(svg => {
|
|
40
|
+
iconCache[name] = iconUri(svg);
|
|
41
|
+
document.querySelectorAll('[data-zg-icon="' + name + '"]').forEach(el => el.style.setProperty('--zg-icon', iconCache[name]));
|
|
42
|
+
}).catch(() => {});
|
|
43
|
+
};
|
|
44
|
+
export const icon = (h, name, size) => { loadIcon(name); return h('span', { class: 'zg-icon', 'data-zg-icon': name, style: { ...(iconCache[name] ? { '--zg-icon': iconCache[name] } : {}), ...(size ? { width: size + 'px', height: size + 'px' } : {}) }, 'aria-hidden': 'true' }); };
|
|
45
|
+
const fmt = {
|
|
46
|
+
num: n => (n == null || Number.isNaN(n)) ? '—' : n === -1 ? '∞' : String(Math.round(n)).replace(/\B(?=(\d{3})+(?!\d))/g, '\u2009'),
|
|
47
|
+
sar: n => (n == null || Number.isNaN(n)) ? '—' : 'SAR\u00a0' + String(Math.round(n)).replace(/\B(?=(\d{3})+(?!\d))/g, '\u2009'),
|
|
48
|
+
dur: (s, u) => { if (s == null || Number.isNaN(+s)) return '—'; s = Math.max(0, Math.round(+s)); const m = Math.floor(s / 60), r = s % 60; return `${m} ${(u && u.m) || 'm'} ${String(r).padStart(2, '0')} ${(u && u.s) || 's'}`; },
|
|
49
|
+
};
|
|
50
|
+
const nav = (h, it, cur, onChange, collapsed) => h('button', { key: it.value, class: 'zg-navitem', 'aria-current': it.value === cur ? 'page' : undefined, title: collapsed ? it.label : undefined, onClick: () => onChange && onChange(it.value) },
|
|
51
|
+
it.icon ? icon(h, it.icon, 18) : null, h('span', null, it.label), it.count ? h('span', { class: 'zg-navitem__count' }, it.count) : null);
|
|
52
|
+
|
|
53
|
+
export const components = {
|
|
54
|
+
Button: { render: ({ h, props: p, children }) => h('button', { type: 'button', class: cx('zg-btn', 'zg-btn--' + (p.variant || 'secondary'), p.size && 'zg-btn--' + p.size, p.block && 'zg-btn--block', p.tone && 'zg-t-' + p.tone), disabled: p.disabled || p.loading, onClick: p.onClick },
|
|
55
|
+
p.loading ? h('span', { class: 'zg-spin' }) : p.icon ? icon(h, p.icon, 16) : null, ...children, p.iconRight ? icon(h, p.iconRight, 16) : null) },
|
|
56
|
+
IconButton: { render: ({ h, props: p }) => h('button', { type: 'button', 'aria-label': p.label, class: cx('zg-ibtn', p.size && 'zg-ibtn--' + p.size, p.ghost && 'zg-ibtn--ghost'), onClick: p.onClick }, icon(h, p.icon)) },
|
|
57
|
+
Badge: { render: ({ h, props: p, children }) => h('span', { class: cx('zg-badge', p.tone && 'zg-badge--' + p.tone) }, p.dot ? h('span', { class: 'zg-badge__dot' }) : null, ...children) },
|
|
58
|
+
Avatar: { render: ({ h, props: p }) => h('span', { class: 'zg-avatar', style: { '--s': ({ xs: 24, sm: 32, md: 40, lg: 56, xl: 72 }[p.size] || p.size || 40) + 'px' }, title: p.name }, p.src ? h('img', { src: p.src, alt: p.name || '' }) : (p.name || '?').split(/\s+/).slice(0, 2).map(w => w[0]).join('').toUpperCase()) },
|
|
59
|
+
Switch: { render: ({ h, props: p }) => h('button', { type: 'button', role: 'switch', class: 'zg-switch', 'aria-checked': String(!!p.modelValue), disabled: p.disabled, 'aria-disabled': p.disabled ? 'true' : undefined, onClick: () => !p.disabled && p.onChange && p.onChange(!p.modelValue) }, h('span', { class: 'zg-switch__track' }, h('span', { class: 'zg-switch__thumb' })), p.label || null) },
|
|
60
|
+
Checkbox: { render: ({ h, props: p }) => h('button', { type: 'button', role: 'checkbox', class: 'zg-check', 'aria-checked': String(!!p.modelValue), disabled: p.disabled, 'aria-disabled': p.disabled ? 'true' : undefined, onClick: () => !p.disabled && p.onChange && p.onChange(!p.modelValue) }, h('span', { class: 'zg-check__box' }, p.modelValue ? icon(h, 'check', 14) : null), p.label || null) },
|
|
61
|
+
SegmentedControl: { render: ({ h, props: p }) => h('div', { class: cx('zg-seg', p.size === 'sm' && 'zg-seg--sm'), role: 'group' }, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', 'aria-pressed': String(it.value === p.modelValue), onClick: () => p.onChange && p.onChange(it.value) }, it.label))) },
|
|
62
|
+
Tabs: { render: ({ h, props: p }) => h('div', { class: 'zg-tabs', role: 'tablist' }, ...(p.items || []).map(it => h('button', { key: it.value, role: 'tab', 'aria-selected': String(it.value === p.modelValue), onClick: () => p.onChange && p.onChange(it.value) }, it.label))) },
|
|
63
|
+
FilterChips: { render: ({ h, props: p }) => { const sel = p.multi ? (p.modelValue || []) : [p.modelValue]; return h('div', { class: 'zg-chips' }, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', class: 'zg-chip', 'aria-pressed': String(sel.includes(it.value)), onClick: () => { if (!p.onChange) return; p.onChange(p.multi ? (sel.includes(it.value) ? sel.filter(v => v !== it.value) : [...sel, it.value]) : it.value); } }, it.label))); } },
|
|
64
|
+
TextField: { render: ({ h, props: p }) => h('label', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null,
|
|
65
|
+
h('span', { class: cx('zg-input', p.error && 'zg-input--error', p.variant && 'zg-input--' + p.variant, p.density === 'compact' && 'zg-input--compact', p.disabled && 'zg-input--disabled') }, p.icon ? icon(h, p.icon, 18) : null, h('input', { type: p.type || 'text', value: p.modelValue ?? '', placeholder: p.placeholder, disabled: p.disabled, readOnly: p.readonly, onInput: e => p.onChange && p.onChange(e.target.value) }), p.suffix || null),
|
|
66
|
+
p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null) },
|
|
67
|
+
Select: { state: { open: false }, render: ({ h, props: p, state, set }) => { const cur = (p.options || []).find(o => o.value === p.modelValue), open = state.open && !p.disabled;
|
|
68
|
+
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null,
|
|
69
|
+
h('div', { class: 'zg-select' }, h('button', { type: 'button', class: 'zg-btn zg-select__trigger', 'aria-haspopup': 'listbox', 'aria-expanded': String(open), disabled: p.disabled, style: { height: 'var(--zg-h-lg)' }, onClick: () => set({ open: !state.open }) }, h('span', null, cur ? cur.label : p.placeholder || '—'), icon(h, open ? 'chevron-up' : 'chevron-down', 16)),
|
|
70
|
+
open ? h('div', { class: 'zg-select__list', role: 'listbox', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...(p.options || []).map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === p.modelValue), onClick: () => { set({ open: false }); p.onChange && p.onChange(o.value); } }, o.label, o.value === p.modelValue ? icon(h, 'check', 16) : null))) : null)); } },
|
|
71
|
+
CodeInput: { render: ({ h, props: p }) => { const n = p.length || 6, v = p.modelValue || '';
|
|
72
|
+
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] || '')),
|
|
73
|
+
h('input', { value: v, inputMode: 'numeric', autoComplete: 'one-time-code', maxLength: n, 'aria-label': p.label || 'Code', 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); } })); } },
|
|
74
|
+
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)])) },
|
|
75
|
+
Card: { 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'), 'data-theme': p.variant === 'inverse' ? 'dark' : (p.variant === 'gradient' && p.intensity === 'vivid' ? (['brand', 'warning', undefined].includes(p.tone) ? 'light' : 'dark') : undefined) },
|
|
76
|
+
(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) },
|
|
77
|
+
Modal: { render: ({ h, props: p, children, slots }) => { const foot = slots.footer || p.footer; return !p.open ? null : h('div', { class: 'zg-modal-scrim', onClick: p.onClose }, h('div', { class: 'zg-modal', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.title, ref: trap({ onClose: p.onClose }), onClick: e => e.stopPropagation() },
|
|
78
|
+
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' }, ...children), foot ? h('div', { class: 'zg-modal__foot' }, foot) : null)); } },
|
|
79
|
+
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() },
|
|
80
|
+
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' }, ...children), foot ? h('div', { class: 'zg-modal__foot' }, foot) : null)); } },
|
|
81
|
+
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))) },
|
|
82
|
+
Breadcrumbs: { render: ({ h, props: p }) => h('nav', { class: 'zg-crumbs', 'aria-label': 'Breadcrumb' }, ...(p.items || []).flatMap((it, i, a) => [i ? 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)])) },
|
|
83
|
+
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);
|
|
84
|
+
const set = new Set([1, total]); for (let i = cur - s; i <= cur + s; i++) if (i >= 1 && i <= total) set.add(i);
|
|
85
|
+
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); });
|
|
86
|
+
return h('nav', { class: 'zg-pager', 'aria-label': p.label || 'Pagination' }, h('button', { key: 'prev', type: 'button', class: 'zg-pager__nav', 'aria-label': 'Previous page', disabled: cur <= 1, onClick: () => go(cur - 1) }, icon(h, 'chevron-left', 16)),
|
|
87
|
+
...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': 'Page ' + n, 'aria-current': n === p.page ? 'page' : undefined, onClick: () => go(n) }, n)),
|
|
88
|
+
h('button', { key: 'next', type: 'button', class: 'zg-pager__nav', 'aria-label': 'Next page', disabled: cur >= total, onClick: () => go(cur + 1) }, icon(h, 'chevron-right', 16))); } },
|
|
89
|
+
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)) + '%' } }))) },
|
|
90
|
+
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)';
|
|
91
|
+
const r = (s - th) / 2 - (ticks ? 12 : 0), cir = 2 * Math.PI * r, tk = [];
|
|
92
|
+
if (ticks) for (let i = 0; i < 60; i++) { const an = i / 60 * 2 * Math.PI, R = s / 2 - 2, L = i % 5 ? 4 : 8; tk.push(h('line', { key: i, x1: +(s / 2 + Math.cos(an) * R).toFixed(2), y1: +(s / 2 + Math.sin(an) * R).toFixed(2), x2: +(s / 2 + Math.cos(an) * (R - L)).toFixed(2), y2: +(s / 2 + Math.sin(an) * (R - L)).toFixed(2), stroke: i / 60 <= v ? col : 'var(--zg-s3)', 'stroke-width': i % 5 ? 1 : 1.6, 'stroke-linecap': 'round' })); }
|
|
93
|
+
return h('div', { class: 'zg-ring', style: { width: s + 'px', height: s + 'px' }, role: 'img', 'aria-label': Math.round(v * 100) + '%' }, h('svg', { width: s, height: s, viewBox: `0 0 ${s} ${s}` }, ...tk,
|
|
94
|
+
h('circle', { cx: s / 2, cy: s / 2, r, fill: 'none', stroke: 'var(--zg-stroke)', 'stroke-width': th }),
|
|
95
|
+
h('circle', { cx: s / 2, cy: s / 2, r, fill: 'none', stroke: col, 'stroke-width': th, 'stroke-linecap': 'round', 'stroke-dasharray': cir, 'stroke-dashoffset': cir * (1 - v), style: { filter: 'drop-shadow(0 0 10px color-mix(in oklch, ' + col + ' 45%, transparent))', transition: 'stroke-dashoffset 1.4s cubic-bezier(.2,.8,.2,1)' } })),
|
|
96
|
+
h('div', { class: 'zg-ring__c' }, h('span', { class: 'zg-ring__v', style: { fontSize: (p.valueSize || s * .22) + 'px' } }, Math.round(v * 100), h('span', { style: { fontSize: '.45em', color: 'var(--zg-text-3)' } }, '%')), p.label ? h('span', { class: 'zg-ring__l' }, p.label) : null)); } },
|
|
97
|
+
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) },
|
|
98
|
+
DataTable: { render: ({ h, props: p }) => { const cols = p.columns || [], rows = p.rows || [], sel = p.selected || [], all = rows.length && rows.every(r => sel.includes(r.id));
|
|
99
|
+
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 }, 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);
|
|
100
|
+
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) || 'Select all', 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))),
|
|
101
|
+
h('tbody', null, ...(rows.length ? rows.map(r => h('tr', { key: r.id, 'aria-selected': sel.includes(r.id) ? 'true' : 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) || 'Select row', 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 }, 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' }, p.empty || '—')))])))); } },
|
|
102
|
+
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) },
|
|
103
|
+
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) },
|
|
104
|
+
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))))) },
|
|
105
|
+
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': 'Main' }, ...(p.items || []).map(it => nav(h, it, p.value, p.onChange))), h('div', { class: 'zg-topnav__actions' }, slots.actions || p.actions || null)) },
|
|
106
|
+
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,
|
|
107
|
+
p.onToggle ? h('button', { type: 'button', class: 'zg-navitem', onClick: p.onToggle, 'aria-label': 'Toggle sidebar' }, icon(h, p.collapsed ? 'panel-left-open' : 'panel-left-close', 18), h('span', null, p.collapseLabel || 'Collapse')) : null) },
|
|
108
|
+
AppShell: { state: { collapsed: false }, render: ({ C, h, props: p, children, slots, state, set }) => {
|
|
109
|
+
const brand = p.brand, top = (p.nav || 'top') === 'top', col = p.collapsed ?? state.collapsed, toggle = () => p.onCollapse ? p.onCollapse(!col) : set({ collapsed: !state.collapsed });
|
|
110
|
+
return h('div', { class: cx('zg-root', 'zg-shell', top && 'zg-shell--top', p.frame && 'zg-shell--frame'), style: { background: 'var(--zg-bg)' } },
|
|
111
|
+
top ? h(C.TopNav, { brand, items: p.items, value: p.value, onChange: p.onChange, actions: p.actions }, null) : h(C.SideNav, { brand, items: p.items, value: p.value, onChange: p.onChange, collapsed: col, onToggle: p.collapsible !== false ? toggle : undefined, footer: slots.footer, collapseLabel: p.collapseLabel }),
|
|
112
|
+
h('main', { class: 'zg-shell__main' }, ...children)); } },
|
|
113
|
+
PageHeader: { render: ({ C, h, props: p, slots }) => h('div', { class: 'zg-page-head' }, h('div', { style: { flex: 1, minWidth: 0 } }, p.crumbs ? h(C.Breadcrumbs, { items: p.crumbs }) : null, h('h1', null, p.title), p.subtitle ? h('p', null, p.subtitle) : null), slots.actions || null) },
|
|
114
|
+
AuthLayout: { render: ({ h, props: p, children }) => h('div', { class: 'zg-root zg-auth', style: p.style }, h('div', { class: 'zg-auth__card' }, p.logo || null, h('div', null, h('h1', { style: { margin: 0, font: '300 30px 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)) },
|
|
115
|
+
LeadCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-lead zg-dm' }, h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.name, size: dzp(p, 32) }), h('div', { style: { flex: 1, minWidth: 0 } }, h('b', { style: { fontSize: dzf(14) } }, p.name), h('div', { class: 'zg-hint zg-mono' }, p.code)), p.tag ? h('span', { class: cx('zg-badge', p.tone && 'zg-badge--' + p.tone) }, p.tag) : null), h('div', { class: 'zg-lead__row', style: { fontSize: dzf(13), color: 'var(--zg-text-2)' } }, icon(h, 'phone', dzp(p, 14)), p.phone, h('span', { class: 'zg-num', style: { marginInlineStart: 'auto', fontSize: dzf(15), fontWeight: 600, color: 'var(--zg-text-1)' } }, p.amount))) },
|
|
116
|
+
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' },
|
|
117
|
+
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)),
|
|
118
|
+
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))))) ) },
|
|
119
|
+
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: 'Seats', meta: `${booked}/${seats}` }), slots.actions || null); } },
|
|
120
|
+
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)); })) },
|
|
121
|
+
RamadanTracker: { render: ({ h, props: p }) => { const day = Number(p.day) || 0; return h('div', { class: 'zg-rt zg-dm', role: 'img', 'aria-label': day ? `Day ${day} of 30` : 'Ramadan tracker' }, ...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) }); })); } },
|
|
122
|
+
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) || 'Play', size: dzb(p), onClick: p.onPlay })) },
|
|
123
|
+
};
|
|
124
|
+
export { fmt };
|