@ziyarago/ui 0.14.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +5 -5
- package/dist/llms-full.txt +8 -4
- package/dist/llms.txt +2 -2
- package/dist/manifest.json +343 -4
- package/dist/style.css +13 -13
- package/docs/INTEGRATION.md +12 -0
- package/package.json +12 -25
- package/src/core/components.js +11 -15
- package/src/core/components2.js +2 -72
- package/src/core/components3.js +2 -44
- package/src/core/components4.js +0 -38
- package/src/core/components6.js +0 -50
- package/src/core/components7.js +0 -34
- package/src/core/index.js +2 -2
- package/src/core/pages.js +2 -36
- package/src/i18n/components/index.js +0 -72
- package/src/icons/builtin.js +99 -0
- package/src/internal.js +10 -0
- package/src/meta/forms.js +5 -1
- package/src/meta/index.js +2 -0
- package/src/meta/pages.js +1 -1
- package/src/react/index.js +1 -107
- package/src/styles/components.css +8 -8
- package/src/styles/components2.css +2 -2
- package/src/styles/components7.css +1 -1
- package/src/styles/components8.css +1 -1
- package/src/styles/extras.css +1 -1
- package/src/vue/index.js +2 -2
- package/types/core.d.ts +1 -1
- package/types/internal.d.ts +30 -0
- package/types/meta.d.ts +5 -0
- package/types/react.d.ts +154 -4141
- package/types/shared.d.ts +2 -0
- package/types/vue-global.d.ts +0 -106
- package/types/vue.d.ts +168 -3601
- package/docs/MIGRATION.md +0 -455
- package/src/compat/index.js +0 -469
- package/src/compat/map.js +0 -282
- package/src/compat/resolver.js +0 -22
- package/src/compat/vite.js +0 -29
- package/src/i18n/components/ActionCard.js +0 -5
- package/src/i18n/components/Actionsheet.js +0 -5
- package/src/i18n/components/CallCard.js +0 -5
- package/src/i18n/components/CallRow.js +0 -5
- package/src/i18n/components/Carousel.js +0 -5
- package/src/i18n/components/ChartCard.js +0 -5
- package/src/i18n/components/ChatAi.js +0 -5
- package/src/i18n/components/ChipInput.js +0 -5
- package/src/i18n/components/ContactPanel.js +0 -5
- package/src/i18n/components/DataGroup.js +0 -5
- package/src/i18n/components/DealCard.js +0 -5
- package/src/i18n/components/FullscreenDialog.js +0 -5
- package/src/i18n/components/Gallery.js +0 -5
- package/src/i18n/components/GuestCard.js +0 -5
- package/src/i18n/components/ImageUpload.js +0 -5
- package/src/i18n/components/ImageViewer.js +0 -5
- package/src/i18n/components/IncomingCall.js +0 -5
- package/src/i18n/components/InlineEdit.js +0 -5
- package/src/i18n/components/IntegrationCard.js +0 -5
- package/src/i18n/components/Map.js +0 -5
- package/src/i18n/components/NotificationBell.js +0 -5
- package/src/i18n/components/PricingCard.js +0 -5
- package/src/i18n/components/QiblaCompass.js +0 -5
- package/src/i18n/components/RamadanTracker.js +0 -5
- package/src/i18n/components/RangeCalendar.js +0 -5
- package/src/i18n/components/RangeSlider.js +0 -5
- package/src/i18n/components/RichEditor.js +0 -5
- package/src/i18n/components/Screensaver.js +0 -5
- package/src/i18n/components/Sortable.js +0 -5
- package/src/i18n/components/StepDialog.js +0 -5
- package/src/i18n/components/StepForm.js +0 -5
- package/src/i18n/components/TabTiles.js +0 -5
- package/src/i18n/components/Tour.js +0 -5
- package/src/i18n/components/TourCard.js +0 -5
- package/src/i18n/components/TourPopover.js +0 -5
- package/src/i18n/components/TreeSelect.js +0 -5
- package/types/compat-resolver.d.ts +0 -12
- package/types/compat-vite.d.ts +0 -11
- package/types/compat.d.ts +0 -176
package/dist/style.css
CHANGED
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
:where(.zg-root,[class*="zg-"]) :where(button,input,select,textarea),:where(button,input,select,textarea):where([class*="zg-"]){font:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0;background-color:transparent}
|
|
91
91
|
:where(.zg-root,[class*="zg-"]) a:where(:not([class])){text-decoration:underline}
|
|
92
92
|
:where(.zg-root,[class*="zg-"]) svg,svg:where([class*="zg-"]){vertical-align:middle}
|
|
93
|
-
.zg-glass{background:var(--zg-s1)
|
|
93
|
+
.zg-glass{background:var(--zg-s1);-webkit-backdrop-filter:blur(24px) saturate(1.5);backdrop-filter:blur(24px) saturate(1.5);box-shadow:var(--zg-rim),var(--zg-shadow-1)}
|
|
94
94
|
.zg-icon:not([style*="--zg-icon"]){visibility:hidden}
|
|
95
95
|
.zg-icon{display:inline-block;flex:none;width:1em;height:1em;background:currentColor;-webkit-mask:var(--zg-icon) center/contain no-repeat;mask:var(--zg-icon) center/contain no-repeat}
|
|
96
96
|
.zg-mono{font-family:var(--zg-font-mono);font-variant-numeric:tabular-nums}
|
|
@@ -153,7 +153,7 @@
|
|
|
153
153
|
.zg-hint{font-size:12px;color:var(--zg-text-3)}.zg-hint--error{color:var(--zg-red)}
|
|
154
154
|
.zg-select{position:relative}
|
|
155
155
|
.zg-select__trigger{width:100%;justify-content:space-between;text-align:start}
|
|
156
|
-
.zg-select__list{position:absolute;z-index:40;inset-inline:0;top:calc(100% + 6px);padding:6px;border-radius:20px;background:var(--zg-glass)
|
|
156
|
+
.zg-select__list{position:absolute;z-index:40;inset-inline:0;top:calc(100% + 6px);padding:6px;border-radius:20px;background:var(--zg-glass);-webkit-backdrop-filter:blur(40px);backdrop-filter:blur(40px);box-shadow:var(--zg-rim),var(--zg-shadow-2);max-height:280px;overflow:auto}
|
|
157
157
|
.zg-select__opt{display:flex;align-items:center;justify-content:space-between;width:100%;height:40px;padding:0 14px;border:0;border-radius:14px;cursor:pointer;background:none;color:var(--zg-text-1);font:500 14px var(--zg-font);text-align:start}
|
|
158
158
|
.zg-select__opt:hover{background:var(--zg-s2)}.zg-select__opt[aria-selected="true"]{color:var(--zg-brand-text);font-weight:700}
|
|
159
159
|
.zg-code{position:relative;display:grid;grid-template-columns:repeat(var(--n),minmax(0,1fr));gap:8px;direction:ltr}
|
|
@@ -171,8 +171,8 @@
|
|
|
171
171
|
.zg-card{border-radius:var(--zg-r-card);padding:20px;display:flex;flex-direction:column;gap:14px;min-width:0}
|
|
172
172
|
.zg-card--inverse{background:linear-gradient(150deg,oklch(0.285 0.022 239.4),oklch(0.2 0.012 240));color:oklch(0.97 0.004 70)}
|
|
173
173
|
.zg-card__head{display:flex;align-items:flex-start;gap:12px}.zg-card__title{font:600 18px var(--zg-font);margin:0}.zg-card__sub{font-size:13px;color:var(--zg-text-3)}
|
|
174
|
-
.zg-modal-scrim{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;padding:16px;background:oklch(0.12 0.005 40 / .5)
|
|
175
|
-
.zg-modal{width:100%;max-width:520px;max-height:92vh;display:flex;flex-direction:column;border-radius:32px;overflow:hidden;background:var(--zg-glass)
|
|
174
|
+
.zg-modal-scrim{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;padding:16px;background:oklch(0.12 0.005 40 / .5);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}
|
|
175
|
+
.zg-modal{width:100%;max-width:520px;max-height:92vh;display:flex;flex-direction:column;border-radius:32px;overflow:hidden;background:var(--zg-glass);-webkit-backdrop-filter:blur(40px) saturate(1.6);backdrop-filter:blur(40px) saturate(1.6);box-shadow:var(--zg-rim),var(--zg-shadow-2);color:var(--zg-text-1)}
|
|
176
176
|
.zg-modal__head{display:flex;align-items:center;gap:12px;padding:20px 20px 16px;box-shadow:0 1px 0 var(--zg-stroke)}.zg-modal__head h3{flex:1;margin:0;font:600 18px var(--zg-font)}
|
|
177
177
|
.zg-modal__body{padding:20px;overflow:auto;display:flex;flex-direction:column;gap:16px}
|
|
178
178
|
.zg-modal__foot{display:flex;justify-content:flex-end;gap:8px;padding:14px 20px 18px;box-shadow:0 -1px 0 var(--zg-stroke)}
|
|
@@ -212,14 +212,14 @@
|
|
|
212
212
|
/* Navigation */
|
|
213
213
|
.zg-crumbs{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--zg-text-3)}.zg-crumbs a{color:var(--zg-text-2);text-decoration:none}.zg-crumbs a:hover{color:var(--zg-text-1)}.zg-crumbs b{color:var(--zg-text-1)}
|
|
214
214
|
.zg-pager{display:inline-flex;gap:4px}.zg-pager button{min-width:36px;height:36px;border:0;border-radius:999px;cursor:pointer;font:600 13px var(--zg-font);background:transparent;color:var(--zg-text-2)}.zg-pager button[aria-current="page"]{background:var(--zg-inv-bg);color:var(--zg-inv-fg)}
|
|
215
|
-
.zg-topnav{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:12px;padding:10px clamp(10px,2vw,20px);background:color-mix(in oklch,var(--zg-bg) 62%,transparent)
|
|
215
|
+
.zg-topnav{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:12px;padding:10px clamp(10px,2vw,20px);background:color-mix(in oklch,var(--zg-bg) 62%,transparent);-webkit-backdrop-filter:blur(28px) saturate(1.6);backdrop-filter:blur(28px) saturate(1.6);box-shadow:0 1px 0 var(--zg-stroke)}
|
|
216
216
|
.zg-topnav__brand{display:flex;align-items:center;gap:10px;font:700 18px var(--zg-font);flex:none}
|
|
217
217
|
.zg-topnav__items{margin-inline-start:auto;display:flex;gap:2px;padding:4px;border-radius:999px;background:var(--zg-s2);box-shadow:var(--zg-rim);overflow-x:auto;scrollbar-width:none;min-width:0}
|
|
218
218
|
.zg-topnav__actions{display:flex;align-items:center;gap:8px;flex:none}
|
|
219
219
|
.zg-navitem{display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 14px;border:0;border-radius:999px;cursor:pointer;font:600 14px var(--zg-font);white-space:nowrap;background:transparent;color:var(--zg-text-2);transition:background .2s}
|
|
220
220
|
.zg-navitem:hover{background:var(--zg-s2)}.zg-navitem[aria-current="page"]{background:var(--zg-inv-bg);color:var(--zg-inv-fg)}
|
|
221
221
|
.zg-navitem__count{height:20px;min-width:20px;padding:0 6px;border-radius:999px;display:grid;place-items:center;font:700 11px var(--zg-font);background:var(--zg-brand);color:var(--zg-on-brand)}
|
|
222
|
-
.zg-sidenav{--w:272px;position:sticky;top:0;height:100vh;width:var(--w);flex:none;display:flex;flex-direction:column;gap:6px;padding:16px 12px;background:color-mix(in oklch,var(--zg-bg) 62%,transparent)
|
|
222
|
+
.zg-sidenav{--w:272px;position:sticky;top:0;height:100vh;width:var(--w);flex:none;display:flex;flex-direction:column;gap:6px;padding:16px 12px;background:color-mix(in oklch,var(--zg-bg) 62%,transparent);-webkit-backdrop-filter:blur(40px) saturate(1.6);backdrop-filter:blur(40px) saturate(1.6);box-shadow:inset -1px 0 0 var(--zg-stroke);transition:width .3s var(--zg-ease);overflow:hidden}
|
|
223
223
|
[dir="rtl"] .zg-sidenav{box-shadow:inset 1px 0 0 var(--zg-stroke)}
|
|
224
224
|
.zg-sidenav--collapsed{--w:76px}
|
|
225
225
|
.zg-sidenav .zg-navitem{width:100%;height:46px;border-radius:16px;justify-content:flex-start;padding:0 14px}
|
|
@@ -229,10 +229,10 @@
|
|
|
229
229
|
.zg-sidenav__sp{flex:1}.zg-sidenav__group{padding:14px 14px 4px;font:700 11px var(--zg-font);letter-spacing:.1em;text-transform:uppercase;color:var(--zg-text-3)}
|
|
230
230
|
.zg-shell{min-height:100vh;display:flex}.zg-shell--top{flex-direction:column}
|
|
231
231
|
.zg-shell__main{flex:1;min-width:0;max-width:1440px;width:100%;margin:0 auto;padding:clamp(16px,2.4vw,32px);display:flex;flex-direction:column;gap:20px}
|
|
232
|
-
.zg-shell__bar{display:none;position:sticky;top:0;z-index:50;align-items:center;gap:12px;padding:10px 16px;background:color-mix(in oklch,var(--zg-bg) 62%,transparent)
|
|
232
|
+
.zg-shell__bar{display:none;position:sticky;top:0;z-index:50;align-items:center;gap:12px;padding:10px 16px;background:color-mix(in oklch,var(--zg-bg) 62%,transparent);-webkit-backdrop-filter:blur(28px);backdrop-filter:blur(28px)}
|
|
233
233
|
@media(max-width:820px){.zg-shell:not(.zg-shell--top){flex-direction:column}.zg-shell:not(.zg-shell--top) .zg-sidenav{display:none}.zg-shell__bar{display:flex}}
|
|
234
234
|
.zg-page-head{display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap}.zg-page-head h1{margin:0;font:300 clamp(28px,3vw,40px) var(--zg-font);letter-spacing:-.03em}.zg-page-head p{margin:4px 0 0;color:var(--zg-text-3);font-size:15px}
|
|
235
|
-
.zg-auth{min-height:100vh;display:grid;place-items:center;padding:24px}.zg-auth__card{width:100%;max-width:440px;padding:clamp(24px,3vw,36px);border-radius:36px;display:flex;flex-direction:column;gap:22px;background:color-mix(in oklch,var(--zg-bg) 55%,transparent)
|
|
235
|
+
.zg-auth{min-height:100vh;display:grid;place-items:center;padding:24px}.zg-auth__card{width:100%;max-width:440px;padding:clamp(24px,3vw,36px);border-radius:36px;display:flex;flex-direction:column;gap:22px;background:color-mix(in oklch,var(--zg-bg) 55%,transparent);-webkit-backdrop-filter:blur(28px) saturate(1.6);backdrop-filter:blur(28px) saturate(1.6);box-shadow:var(--zg-rim),var(--zg-shadow-1)}
|
|
236
236
|
|
|
237
237
|
.zg-shell--frame{min-height:0;height:100%;overflow:auto}.zg-shell--frame .zg-sidenav{height:100%;position:relative}.zg-shell--frame .zg-topnav{position:sticky}
|
|
238
238
|
|
|
@@ -268,7 +268,7 @@
|
|
|
268
268
|
.zg-es{display:flex;flex-direction:column;align-items:center;gap:8px;padding:32px 16px;text-align:center;color:var(--zg-text-2)}.zg-es b{color:var(--zg-text-1);font-size:17px}
|
|
269
269
|
.zg-es__ico{width:64px;height:64px;border-radius:22px;display:grid;place-items:center;background:var(--zg-brand-soft);color:var(--zg-brand-text);box-shadow:var(--zg-rim)}.zg-es__img{width:112px;height:112px;background:center/contain no-repeat}
|
|
270
270
|
.zg-es__a{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-top:8px}.zg-es--page{min-height:60vh;justify-content:center}
|
|
271
|
-
.zg-lo{position:relative}.zg-lo__o{position:absolute;inset:0;z-index:5;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;border-radius:inherit;background:color-mix(in oklch,var(--zg-bg) 55%,transparent)
|
|
271
|
+
.zg-lo{position:relative}.zg-lo__o{position:absolute;inset:0;z-index:5;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;border-radius:inherit;background:color-mix(in oklch,var(--zg-bg) 55%,transparent);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}
|
|
272
272
|
.zg-car{position:relative;border-radius:28px;overflow:hidden;box-shadow:var(--zg-rim),var(--zg-shadow-1)}.zg-car__s{aspect-ratio:16/8;background:var(--zg-s3) center/cover;display:flex;align-items:flex-end}
|
|
273
273
|
.zg-car__c{width:100%;padding:44px 20px 18px;color:#fff;display:flex;flex-direction:column;gap:2px;background:linear-gradient(transparent,oklch(0.1 0 0 / .7))}
|
|
274
274
|
.zg-car__p,.zg-car__n{position:absolute;top:50%;margin-top:-16px}.zg-car__p{inset-inline-start:12px}.zg-car__n{inset-inline-end:12px}
|
|
@@ -280,7 +280,7 @@
|
|
|
280
280
|
.zg-code-b pre{margin:0;padding:16px;overflow:auto;font-size:13px;line-height:1.6;direction:ltr;text-align:left}
|
|
281
281
|
.zg-price--on{box-shadow:var(--zg-rim),0 0 0 2px var(--zg-brand),var(--zg-shadow-1)}.zg-feat{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:10px;font-size:14px}.zg-feat li{display:flex;gap:10px;align-items:center;color:var(--zg-text-2)}.zg-feat .zg-icon{color:var(--zg-green)}
|
|
282
282
|
.zg-inc{display:flex;align-items:center;gap:14px;padding:14px 16px;border-radius:28px;max-width:440px}.zg-inc__ok{background:var(--zg-green);color:#fff;box-shadow:0 8px 24px color-mix(in oklch,var(--zg-green) 45%,transparent)}
|
|
283
|
-
.zg-dd{position:relative;display:inline-block}.zg-menu{position:absolute;z-index:40;inset-inline-start:0;top:calc(100% + 6px);min-width:220px;padding:6px;border-radius:20px;background:var(--zg-glass)
|
|
283
|
+
.zg-dd{position:relative;display:inline-block}.zg-menu{position:absolute;z-index:40;inset-inline-start:0;top:calc(100% + 6px);min-width:220px;padding:6px;border-radius:20px;background:var(--zg-glass);-webkit-backdrop-filter:blur(40px);backdrop-filter:blur(40px);box-shadow:var(--zg-rim),var(--zg-shadow-2);display:flex;flex-direction:column}
|
|
284
284
|
.zg-menu--static{position:static;display:inline-flex}.zg-menu__i{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:10px;min-height:40px;padding:0 12px;border-radius:14px;cursor:pointer;font:500 14px var(--zg-font);color:var(--zg-text-1)}
|
|
285
285
|
.zg-menu__i:hover{background:var(--zg-s2)}.zg-menu__i--danger{color:var(--zg-red)}.zg-menu__i .zg-kbd{margin-inline-start:auto}.zg-menu__sep{height:1px;margin:4px 8px;background:var(--zg-stroke)}
|
|
286
286
|
.zg-kbd{display:inline-grid;place-items:center;min-width:22px;height:22px;padding:0 6px;border-radius:7px;font:600 11px var(--zg-font-mono);color:var(--zg-text-2);background:var(--zg-s2);box-shadow:var(--zg-rim),0 1px 0 var(--zg-stroke)}
|
|
@@ -424,7 +424,7 @@
|
|
|
424
424
|
.zg-link:hover{text-decoration-color:currentColor}.zg-link:focus-visible{outline:2px solid var(--zg-brand);outline-offset:2px}.zg-link--plain{text-decoration:none}
|
|
425
425
|
.zg-press{all:unset;box-sizing:border-box;cursor:pointer;display:inline-flex;align-items:center;gap:8px;border-radius:12px;-webkit-tap-highlight-color:transparent}
|
|
426
426
|
.zg-press:active{opacity:.7}.zg-press:focus-visible{outline:2px solid var(--zg-brand);outline-offset:2px}.zg-press[disabled]{opacity:.45;cursor:not-allowed}
|
|
427
|
-
.zg-liquid{position:relative;border-radius:var(--zg-liquid-r,24px);padding:20px;background:linear-gradient(135deg,color-mix(in oklch,var(--zg-text-1) 14%,transparent),color-mix(in oklch,var(--zg-text-1) 4%,transparent))
|
|
427
|
+
.zg-liquid{position:relative;border-radius:var(--zg-liquid-r,24px);padding:20px;background:linear-gradient(135deg,color-mix(in oklch,var(--zg-text-1) 14%,transparent),color-mix(in oklch,var(--zg-text-1) 4%,transparent));-webkit-backdrop-filter:blur(var(--zg-liquid-b,18px)) saturate(1.8);backdrop-filter:blur(var(--zg-liquid-b,18px)) saturate(1.8);box-shadow:inset 0 1px 0 color-mix(in oklch,var(--zg-text-1) 30%,transparent),inset 0 -1px 0 color-mix(in oklch,var(--zg-text-1) 8%,transparent),0 12px 32px oklch(0 0 0/.25);border:1px solid var(--zg-stroke);overflow:hidden}
|
|
428
428
|
.zg-sheet-scrim{align-items:flex-end;justify-content:center;padding:0}
|
|
429
429
|
.zg-sheet{width:min(560px,100%);display:flex;flex-direction:column;gap:12px;padding:10px 20px 20px;border-radius:28px 28px 0 0;background:var(--zg-s1);box-shadow:var(--zg-rim),var(--zg-shadow-1);overflow:auto;animation:zg-sheet-in .22s ease-out}
|
|
430
430
|
.zg-sheet__grab{align-self:center;width:40px;height:4px;border-radius:2px;background:var(--zg-stroke);flex:none;margin-block:2px 4px}
|
|
@@ -536,7 +536,7 @@
|
|
|
536
536
|
.zg-ash__proofT{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;margin-inline-start:10px}.zg-ash__proofT span{color:var(--zg-text-3);font-size:13px}
|
|
537
537
|
.zg-ash--light .zg-ash__grid>.zg-ash__media,.zg-ash--tint .zg-ash__grid>.zg-ash__media{border-radius:var(--ash-r);min-height:520px;display:flex;flex-direction:column;justify-content:flex-end;box-shadow:var(--zg-rim)}
|
|
538
538
|
.zg-ash__mh{position:absolute;inset-inline:0;top:0;margin:0;padding:clamp(20px,3vw,36px);font:400 clamp(24px,2.8vw,34px)/1.2 var(--zg-font);color:#fff;text-shadow:0 1px 16px rgb(0 0 0/.25)}
|
|
539
|
-
.zg-ash__glass{position:relative;margin:12px;padding:18px;border-radius:calc(var(--ash-r) - 8px);display:flex;flex-direction:column;gap:12px;background:color-mix(in oklch,var(--zg-bg) 38%,transparent)
|
|
539
|
+
.zg-ash__glass{position:relative;margin:12px;padding:18px;border-radius:calc(var(--ash-r) - 8px);display:flex;flex-direction:column;gap:12px;background:color-mix(in oklch,var(--zg-bg) 38%,transparent);-webkit-backdrop-filter:blur(22px) saturate(1.4);backdrop-filter:blur(22px) saturate(1.4);box-shadow:var(--zg-rim);color:var(--zg-text-1)}
|
|
540
540
|
.zg-ash__glass p{margin:0;color:var(--zg-text-2)}
|
|
541
541
|
.zg-ash__chips{display:flex;gap:10px;flex-wrap:wrap}
|
|
542
542
|
.zg-ash__chip{flex:1;text-align:center;padding:14px 18px;border-radius:999px;box-shadow:inset 0 0 0 1px color-mix(in oklch,var(--zg-text-1) 40%,transparent)}
|
|
@@ -1413,7 +1413,7 @@
|
|
|
1413
1413
|
.zg-avatar__st{position:absolute;inset-inline-end:0;bottom:0;width:max(8px,calc(var(--s)*.26));height:max(8px,calc(var(--s)*.26));border-radius:50%;background:var(--zg-text-3);box-shadow:0 0 0 2px var(--zg-bg)}
|
|
1414
1414
|
.zg-avatar__st--online{background:var(--zg-green)}.zg-avatar__st--away{background:var(--zg-amber)}.zg-avatar__st--busy{background:var(--zg-red)}
|
|
1415
1415
|
.zg-avatar--sky{background:var(--zg-sky);color:var(--zg-bg)}.zg-avatar--success{background:var(--zg-green);color:var(--zg-bg)}.zg-avatar--warning{background:var(--zg-amber);color:var(--zg-bg)}.zg-avatar--danger{background:var(--zg-red);color:var(--zg-bg)}.zg-avatar--neutral{background:var(--zg-s3);color:var(--zg-text-1)}
|
|
1416
|
-
.zg-card.zg-card--outline{background:transparent
|
|
1416
|
+
.zg-card.zg-card--outline{background:transparent;-webkit-backdrop-filter:none;backdrop-filter:none;box-shadow:inset 0 0 0 1px var(--zg-stroke)}
|
|
1417
1417
|
.zg-card.zg-card--flush{padding:0}
|
|
1418
1418
|
.zg-card--click{cursor:pointer;transition:transform .2s var(--zg-ease)}.zg-card--click:hover{transform:translateY(-2px)}.zg-card--click:focus-visible{outline:2px solid var(--zg-brand);outline-offset:2px}
|
|
1419
1419
|
.zg-card__foot{display:flex;align-items:center;gap:8px;padding-top:14px;box-shadow:0 -1px 0 var(--zg-stroke)}
|
package/docs/INTEGRATION.md
CHANGED
|
@@ -162,3 +162,15 @@ import '@ziyarago/ui-pro/style.css' // plugin css, always after style.css
|
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
Plugin CSS must only use the public design tokens (`--zg-*`, see `@ziyarago/ui/tokens.css`) and its own prefixed classes. Auto-import: pass `plugins: ['@ziyarago/ui-pro']` to the Nuxt module options (`ziyarago: { plugins: [...] }`) or to `ZiyaragoUiResolver({ plugins: [...] })`.
|
|
165
|
+
|
|
166
|
+
## Migrating from local copies of glass-ui-kit
|
|
167
|
+
The migration tooling (compat layer with the old component names, resolver and Vite plugin, step by step guide) is part of `@ziyarago/ui-pro`.
|
|
168
|
+
|
|
169
|
+
## 9. Icons and CSP
|
|
170
|
+
The icons the components use themselves (about 100: chevrons, close, search, eye, calendar ...) are built into the package and load without a network request, so they work under `connect-src 'self'`. Icons you pass by name that are not in that set (`<UiButton icon="rocket">`) are fetched once from `https://unpkg.com/lucide-static@0.460.0/icons/`. Under a strict CSP either self-host them or register them:
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
import { setIconBase, registerIcons } from '@ziyarago/ui/vue';
|
|
174
|
+
setIconBase('/icons/'); // copy node_modules/lucide-static/icons to public/icons
|
|
175
|
+
registerIcons({ 'my-logo': '<svg viewBox="0 0 24 24" ...>...</svg>' }); // or inline SVG strings, no request at all
|
|
176
|
+
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziyarago/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "ZiyaraGo UI — glass design system for Umrah and Hajj products. OKLCH tokens, two palettes, dark and light, RTL, Thmanyah. One framework-neutral core with Vue 3, Nuxt and React adapters,
|
|
3
|
+
"version": "0.15.1",
|
|
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, 102 free components and 6 page templates (more in @ziyarago/ui-pro).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui",
|
|
7
7
|
"design-system",
|
|
@@ -75,21 +75,13 @@
|
|
|
75
75
|
"types": "./types/resolver.d.ts",
|
|
76
76
|
"default": "./src/resolver.js"
|
|
77
77
|
},
|
|
78
|
-
"./compat": {
|
|
79
|
-
"types": "./types/compat.d.ts",
|
|
80
|
-
"default": "./src/compat/index.js"
|
|
81
|
-
},
|
|
82
|
-
"./compat/resolver": {
|
|
83
|
-
"types": "./types/compat-resolver.d.ts",
|
|
84
|
-
"default": "./src/compat/resolver.js"
|
|
85
|
-
},
|
|
86
|
-
"./compat/vite": {
|
|
87
|
-
"types": "./types/compat-vite.d.ts",
|
|
88
|
-
"default": "./src/compat/vite.js"
|
|
89
|
-
},
|
|
90
78
|
"./i18n": {
|
|
91
79
|
"types": "./types/i18n.d.ts",
|
|
92
80
|
"default": "./src/i18n/index.js"
|
|
81
|
+
},
|
|
82
|
+
"./internal": {
|
|
83
|
+
"types": "./types/internal.d.ts",
|
|
84
|
+
"default": "./src/internal.js"
|
|
93
85
|
}
|
|
94
86
|
},
|
|
95
87
|
"files": [
|
|
@@ -102,11 +94,11 @@
|
|
|
102
94
|
"CHANGELOG.md",
|
|
103
95
|
"THIRD_PARTY_NOTICES.md",
|
|
104
96
|
"src",
|
|
105
|
-
"docs/INTEGRATION.md"
|
|
106
|
-
"docs/MIGRATION.md"
|
|
97
|
+
"docs/INTEGRATION.md"
|
|
107
98
|
],
|
|
108
99
|
"scripts": {
|
|
109
100
|
"types": "node scripts/types.mjs",
|
|
101
|
+
"icons": "node scripts/icons.mjs",
|
|
110
102
|
"build": "node scripts/i18n-index.mjs && node scripts/build.mjs && node scripts/types.mjs && node scripts/manifest.mjs",
|
|
111
103
|
"check": "node scripts/check.mjs && node scripts/check-meta.mjs && node scripts/i18n-index.mjs",
|
|
112
104
|
"test": "npm run test:unit && npm run test:ssr && npm run test:browser",
|
|
@@ -156,6 +148,7 @@
|
|
|
156
148
|
"axe-core": "^4.14.0",
|
|
157
149
|
"conventional-changelog-conventionalcommits": "^9.3.1",
|
|
158
150
|
"esbuild": "^0.28.2",
|
|
151
|
+
"lucide-static": "^0.460.0",
|
|
159
152
|
"puppeteer-core": "^23.11.1",
|
|
160
153
|
"react": "^19.3.1",
|
|
161
154
|
"react-dom": "^19.3.1",
|
|
@@ -185,17 +178,11 @@
|
|
|
185
178
|
"resolver": [
|
|
186
179
|
"types/resolver.d.ts"
|
|
187
180
|
],
|
|
188
|
-
"compat": [
|
|
189
|
-
"types/compat.d.ts"
|
|
190
|
-
],
|
|
191
|
-
"compat/resolver": [
|
|
192
|
-
"types/compat-resolver.d.ts"
|
|
193
|
-
],
|
|
194
|
-
"compat/vite": [
|
|
195
|
-
"types/compat-vite.d.ts"
|
|
196
|
-
],
|
|
197
181
|
"meta": [
|
|
198
182
|
"types/meta.d.ts"
|
|
183
|
+
],
|
|
184
|
+
"internal": [
|
|
185
|
+
"types/internal.d.ts"
|
|
199
186
|
]
|
|
200
187
|
}
|
|
201
188
|
}
|
package/src/core/components.js
CHANGED
|
@@ -6,7 +6,9 @@ import { t } from '../i18n/index.js';
|
|
|
6
6
|
import { rove, tabIdx, listKeys, openKeys, focusIn, toTrigger, trigger } from './keys.js';
|
|
7
7
|
export const cx = (...a) => a.filter(Boolean).join(' ');
|
|
8
8
|
// Copies the listed props that are set; used to forward native attributes and listeners to the real <input>/<textarea>.
|
|
9
|
-
|
|
9
|
+
const DOM_NAMES = { autocomplete: 'autoComplete', maxlength: 'maxLength', minlength: 'minLength', inputmode: 'inputMode', autocapitalize: 'autoCapitalize', spellcheck: 'spellCheck', autofocus: 'autoFocus' };
|
|
10
|
+
// DOM property spelling (autoComplete, maxLength ...) so React does not warn; Vue accepts it too.
|
|
11
|
+
export const pick = (p, keys) => { const o = {}; for (const k of keys) if (p[k] !== undefined) o[DOM_NAMES[k] || k] = p[k]; return o; };
|
|
10
12
|
export const INPUT_ATTRS = ['name', 'autocomplete', 'required', 'min', 'max', 'step', 'maxlength', 'minlength', 'inputmode', 'pattern', 'spellcheck', 'autofocus', 'autocapitalize', 'onBlur', 'onFocus', 'onKeydown', 'onKeyDown', 'onKeyup', 'onKeyUp', 'onPaste'];
|
|
11
13
|
export const DZ = { xs: .82, sm: .91, md: 1, lg: 1.12, xl: 1.25 };
|
|
12
14
|
// Shared outside-click / Escape close for popups. Put `ref: ddRef(close)` on the popup element; its parent is the component root
|
|
@@ -34,14 +36,19 @@ export const ddRef = (close, o = {}) => el => {
|
|
|
34
36
|
export const dzp = (p, b) => Math.round(b * (DZ[p.size] || 1));
|
|
35
37
|
export const dzf = n => `calc(${n}px*var(--dz,1))`;
|
|
36
38
|
export const dzb = p => ({ xs: 'xs', sm: 'xs', md: 'sm', lg: 'md', xl: 'lg' }[p.size] || 'sm');
|
|
37
|
-
// Icons: Lucide SVGs
|
|
39
|
+
// Icons: Lucide SVGs inlined as data-URI masks. The ~100 icons the components use come from a lazily imported built-in set (no network, CSP-safe);
|
|
40
|
+
// any other name is fetched once from ZG_ICON_BASE (unpkg by default; override with setIconBase to self-host) or taken from registerIcons().
|
|
38
41
|
export let ZG_ICON_BASE = 'https://unpkg.com/lucide-static@0.460.0/icons/';
|
|
39
42
|
export const setIconBase = b => { ZG_ICON_BASE = b.endsWith('/') ? b : b + '/'; };
|
|
40
|
-
const iconCache = {}, iconPending = {};
|
|
43
|
+
const iconCache = {}, iconPending = {}, iconExtra = {};
|
|
44
|
+
let iconBuiltin;
|
|
45
|
+
const getBuiltin = () => iconBuiltin || (iconBuiltin = import('../icons/builtin.js').then(m => m.default).catch(() => ({})));
|
|
46
|
+
/** Add or replace icons by name with inline SVG strings: registerIcons({ 'my-logo': '<svg ...>...</svg>' }). They win over the built-in set and the CDN. */
|
|
47
|
+
export const registerIcons = map => { Object.assign(iconExtra, map); for (const k of Object.keys(map)) { delete iconCache[k]; delete iconPending[k]; } };
|
|
41
48
|
const iconUri = svg => 'url("data:image/svg+xml,' + encodeURIComponent(svg.replace(/stroke="currentColor"/g, 'stroke="black"').replace(/currentColor/g, 'black')) + '")';
|
|
42
49
|
const loadIcon = name => {
|
|
43
50
|
if (iconCache[name] || iconPending[name] || typeof document === 'undefined') return;
|
|
44
|
-
iconPending[name] = fetch(ZG_ICON_BASE + name + '.svg').then(r => r.text()).then(svg => {
|
|
51
|
+
iconPending[name] = (iconExtra[name] ? Promise.resolve(iconExtra[name]) : getBuiltin().then(b => b[name])).then(svg => svg || fetch(ZG_ICON_BASE + name + '.svg').then(r => r.text())).then(svg => {
|
|
45
52
|
iconCache[name] = iconUri(svg);
|
|
46
53
|
document.querySelectorAll('[data-zg-icon="' + name + '"]').forEach(el => el.style.setProperty('--zg-icon', iconCache[name]));
|
|
47
54
|
}).catch(() => {});
|
|
@@ -81,7 +88,6 @@ export const components = {
|
|
|
81
88
|
CodeInput: { render: ({ h, props: p }) => { const n = p.length || 6, v = p.modelValue || '';
|
|
82
89
|
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] || '')),
|
|
83
90
|
h('input', { value: v, inputMode: 'numeric', autoComplete: 'one-time-code', maxLength: n, 'aria-label': p.label || t('CodeInput', 'label'), onInput: e => { const x = e.target.value.replace(/\D/g, '').slice(0, n); p.onChange && p.onChange(x); if (x.length === n && v.length !== n) p.onComplete && p.onComplete(x); } })); } },
|
|
84
|
-
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)])) },
|
|
85
91
|
Card: { slots: ['header', 'footer'], render: ({ h, props: p, children, slots }) => h('section', { class: cx('zg-card', 'zg-glass', p.variant === 'inverse' && 'zg-card--inverse', p.variant === 'gradient' && 'zg-card--grad', p.variant === 'gradient' && 'zg-card--t-' + (p.tone || 'brand'), p.variant === 'gradient' && p.intensity === 'vivid' && 'zg-card--vivid', p.variant === 'outline' && 'zg-card--outline', p.padding === 'none' && 'zg-card--flush', p.interactive && 'zg-card--click'), 'data-theme': p.variant === 'inverse' ? 'dark' : (p.variant === 'gradient' && p.intensity === 'vivid' ? (['brand', 'warning', undefined].includes(p.tone) ? 'light' : 'dark') : undefined), onClick: p.onClick, tabIndex: p.interactive && p.onClick ? 0 : undefined, onKeydown: p.interactive && p.onClick ? e => { if (e.key === 'Enter' && e.target === e.currentTarget) p.onClick(e); } : undefined },
|
|
86
92
|
slots.header ? h('div', { class: 'zg-card__head' }, slots.header) : (p.title || slots.actions) ? h('div', { class: 'zg-card__head' }, h('div', { style: { flex: 1, minWidth: 0 } }, h('h3', { class: 'zg-card__title' }, p.title), p.subtitle ? h('div', { class: 'zg-card__sub' }, p.subtitle) : null), slots.actions || null) : null, ...children, slots.footer ? h('div', { class: 'zg-card__foot' }, slots.footer) : null) },
|
|
87
93
|
Modal: { slots: ['title', 'subtitle'], render: ({ h, props: p, children, slots }) => { const foot = slots.footer || p.footer, sub = slots.subtitle || p.subtitle, ttl = slots.title || p.title; return !p.open ? null : h('div', { class: 'zg-modal-scrim', onClick: p.closeOnBackdrop === false ? undefined : p.onClose }, h('div', { class: 'zg-modal', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.title, style: p.width ? { maxWidth: p.width } : undefined, ref: trap({ onClose: p.closeOnEsc === false ? undefined : p.onClose }), onClick: e => e.stopPropagation() },
|
|
@@ -109,8 +115,6 @@ export const components = {
|
|
|
109
115
|
const th = c => h('th', { key: c.key, 'aria-sort': p.sortKey === c.key ? (p.sortDir === 'desc' ? 'descending' : 'ascending') : undefined, class: c.num ? 'num' : undefined, style: cs(c) }, c.sortable ? h('button', { type: 'button', onClick: () => p.onSort && p.onSort(c.key, p.sortKey === c.key && p.sortDir !== 'desc' ? 'desc' : 'asc') }, c.label, p.sortKey === c.key ? icon(h, p.sortDir === 'desc' ? 'arrow-down' : 'arrow-up', 12) : null) : c.label);
|
|
110
116
|
return h('div', { class: 'zg-table-wrap', tabIndex: 0 }, h('table', { class: 'zg-table' }, h('thead', null, h('tr', null, p.onSelect ? h('th', { style: { width: '44px' } }, h('button', { type: 'button', role: 'checkbox', class: 'zg-check', 'aria-checked': String(!!all), 'aria-label': (p.labels && p.labels.selectAll) || t('DataTable', 'selectAll'), onClick: () => p.onSelect(all ? [] : rows.map(r => r.id)) }, h('span', { class: 'zg-check__box' }, all ? icon(h, 'check', 14) : null))) : null, ...cols.map(th))),
|
|
111
117
|
h('tbody', null, ...(rows.length ? rows.map((r, ri) => h('tr', { key: r.id, 'aria-selected': sel.includes(r.id) ? 'true' : undefined, class: p.onRowClick ? 'zg-tr--click' : undefined, onClick: p.onRowClick ? () => p.onRowClick(r, ri) : undefined }, p.onSelect ? h('td', null, h('button', { type: 'button', role: 'checkbox', class: 'zg-check', 'aria-checked': String(sel.includes(r.id)), 'aria-label': (p.labels && p.labels.selectRow) || t('DataTable', 'selectRow'), onClick: () => p.onSelect(sel.includes(r.id) ? sel.filter(i => i !== r.id) : [...sel, r.id]) }, h('span', { class: 'zg-check__box' }, sel.includes(r.id) ? icon(h, 'check', 14) : null))) : null, ...cols.map(c => h('td', { key: c.key, class: c.num ? 'num' : undefined, style: cs(c) }, c.render ? c.render(r[c.key], r, h) : (r[c.key] ?? '—'))))) : [h('tr', { key: 'e' }, h('td', { colSpan: cols.length + 1 }, h('div', { class: 'zg-empty' }, slots.empty || p.empty || '—')))])))); } },
|
|
112
|
-
KanbanColumn: { render: ({ h, props: p, children }) => h('div', { class: 'zg-kcol zg-dm' }, h('div', { class: 'zg-kcol__head' }, p.title, h('span', { class: 'zg-badge' }, p.count ?? children.length)), ...children) },
|
|
113
|
-
KanbanCard: { render: ({ h, props: p, children }) => h('div', { class: 'zg-kcard zg-dm' }, h('b', { style: { fontSize: dzf(14) } }, p.title), p.sub ? h('span', { class: 'zg-hint' }, p.sub) : null, ...children, p.tag ? h('span', { class: cx('zg-badge', p.tone && 'zg-badge--' + p.tone), style: { alignSelf: 'flex-start' } }, p.tag) : null) },
|
|
114
118
|
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))))) },
|
|
115
119
|
TopNav: { render: ({ h, props: p, slots }) => h('header', { class: 'zg-topnav' }, h('div', { class: 'zg-topnav__brand' }, p.brand), h('nav', { class: 'zg-topnav__items', 'aria-label': t('TopNav', 'label') }, ...(p.items || []).map(it => nav(h, it, p.value, p.onChange))), h('div', { class: 'zg-topnav__actions' }, slots.actions || p.actions || null)) },
|
|
116
120
|
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,
|
|
@@ -122,13 +126,5 @@ export const components = {
|
|
|
122
126
|
h('main', { class: 'zg-shell__main' }, ...children)); } },
|
|
123
127
|
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) },
|
|
124
128
|
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)) },
|
|
125
|
-
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))) },
|
|
126
|
-
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' },
|
|
127
|
-
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)),
|
|
128
|
-
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))))) ) },
|
|
129
|
-
TourCard: { render: ({ C, h, props: p, slots }) => { const seats = Number(p.seats) || 0, booked = Number(p.booked) || 0; return h('article', { class: 'zg-card zg-glass zg-dm' }, h('div', { class: 'zg-card__head' }, h('div', { style: { flex: 1 } }, h('h3', { class: 'zg-card__title' }, p.name), h('div', { class: 'zg-card__sub' }, [p.city, p.sessions != null && p.sessions !== '' ? p.sessions + ' sessions' : null].filter(Boolean).join(' · '))), h('span', { class: 'zg-badge zg-badge--info' }, '360°')), h(C.Progress, { value: seats > 0 ? Math.min(100, booked / seats * 100) : 0, label: t('TourCard', 'seats'), meta: `${booked}/${seats}` }), slots.actions || null); } },
|
|
130
|
-
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)); })) },
|
|
131
|
-
RamadanTracker: { render: ({ h, props: p }) => { const day = Number(p.day) || 0; return h('div', { class: 'zg-rt zg-dm', role: 'img', 'aria-label': day ? t('RamadanTracker', 'day', { day }) : t('RamadanTracker', 'label') }, ...Array.from({ length: 30 }, (_, i) => { const n = i + 1; return h('i', { key: n, 'data-s': n === day ? 'cur' : n < day ? 'past' : n > 20 ? 'last' : undefined, 'data-odd': n > 20 && n % 2 ? '' : undefined, title: String(n) }); })); } },
|
|
132
|
-
CallRow: { render: ({ C, h, props: p }) => h('div', { class: 'zg-call zg-dm' }, h(C.Avatar, { name: p.name, size: dzp(p, 36) }), h('div', { style: { minWidth: 0 } }, h('b', { style: { fontSize: dzf(14) } }, p.name), h('div', { class: 'zg-hint' }, [p.status, fmt.dur(p.duration, p.durUnits)].filter(Boolean).join(' · '))), h(C.IconButton, { icon: 'play', label: (p.labels && p.labels.play) || t('CallRow', 'play'), size: dzb(p), onClick: p.onPlay })) },
|
|
133
129
|
};
|
|
134
130
|
export { fmt };
|
package/src/core/components2.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @ziyarago/ui · components, part 2 — Forms, Surfaces, Actions, Data, Navigation, Layout, CRM, Hotels.
|
|
2
|
-
import { icon, cx,
|
|
2
|
+
import { icon, cx, ddRef, pick, INPUT_ATTRS } from './components.js';
|
|
3
3
|
import { trap } from './dialog.js';
|
|
4
4
|
import { listKeys, openKeys, tabIdx, toTrigger, trigger } from './keys.js';
|
|
5
5
|
const tipId = s => { let x = 5381; for (const c of String(s)) x = (x * 33 ^ c.charCodeAt(0)) >>> 0; return 'zg-tip-' + x.toString(36); };
|
|
@@ -11,26 +11,6 @@ const dloc = p => p.locale || (getUiLocale() === 'en' ? 'en-GB' : intlLocale());
|
|
|
11
11
|
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
12
12
|
const num = v => Number.isFinite(+v) && v !== null && v !== '' ? +v : 0;
|
|
13
13
|
const arr = v => Array.isArray(v) ? v : [];
|
|
14
|
-
const idx = (p, st, n) => clamp(Math.trunc(num(p.index !== undefined && p.index !== null ? p.index : st.i)), 0, Math.max(0, n - 1));
|
|
15
|
-
// Client-only autoplay: a ref callback re-run on each render; the interval lives on the element and ends itself once the element is detached.
|
|
16
|
-
const autoplay = (el, ms, step) => {
|
|
17
|
-
if (!el || typeof window === 'undefined') return;
|
|
18
|
-
const a = el.__zgAuto || (el.__zgAuto = { t: 0, ms: 0 });
|
|
19
|
-
a.step = step;
|
|
20
|
-
if (a.ms === ms) return;
|
|
21
|
-
clearInterval(a.t); a.t = 0; a.ms = ms;
|
|
22
|
-
if (!ms) return;
|
|
23
|
-
a.t = setInterval(() => {
|
|
24
|
-
if (!el.isConnected) { clearInterval(a.t); a.t = 0; return; }
|
|
25
|
-
const m = el.closest('[data-motion]');
|
|
26
|
-
if ((m && m.getAttribute('data-motion') === 'off') || document.hidden || (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) || el.matches(':hover, :focus-within')) return;
|
|
27
|
-
a.step();
|
|
28
|
-
}, ms);
|
|
29
|
-
};
|
|
30
|
-
let dragging = null;
|
|
31
|
-
const ROOM_TONES = { occupied: 'brand', clean: 'success', dirty: 'warning', blocked: 'danger' };
|
|
32
|
-
const TONE_OK = ['brand', 'sky', 'success', 'warning', 'danger', 'neutral'];
|
|
33
|
-
const CALL_TONES = { answered: 'success', completed: 'success', missed: 'danger', declined: 'danger', rejected: 'danger', failed: 'danger', voicemail: 'warning', busy: 'warning', 'no answer': 'warning', ringing: 'info', 'in progress': 'info' };
|
|
34
14
|
export const pd = v => { if (v == null || v === '') return null; if (v instanceof Date) return isNaN(v) ? null : v; if (typeof v === 'string') { const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v.trim()); if (m) return new Date(+m[1], +m[2] - 1, +m[3]); } const d = new Date(v); return isNaN(d) ? null : d; };
|
|
35
15
|
const lab =(h, p, body) => h('label', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label, p.required ? h('span', { class: 'zg-req', 'aria-hidden': 'true' }, ' *') : null) : null, body, p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null);
|
|
36
16
|
const field = (h, p, pre, input, suf) => h('span', { class: cx('zg-input', p.error && 'zg-input--error', p.disabled && 'zg-input--disabled') }, pre || null, input, suf || null);
|
|
@@ -51,39 +31,21 @@ export const more = {
|
|
|
51
31
|
LoadingOverlay: { render: ({ C, h, props: p, children }) => h('div', { class: 'zg-lo' }, ...children, p.loading ? h('div', { class: 'zg-lo__o' }, h(C.Spinner, { size: 28 }), p.label ? h('span', null, p.label) : null) : null) },
|
|
52
32
|
ConfirmDialog: { render: ({ C, h, props: p, children }) => h(C.Modal, { open: p.open, size: p.size, title: p.title, onClose: p.onCancel, footer: [h(C.Button, { key: 'c', variant: 'ghost', onClick: p.onCancel }, p.cancelLabel || t('ConfirmDialog', 'cancel')), h(C.Button, { key: 'k', variant: p.tone === 'danger' ? 'danger' : 'primary', loading: p.loading, onClick: p.onConfirm }, p.confirmLabel || t('ConfirmDialog', 'confirm'))] }, h('span', { style: { color: 'var(--zg-text-2)', lineHeight: 1.6 } }, p.description, ...children)) },
|
|
53
33
|
Popconfirm: { state: { open: false }, render: ({ C, h, props: p, state, set }) => h('span', { class: 'zg-dd' }, h(C.Button, { size: p.size || 'sm', variant: p.variant || 'danger', onClick: () => set({ open: !state.open }) }, p.label), state.open ? h('div', { class: 'zg-pop zg-glass', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, h('b', { style: { fontSize: '14px' } }, p.title), h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' } }, h(C.Button, { size: 'xs', variant: 'ghost', onClick: () => set({ open: false }) }, p.cancelLabel || t('Popconfirm', 'no')), h(C.Button, { size: 'xs', variant: 'primary', onClick: () => { set({ open: false }); p.onConfirm && p.onConfirm(); } }, p.confirmLabel || t('Popconfirm', 'yes')))) : null) },
|
|
54
|
-
Carousel: { state: { i: 0 }, render: ({ h, props: p, state, set }) => { const items = arr(p.items).map(x => x || {}), n = items.length, i = idx(p, state, n), it = items[i] || {}, L = p.labels || {};
|
|
55
|
-
const to = k => { if (!n) return; k = ((k % n) + n) % n; if (k === i) return; if (p.index === undefined || p.index === null) set({ i: k }); p.onChange && p.onChange(k); };
|
|
56
|
-
const ms = p.autoplay === true ? 5000 : n > 1 && num(p.autoplay) > 0 ? num(p.autoplay) : 0;
|
|
57
|
-
return h('div', { class: 'zg-car', role: 'region', 'aria-roledescription': 'carousel', 'aria-label': p.label || undefined, ref: el => autoplay(el, n > 1 ? ms : 0, () => to(i + 1)), onKeyDown: e => { if (e.key === 'ArrowLeft') to(i - 1); else if (e.key === 'ArrowRight') to(i + 1); } }, h('div', { class: 'zg-car__s', 'aria-live': ms ? 'off' : 'polite', style: { backgroundImage: it.image ? `url(${it.image})` : undefined } }, h('div', { class: 'zg-car__c' }, it.title ? h('b', null, it.title) : null, it.sub ? h('span', null, it.sub) : null)), n > 1 ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-car__p', 'aria-label': L.prev || t('Carousel', 'prev'), onClick: () => to(i - 1) }, icon(h, 'chevron-left')) : null, n > 1 ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-car__n', 'aria-label': L.next || t('Carousel', 'next'), onClick: () => to(i + 1) }, icon(h, 'chevron-right')) : null, n > 1 ? h('div', { class: 'zg-car__d' }, ...items.map((_, k) => h('button', { key: k, type: 'button', 'aria-label': (L.slide || t('Carousel', 'slide')) + ' ' + (k + 1), 'aria-current': String(k === i), onClick: () => to(k) }))) : null); } },
|
|
58
|
-
Gallery: { state: { i: 0 }, render: ({ h, props: p, state, set }) => { const im = arr(p.images).filter(Boolean), n = im.length, i = idx(p, state, n), L = p.labels || {};
|
|
59
|
-
const to = k => { if (k === i) return; if (p.index === undefined || p.index === null) set({ i: k }); p.onChange && p.onChange(k); };
|
|
60
|
-
return h('div', { class: 'zg-gal' }, h('div', { class: 'zg-gal__m', style: { backgroundImage: n ? `url(${im[i]})` : undefined }, role: 'img', 'aria-label': p.alt || '' }, n ? h('span', { class: 'zg-badge zg-gal__n' }, `${i + 1} / ${n}`) : null), n ? h('div', { class: 'zg-gal__t' }, ...im.map((u, k) => h('button', { key: k, type: 'button', 'aria-label': (L.photo || t('Gallery', 'photo')) + ' ' + (k + 1), 'aria-current': String(k === i), style: { backgroundImage: `url(${u})` }, onClick: () => to(k) }))) : null); } },
|
|
61
34
|
Code: { render: ({ C, h, props: p }) => h('div', { class: 'zg-code-b' }, h('div', { class: 'zg-code-b__h' }, h('span', null, p.title || p.language || 'code'), h(C.CopyButton, { value: p.code, size: 'xs' })), h('pre', { class: 'zg-mono', tabIndex: 0 }, p.code)) },
|
|
62
|
-
PricingCard: { render: ({ C, h, props: p }) => h('div', { class: cx('zg-card', 'zg-glass', p.featured && 'zg-price--on') }, h('div', { class: 'zg-card__head' }, h('h3', { class: 'zg-card__title' }, p.name), p.badge ? h(C.Badge, { tone: 'brand' }, p.badge) : null), h('div', { style: { display: 'flex', alignItems: 'baseline', gap: '6px' } }, h('span', { class: 'zg-num', style: { fontSize: '44px' } }, p.price), h('span', { class: 'zg-hint' }, p.period || '')), h('ul', { class: 'zg-feat' }, ...(p.features || []).map((f, i) => h('li', { key: i }, icon(h, 'check', 16), f))), h(C.Button, { variant: p.featured ? 'primary' : 'secondary', block: true, onClick: p.onSelect }, p.cta || t('PricingCard', 'choose'))) },
|
|
63
|
-
IntegrationCard: { render: ({ C, h, props: p }) => { const st = ['connected', 'error'].includes(p.status) ? p.status : 'off', tone = { connected: 'success', error: 'danger' }[st], L = { connected: t('IntegrationCard', 'connected'), error: t('IntegrationCard', 'error'), off: t('IntegrationCard', 'off'), manage: t('IntegrationCard', 'manage'), reconnect: t('IntegrationCard', 'reconnect'), link: t('IntegrationCard', 'link'), ...(p.labels || {}) };
|
|
64
|
-
return h('div', { class: 'zg-card zg-glass' }, h('div', { class: 'zg-card__head' }, h('span', { class: 'zg-tl__dot', style: { margin: '8px 4px 0' } }), h('div', { style: { flex: 1 } }, h('h3', { class: 'zg-card__title' }, p.name), h('div', { class: 'zg-card__sub' }, p.description)), h(C.Badge, { tone, dot: true }, L[st])), h(C.Button, { variant: st === 'connected' ? 'secondary' : 'primary', size: 'sm', onClick: p.onToggle }, st === 'connected' ? L.manage : st === 'error' ? L.reconnect : L.link)); } },
|
|
65
|
-
IncomingCall: { render: ({ C, h, props: p }) => h('div', { class: 'zg-inc zg-glass zg-dm' }, h(C.Avatar, { name: p.name, size: dzp(p, 48) }), h('div', { style: { flex: 1, minWidth: 0 } }, h('div', { class: 'zg-hint' }, p.label || t('IncomingCall', 'incoming')), h('b', { style: { fontSize: dzf(16) } }, p.name), h('div', { class: 'zg-mono zg-hint' }, p.phone)), h(C.IconButton, { icon: 'phone-off', label: t('IncomingCall', 'decline'), size: p.size ? p.size : undefined, onClick: p.onDecline }), h('button', { type: 'button', class: 'zg-ibtn zg-inc__ok', 'aria-label': t('IncomingCall', 'answer'), onClick: p.onAnswer }, icon(h, 'phone'))) },
|
|
66
|
-
CallCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-card zg-glass zg-dm' }, h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.name, size: dzp(p, 36) }), h('div', { style: { flex: 1, minWidth: 0 } }, h('b', { style: { fontSize: dzf(14) } }, p.name), h('div', { class: 'zg-hint zg-mono' }, p.phone)), p.status ? h(C.Badge, { tone: { ...CALL_TONES, ...(p.statusTones || {}) }[String(p.status).toLowerCase()] || (p.statusTones || {})[p.status], dot: true }, p.status) : null), h('div', { class: 'zg-lead__row' }, h(C.IconButton, { icon: 'play', label: (p.labels && p.labels.play) || t('CallCard', 'play'), size: dzb(p), onClick: p.onPlay }), h(C.Progress, { value: num(p.progress) }), h('span', { class: 'zg-mono zg-hint' }, fmt.dur(p.duration, p.durUnits)))) },
|
|
67
35
|
/* ── Actions ── */
|
|
68
36
|
Dropdown: { state: { open: false }, render: ({ h, props: p, state, set }) => h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: cx('zg-btn', p.size && 'zg-btn--' + p.size), 'aria-haspopup': 'menu', 'aria-expanded': String(state.open && !p.disabled), disabled: p.disabled, onClick: () => set({ open: !state.open }), onKeydown: openKeys(state.open && !p.disabled, () => set({ open: true })) }, p.icon ? icon(h, p.icon, 16) : null, p.label, icon(h, 'chevron-down', 14)), state.open && !p.disabled ? h('div', { class: 'zg-menu', role: 'menu', 'data-zg-dd': '', ref: ddRef(() => set({ open: false }), { focus: true }), onKeydown: listKeys('[role=menuitem]', { wrap: true, onTab: e => { const t = trigger(e.currentTarget); t && t.focus(); set({ open: false }); } }) }, ...(p.items || []).map((it, i, all) => it.separator ? h('div', { key: i, class: 'zg-menu__sep', role: 'separator' }) : h('button', { key: i, type: 'button', role: 'menuitem', tabIndex: tabIdx(all.map(x => ({ disabled: x.separator || x.disabled })), () => false)(i), disabled: it.disabled, 'aria-disabled': it.disabled ? 'true' : undefined, class: cx('zg-menu__i', it.tone === 'danger' && 'zg-menu__i--danger'), onClick: e => { toTrigger(e); set({ open: false }); it.onClick && it.onClick(it); p.onSelect && p.onSelect(it.value); } }, it.icon ? icon(h, it.icon, 16) : null, it.label, it.shortcut ? h('span', { class: 'zg-kbd' }, it.shortcut) : null))) : null) },
|
|
69
37
|
Menu: { render: ({ h, props: p }) => p.open === false ? null : h('div', { class: 'zg-menu zg-menu--static', role: 'menu', 'data-zg-dd': '', ref: p.open === true && p.onClose ? ddRef(p.onClose, { self: true, focus: true }) : undefined, onKeydown: listKeys('[role=menuitem]', { wrap: true, onTab: () => p.onClose && p.onClose() }) }, ...(p.items || []).map((it, i, all) => it.separator ? h('div', { key: i, class: 'zg-menu__sep', role: 'separator' }) : h('button', { key: i, type: 'button', role: 'menuitem', tabIndex: tabIdx(all.map(x => ({ disabled: x.separator || x.disabled })), () => false)(i), disabled: it.disabled, 'aria-disabled': it.disabled ? 'true' : undefined, class: cx('zg-menu__i', it.tone === 'danger' && 'zg-menu__i--danger'), onClick: () => { it.onClick && it.onClick(it); p.onSelect && p.onSelect(it.value); } }, it.icon ? icon(h, it.icon, 16) : null, it.label, it.shortcut ? h('span', { class: 'zg-kbd' }, it.shortcut) : null))) },
|
|
70
38
|
Tooltip: { render: ({ h, props: p, children }) => h('span', { class: cx('zg-tip', p.placement && p.placement !== 'top' && 'zg-tip--' + p.placement), 'data-tip': p.text, tabIndex: 0, 'aria-describedby': p.text ? tipId(p.text) : undefined, ref: p.text ? tipRef : undefined }, ...children, p.text ? h('span', { id: tipId(p.text), role: 'tooltip', class: 'zg-sr' }, p.text) : null) },
|
|
71
|
-
HoverCard: { render: ({ h, props: p, children }) => h('span', { class: 'zg-hc', tabIndex: 0 }, h('span', null, p.trigger), h('div', { class: 'zg-hc__c zg-glass' }, ...children)) },
|
|
72
39
|
Popover: { state: { open: false }, render: ({ C, h, props: p, state, set, children }) => h('span', { class: 'zg-dd' }, h(C.Button, { size: p.size, onClick: () => set({ open: !state.open }) }, p.label), state.open ? h('div', { class: 'zg-pop zg-glass', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...children) : null) },
|
|
73
40
|
CopyButton: { state: { done: false, err: false }, render: ({ C, h, props: p, state, set }) => { const reset = () => setTimeout(() => set({ done: false, err: false }), 1500), fail = e => { set({ done: false, err: true }); p.onError && p.onError(e); reset(); }, ok = () => { set({ done: true, err: false }); p.onCopy && p.onCopy(p.value); reset(); };
|
|
74
41
|
const msg = state.err ? t('CopyButton', 'failed') : state.done ? t('CopyButton', 'copied') : '';
|
|
75
42
|
return h('span', { style: { display: 'inline-flex' } }, h(C.Button, { size: p.size || 'sm', variant: state.err ? 'danger' : p.variant, icon: state.err ? 'triangle-alert' : state.done ? 'check' : 'copy', onClick: () => { try { const cb = typeof navigator !== 'undefined' && navigator.clipboard; if (!cb || !cb.writeText) return fail(new Error('Clipboard unavailable')); Promise.resolve(cb.writeText(p.value)).then(ok, fail); } catch (e) { fail(e); } } }, p.label || msg || t('CopyButton', 'copy')),
|
|
76
43
|
h('span', { role: 'status', 'aria-live': 'polite', style: { position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', clip: 'rect(0 0 0 0)', whiteSpace: 'nowrap' } }, msg)); } },
|
|
77
|
-
CommandBar: { render: ({ h, props: p }) => h('div', { class: cx('zg-cb', 'zg-glass') }, ...(p.items || []).map((it, i) => it.separator ? h('span', { key: i, class: 'zg-cb__s' }) : h('button', { key: i, type: 'button', class: 'zg-ibtn zg-ibtn--ghost zg-ibtn--sm', title: it.label, 'aria-label': it.label, disabled: it.disabled, onClick: () => p.onAction && p.onAction(it.id) }, icon(h, it.icon || 'circle')))) },
|
|
78
44
|
CommandPalette: { state: { q: '', a: 0 }, render: ({ h, props: p, state, set }) => { if (!p.open) return null; const f = (p.commands || []).filter(c => !state.q || (c.label + ' ' + (c.keywords || '')).toLowerCase().includes(state.q.toLowerCase())); const ai = f.length ? clamp(state.a, 0, f.length - 1) : -1; const run = c => { if (!c) return; p.onRun && p.onRun(c.id); p.onClose && p.onClose(); };
|
|
79
45
|
return h('div', { class: 'zg-modal-scrim', style: { alignItems: 'flex-start', paddingTop: '12vh' }, onClick: p.onClose }, h('div', { class: 'zg-modal', style: { maxWidth: '560px' }, role: 'dialog', 'aria-modal': 'true', 'aria-label': t('CommandPalette', 'label'), ref: trap({ onClose: p.onClose }), onClick: e => e.stopPropagation() },
|
|
80
46
|
h('div', { class: 'zg-modal__head' }, icon(h, 'search', 18), h('input', { value: state.q, autoFocus: true, 'data-autofocus': '', role: 'combobox', 'aria-expanded': 'true', 'aria-controls': 'zg-cp-list', 'aria-activedescendant': ai >= 0 ? 'zg-cp-opt-' + ai : undefined, 'aria-label': p.placeholder || t('CommandPalette', 'placeholder'), placeholder: p.placeholder || t('CommandPalette', 'placeholder'), onInput: e => set({ q: e.target.value, a: 0 }), onKeyDown: e => { if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); if (f.length) set({ a: (ai + (e.key === 'ArrowDown' ? 1 : -1) + f.length) % f.length }); } else if (e.key === 'Enter') { e.preventDefault(); run(f[ai]); } }, style: { flex: 1, border: 0, outline: 0, background: 'none', color: 'var(--zg-text-1)', font: '500 17px var(--zg-font)' } }), h('span', { class: 'zg-kbd' }, 'Esc')),
|
|
81
47
|
h('div', { class: 'zg-modal__body', id: 'zg-cp-list', role: 'listbox', style: { padding: '8px', gap: '2px' } }, ...(f.length ? f.map((c, i) => h('button', { key: c.id, type: 'button', id: 'zg-cp-opt-' + i, role: 'option', tabIndex: -1, 'aria-selected': String(i === ai), class: cx('zg-menu__i', i === ai && 'is-active'), ref: i === ai ? (el => { if (el && el.scrollIntoView) el.scrollIntoView({ block: 'nearest' }); }) : undefined, onMouseMove: () => { if (i !== ai) set({ a: i }); }, onClick: () => run(c) }, c.icon ? icon(h, c.icon, 16) : null, c.label, c.shortcut ? h('span', { class: 'zg-kbd' }, c.shortcut) : null)) : [h('div', { class: 'zg-empty' }, t('CommandPalette', 'empty'))])))); } },
|
|
82
|
-
NotificationBell: { state: { open: false, seen: {}, all: false }, render: ({ C, h, props: p, state, set }) => { const ctl = !!(p.onRead || p.onMarkAllRead), isRead = i => !!i.read || (!ctl && (state.all || !!state.seen[i.id])), un = (p.items || []).filter(i => !isRead(i)).length;
|
|
83
|
-
return h('div', { class: 'zg-dd' }, h('button', { type: 'button', class: 'zg-ibtn', 'aria-label': t('NotificationBell', 'label'), 'aria-expanded': String(state.open), onClick: () => set({ open: !state.open }) }, icon(h, 'bell'), un ? h('span', { class: 'zg-bell__n' }, un) : null),
|
|
84
|
-
state.open ? h('div', { class: 'zg-pop zg-glass', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })), style: { width: '320px', padding: '8px' } }, un ? h('button', { key: '_all', type: 'button', class: 'zg-menu__i', style: { justifyContent: 'flex-end', minHeight: '32px', color: 'var(--zg-brand-text)' }, onClick: () => { if (!ctl) set({ all: true }); p.onMarkAllRead && p.onMarkAllRead(); } }, t('NotificationBell', 'markAllRead')) : null, ...(p.items || []).map(it => h('div', { key: it.id, class: 'zg-menu__i', role: 'button', tabIndex: 0, style: { alignItems: 'flex-start', opacity: isRead(it) ? .6 : 1 }, onClick: () => { if (!isRead(it)) { if (!ctl) set({ seen: { ...state.seen, [it.id]: true } }); p.onRead && p.onRead(it.id); } p.onSelect && p.onSelect(it); }, onKeyDown: e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.currentTarget.click(); } } }, h('span', { style: { display: 'flex', flexDirection: 'column', gap: '2px', flex: 1 } }, h('b', { style: { fontSize: '14px' } }, it.title), h('span', { class: 'zg-hint' }, it.message), h('span', { class: 'zg-hint' }, it.time)), !isRead(it) ? h('span', { class: 'zg-tl__dot', style: { margin: '6px 4px' } }) : null))) : null); } },
|
|
85
48
|
ToggleGroup: { render: ({ h, props: p }) => { const cur = p.multiple ? (p.modelValue || []) : [p.modelValue]; return h('div', { class: 'zg-seg', role: 'group' }, ...(p.options || []).map(o => h('button', { key: o.value, type: 'button', 'aria-pressed': String(cur.includes(o.value)), title: o.label, onClick: () => p.onChange && p.onChange(p.multiple ? (cur.includes(o.value) ? cur.filter(v => v !== o.value) : [...cur, o.value]) : (p.allowEmpty && cur.includes(o.value) ? null : o.value)) }, o.icon ? icon(h, o.icon, 16) : null, p.iconOnly ? null : o.label))); } },
|
|
86
|
-
Fab: { render: ({ h, props: p }) => h('button', { type: 'button', class: 'zg-fab', 'aria-label': p.label || p.icon || 'plus', onClick: p.onClick }, icon(h, p.icon || 'plus', 20), p.label ? h('span', null, p.label) : null) },
|
|
87
49
|
/* ── Forms ── */
|
|
88
50
|
Textarea: { render: ({ h, props: p }) => lab(h, p, [h('textarea', { ...pick(p, ['name', 'required', 'autocomplete', 'spellcheck', 'autofocus', 'minlength', 'onBlur', 'onFocus', 'onKeydown', 'onKeyDown', 'onKeyup', 'onKeyUp']), class: cx('zg-ta', p.error && 'zg-input--error', p.resize === false && 'zg-ta--fixed', p.autoResize && 'zg-ta--auto'), rows: p.rows || 4, value: p.modelValue ?? '', placeholder: p.placeholder, maxLength: p.maxlength, disabled: p.disabled, readOnly: p.readonly, onInput: e => { p.onChange && p.onChange(e.target.value); p.onInput && p.onInput(e); } }),
|
|
89
51
|
p.showCount ? h('span', { class: 'zg-hint zg-ta__count' }, String((p.modelValue ?? '').length) + (p.maxlength ? ' / ' + p.maxlength : '')) : null]) },
|
|
@@ -97,11 +59,6 @@ export const more = {
|
|
|
97
59
|
Slider: { render: ({ h, props: p }) => { const mn = p.min ?? 0, mx = p.max ?? 100, v = p.modelValue ?? mn, pct = (v - mn) / (mx - mn) * 100;
|
|
98
60
|
return lab(h, p, h('span', { style: { display: 'flex', alignItems: 'center', gap: '12px' } }, h('input', { type: 'range', class: 'zg-range', min: mn, max: mx, step: p.step || 1, value: v, style: { '--p': pct + '%' }, onInput: e => p.onChange && p.onChange(Number(e.target.value)) }), p.showValue !== false ? h('span', { class: 'zg-mono', style: { minWidth: '48px', textAlign: 'end' } }, p.formatValue ? p.formatValue(v) : v) : null)); } },
|
|
99
61
|
Rating: { render: ({ h, props: p }) => h('div', { class: 'zg-rate', role: 'radiogroup', 'aria-label': p.label || t('Rating', 'label') }, ...Array.from({ length: p.max || 5 }, (_, i) => h('button', { key: i, type: 'button', disabled: p.readonly, 'aria-label': t('Rating', 'stars', { n: i + 1 }), 'data-on': String(i < (p.modelValue || 0)), onClick: () => p.onChange && p.onChange(i + 1) }, icon(h, 'star', ({ xs: 14, sm: 18, md: 20, lg: 26, xl: 32 })[p.size] || p.size || 20)))) },
|
|
100
|
-
ChipInput: { state: { q: '' }, render: ({ h, props: p, state, set }) => { const v = p.modelValue || [];
|
|
101
|
-
const add = t => { t = t.trim(); if (t && !v.includes(t) && (!p.maxItems || v.length < p.maxItems)) p.onChange && p.onChange([...v, t]); set({ q: '' }); };
|
|
102
|
-
return lab(h, p, h('span', { class: 'zg-input zg-chipin' }, ...v.map(tg => h('span', { key: tg, class: 'zg-tag' }, tg, h('button', { type: 'button', 'aria-label': t('ChipInput', 'remove', { name: tg }), onClick: () => p.onChange && p.onChange(v.filter(x => x !== tg)) }, icon(h, 'x', 12)))), h('input', { value: state.q, placeholder: v.length ? '' : p.placeholder, onInput: e => set({ q: e.target.value }), onKeyDown: e => { if (e.key === 'Enter' || e.key === ',') { e.preventDefault(); add(state.q); } else if (e.key === 'Backspace' && !state.q && v.length) p.onChange && p.onChange(v.slice(0, -1)); } }))); } },
|
|
103
|
-
InlineEdit: { state: { edit: false, d: '' }, render: ({ C, h, props: p, state, set }) => state.edit ? h('span', { class: 'zg-input', style: { '--h': 'var(--zg-h-md)' } }, h('input', { value: state.d, autoFocus: true, onInput: e => set({ d: e.target.value }), onKeyDown: e => { if (e.key === 'Enter') { p.onChange && p.onChange(state.d); set({ edit: false }); } if (e.key === 'Escape') set({ edit: false }); } }), h(C.IconButton, { icon: 'check', label: t('InlineEdit', 'save'), size: 'sm', onClick: () => { p.onChange && p.onChange(state.d); set({ edit: false }); } })) : h('button', { type: 'button', class: 'zg-inline', onClick: () => set({ edit: true, d: p.modelValue || '' }) }, p.modelValue || h('span', { class: 'zg-hint' }, p.emptyText || t('InlineEdit', 'edit')), icon(h, 'pencil', 14)) },
|
|
104
|
-
ColorPicker: { render: ({ h, props: p }) => h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-chips' }, ...(p.presets || ['#BC926A', '#5FAACC', '#3FD13F', '#E26E5F', '#E8B964', '#A98BD6', '#1B1919']).map(c => h('button', { key: c, type: 'button', 'aria-label': c, 'aria-pressed': String(c === p.modelValue), class: 'zg-swatch', style: { background: c }, onClick: () => p.onChange && p.onChange(c) })))) },
|
|
105
62
|
FileUpload: { state: { drag: false }, render: ({ h, props: p, state, set }) => { const ac = String(p.accept || '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean), okType = f => !ac.length || ac.some(a => a[0] === '.' ? f.name.toLowerCase().endsWith(a) : a.endsWith('/*') ? (f.type || '').toLowerCase().startsWith(a.slice(0, -1)) : (f.type || '').toLowerCase() === a), rej = (f, r) => p.onReject && p.onReject(f, r),
|
|
106
63
|
take = list => { const ok = []; for (const f of list) { if (!okType(f)) rej(f, 'type'); else if (p.maxSize && f.size > p.maxSize) rej(f, 'size'); else ok.push(f); } if (!p.multiple) ok.slice(1).forEach(f => rej(f, 'count')); const out = p.multiple ? ok : ok.slice(0, 1); if (out.length && p.onChange) p.onChange(out); }, pick = e => { const i = e.currentTarget.querySelector('input'); if (i) i.click(); };
|
|
107
64
|
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-fdrop', role: 'button', tabIndex: 0, 'aria-label': p.title || t('FileUpload', 'title'), 'data-drag': state.drag ? '' : undefined, onClick: pick, onKeydown: e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); pick(e); } }, onDragenter: e => { e.preventDefault(); if (!state.drag) set({ drag: true }); }, onDragover: e => { e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'; if (!state.drag) set({ drag: true }); }, onDragleave: e => { if (!e.currentTarget.contains(e.relatedTarget)) set({ drag: false }); }, onDrop: e => { e.preventDefault(); set({ drag: false }); take([...(e.dataTransfer ? e.dataTransfer.files : [])]); } }, icon(h, p.image ? 'image-up' : 'upload', 24), h('b', null, p.title || t('FileUpload', 'title')), h('span', { class: 'zg-hint' }, p.hint || t('FileUpload', 'hint')), h('input', { type: 'file', multiple: p.multiple, accept: p.accept, tabIndex: -1, 'aria-hidden': 'true', style: { display: 'none' }, onClick: e => e.stopPropagation(), onChange: e => { take([...e.target.files]); e.target.value = ''; } })), p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : null); } },
|
|
@@ -111,9 +68,6 @@ export const more = {
|
|
|
111
68
|
MultiSelect: { state: { open: false }, render: ({ h, props: p, state, set }) => { const v = p.modelValue || [], tog = x => p.onChange && p.onChange(v.includes(x) ? v.filter(i => i !== x) : [...v, x]);
|
|
112
69
|
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-select' }, h('button', { type: 'button', class: 'zg-btn zg-select__trigger', 'aria-haspopup': 'listbox', 'aria-expanded': String(state.open && !p.disabled), disabled: p.disabled, style: { height: 'var(--zg-h-lg)' }, onClick: () => set({ open: !state.open }) }, h('span', null, v.length ? t('MultiSelect', 'selected', { n: v.length }) : (p.placeholder || '—')), icon(h, 'chevron-down', 16)),
|
|
113
70
|
state.open && !p.disabled ? h('div', { class: 'zg-select__list', role: 'listbox', 'aria-label': p.label || p.placeholder, 'aria-multiselectable': 'true', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...(p.options || []).map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(v.includes(o.value)), onClick: () => tog(o.value) }, h('span', { class: 'zg-check__box', style: { width: '18px', height: '18px', borderRadius: '6px', background: v.includes(o.value) ? 'var(--zg-brand)' : 'transparent' } }, v.includes(o.value) ? icon(h, 'check', 12) : null), h('span', { style: { flex: 1, marginInlineStart: '10px' } }, o.label)))) : null)); } },
|
|
114
|
-
GroupedSelect: { state: { open: false }, render: ({ h, props: p, state, set }) => { const all = (p.groups || []).flatMap(g => g.options), cur = all.find(o => o.value === p.modelValue);
|
|
115
|
-
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { class: 'zg-select' }, h('button', { type: 'button', class: 'zg-btn zg-select__trigger', 'aria-haspopup': 'listbox', 'aria-expanded': String(state.open && !p.disabled), disabled: p.disabled, style: { height: 'var(--zg-h-lg)' }, onClick: () => set({ open: !state.open }) }, h('span', null, cur ? cur.label : (p.placeholder || '—')), icon(h, 'chevron-down', 16)),
|
|
116
|
-
state.open && !p.disabled ? h('div', { class: 'zg-select__list', role: 'listbox', 'aria-label': p.label || p.placeholder, 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...(p.groups || []).flatMap(g => [h('div', { key: g.label, class: 'zg-sidenav__group', style: { padding: '8px 14px 2px' } }, g.label), ...g.options.map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === p.modelValue), onClick: () => { set({ open: false }); p.onChange && p.onChange(o.value); } }, o.label))])) : null)); } },
|
|
117
71
|
PhoneInput: { render: ({ C, h, props: p }) => { const CC = p.countries || [{ value: '+966', label: 'SA +966' }, { value: '+998', label: 'UZ +998' }, { value: '+7', label: 'RU +7' }, { value: '+90', label: 'TR +90' }, { value: '+20', label: 'EG +20' }], v = p.modelValue || {};
|
|
118
72
|
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { style: { display: 'grid', gridTemplateColumns: '130px minmax(0,1fr)', gap: '8px', alignItems: 'start' } }, h(C.Select, { options: CC, modelValue: v.code || CC[0].value, onChange: c => p.onChange && p.onChange({ ...v, code: c }) }), field(h, p, icon(h, 'phone', 16), h('input', { type: 'tel', inputMode: 'tel', value: v.number || '', placeholder: p.placeholder ?? '90 123 45 67', onInput: e => p.onChange && p.onChange({ ...v, number: e.target.value }) }))), typeof p.error === 'string' && p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null); } },
|
|
119
73
|
Calendar: { state: { ym: null }, render: ({ h, props: p, state, set }) => { const s = pd(p.modelValue), b = state.ym ? new Date(state.ym) : (s || new Date()), y = b.getFullYear(), m = b.getMonth(), loc = dloc(p), ws = p.weekStartsOn ?? 1, off = (new Date(y, m, 1).getDay() - ws + 7) % 7, dn = new Date(y, m + 1, 0).getDate(), today = new Date();
|
|
@@ -127,47 +81,23 @@ export const more = {
|
|
|
127
81
|
return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null, h('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } }, h('div', { style: { flex: 1 } }, h(C.Select, { options: hs, disabled: p.disabled, modelValue: H, onChange: v => p.onChange && p.onChange(v + ':' + M) })), h('b', null, ':'), h('div', { style: { flex: 1 } }, h(C.Select, { options: ms, disabled: p.disabled, modelValue: M, onChange: v => p.onChange && p.onChange(H + ':' + v) })))); } },
|
|
128
82
|
FormField: { render: ({ h, props: p, children }) => h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label, p.required ? h('span', { style: { color: 'var(--zg-red)' } }, ' *') : null) : null, ...children, p.error ? h('span', { class: 'zg-hint zg-hint--error' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null) },
|
|
129
83
|
LoginForm: { state: { e: '', pw: '', rm: true }, render: ({ C, h, props: p, state, set }) => { const L = { email: t('LoginForm', 'email'), password: t('LoginForm', 'password'), remember: t('LoginForm', 'remember'), forgot: t('LoginForm', 'forgot'), submit: t('LoginForm', 'submit'), ...(p.labels || {}) }; return h('form', { class: 'zg-field', style: { gap: '14px' }, onSubmit: ev => { ev.preventDefault(); p.onSubmit && p.onSubmit({ email: state.e, password: state.pw, remember: state.rm }); } },
|
|
130
|
-
p.error ? h(C.Alert, { tone: 'danger' }, p.error) : null, h(C.TextField, { label: L.email, icon: 'user-round', modelValue: state.e, onChange: v => set({ e: v }) }), h(C.PasswordInput, { label: L.password, modelValue: state.pw, onChange: v => set({ pw: v }) }),
|
|
84
|
+
p.error ? h(C.Alert, { tone: 'danger' }, p.error) : null, h(C.TextField, { label: L.email, icon: 'user-round', name: p.emailName || 'email', type: p.emailType || 'text', autocomplete: 'username', inputmode: p.emailType === 'tel' ? 'tel' : p.emailType === 'email' ? 'email' : undefined, placeholder: L.emailPlaceholder, required: p.required, modelValue: state.e, onChange: v => set({ e: v }) }), h(C.PasswordInput, { label: L.password, name: p.passwordName || 'password', placeholder: L.passwordPlaceholder, required: p.required, modelValue: state.pw, onChange: v => set({ pw: v }) }),
|
|
131
85
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } }, p.showRemember !== false ? h(C.Checkbox, { label: L.remember, modelValue: state.rm, onChange: v => set({ rm: v }) }) : h('span', null), p.showForgot !== false ? h('a', { href: '#', onClick: e => { e.preventDefault(); p.onForgot && p.onForgot(); }, style: { color: 'var(--zg-brand-text)', fontWeight: 600, fontSize: '14px' } }, L.forgot) : null),
|
|
132
86
|
h('button', { type: 'submit', class: 'zg-btn zg-btn--primary zg-btn--lg zg-btn--block', disabled: p.loading }, p.loading ? h('span', { class: 'zg-spin' }) : null, p.submitLabel || L.submit)); } },
|
|
133
|
-
StepForm: { state: { i: 0 }, render: ({ C, h, props: p, state, set }) => { const st = p.steps || [], i = clamp(state.i, 0, st.length - 1);
|
|
134
|
-
return h('div', { class: 'zg-field', style: { gap: '18px' } }, h(C.Stepper, { steps: st.map(s => s.label), current: i }), h('div', null, st[i] && st[i].content), h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' } }, h(C.Button, { size: p.size, variant: 'ghost', disabled: i === 0, onClick: () => set({ i: i - 1 }) }, t('StepForm', 'back')), h(C.Button, { size: p.size, variant: 'primary', onClick: () => i === st.length - 1 ? p.onFinish && p.onFinish() : set({ i: i + 1 }) }, i === st.length - 1 ? (p.finishLabel || t('StepForm', 'finish')) : t('StepForm', 'next')))); } },
|
|
135
87
|
/* ── Data ── */
|
|
136
88
|
Tag: { render: ({ h, props: p, children }) => h('span', { class: 'zg-tag' }, ...children, (p.onRemove || p.onClose) ? h('button', { type: 'button', 'aria-label': t('Tag', 'remove'), onClick: p.onRemove || p.onClose }, icon(h, 'x', 12)) : null) },
|
|
137
89
|
Kbd: { render: ({ h, children }) => h('kbd', { class: 'zg-kbd' }, ...children) },
|
|
138
90
|
StatsCard: { render: ({ h, props: p }) => h('div', { class: 'zg-kpi' }, h('div', { style: { display: 'flex', alignItems: 'center', gap: '10px' } }, p.icon ? h('span', { class: 'zg-toast__icon' }, icon(h, p.icon, 18)) : null, h('span', { class: 'zg-kpi__label' }, p.label)), h('span', { class: 'zg-kpi__value zg-num' }, p.value), p.delta ? h('span', { class: cx('zg-delta', p.trend === 'down' ? 'zg-delta--down' : 'zg-delta--up') }, (p.trend === 'down' ? '▼ ' : '▲ ') + p.delta) : null) },
|
|
139
|
-
KpiInline: { render: ({ h, props: p }) => h('span', { class: 'zg-kpii' }, h('span', { class: 'zg-hint' }, p.label), h('b', { class: 'zg-num zg-kpii__v', style: { fontSize: '20px', fontWeight: 300 } }, p.value), p.delta ? h('span', { class: cx('zg-delta', String(p.delta).startsWith('-') ? 'zg-delta--down' : 'zg-delta--up') }, p.delta) : null) },
|
|
140
91
|
MiniChart: { render: ({ h, props: p }) => { const v = arr(p.values).map(x => Math.max(0, num(x))), mx = Math.max(...v, 1), H = p.height || { xs: 28, sm: 36, md: 48, lg: 64, xl: 80 }[p.size] || 48, W = v.length * 14;
|
|
141
92
|
return p.type === 'line' ? h('svg', { width: '100%', height: H, viewBox: `0 0 ${Math.max(W, 10)} ${H}`, preserveAspectRatio: 'none', role: 'img' }, h('path', { d: v.map((x, i) => `${i ? 'L' : 'M'}${i * 14 + 7} ${H - 4 - (x / mx) * (H - 8)}`).join(' '), fill: 'none', stroke: 'var(--zg-brand)', 'stroke-width': 2, 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'vector-effect': 'non-scaling-stroke' }))
|
|
142
93
|
: h('div', { class: 'zg-mini', style: { height: H + 'px' } }, ...v.map((x, i) => h('i', { key: i, style: { height: Math.max(6, x / mx * 100) + '%', background: p.highlightLast && i === v.length - 1 ? 'var(--zg-brand)' : 'var(--zg-s3)' } }))); } },
|
|
143
94
|
Descriptions: { render: ({ h, props: p }) => h('dl', { class: 'zg-desc' }, ...(p.items || []).flatMap((it, i) => [h('dt', { key: 'k' + i }, it.label), h('dd', { key: 'v' + i }, it.value ?? '—')])) },
|
|
144
95
|
LazyImage: { render: ({ h, props: p }) => h('span', { role: 'img', 'aria-label': p.alt || '', class: 'zg-lazy', style: { aspectRatio: p.aspectRatio || '16/9', backgroundImage: p.src ? `url(${p.src})` : undefined, borderRadius: (p.rounded ?? 20) + 'px' } }) },
|
|
145
|
-
SystemStatus: { render: ({ h, props: p }) => h('div', { class: 'zg-sys' }, ...(p.items || []).map((it, i) => h('div', { key: i, class: 'zg-sys__r' }, h('span', { class: 'zg-badge__dot', style: { color: { ok: 'var(--zg-green)', warn: 'var(--zg-amber)', down: 'var(--zg-red)' }[it.state || 'ok'] } }), h('span', { style: { flex: 1 } }, it.name), h('span', { class: 'zg-mono', style: { color: { ok: 'var(--zg-green)', warn: 'var(--zg-amber)', down: 'var(--zg-red)' }[it.state || 'ok'] } }, it.value)))) },
|
|
146
|
-
TreeView: { state: { open: {} }, render: ({ h, props: p, state, set }) => { const node = (n, d) => { const has = n.children && n.children.length, o = state.open[n.id] ?? d < 1;
|
|
147
|
-
return h('div', { key: n.id }, h('button', { type: 'button', class: cx('zg-tree__n', p.selected === n.id && 'zg-tree__n--on'), style: { paddingInlineStart: 8 + d * 18 + 'px' }, 'aria-expanded': has ? String(o) : undefined, onClick: () => { has && set({ open: { ...state.open, [n.id]: !o } }); p.onSelect && p.onSelect(n.id); } }, has ? icon(h, o ? 'chevron-down' : 'chevron-right', 14) : h('span', { style: { width: '14px' } }), n.icon ? icon(h, n.icon, 16) : null, n.label), has && o ? n.children.map(c => node(c, d + 1)) : null); };
|
|
148
|
-
return h('div', { class: 'zg-tree' }, ...(p.nodes || []).map(n => node(n, 0))); } },
|
|
149
|
-
CalendarView: { render: ({ C, h, props: p }) => h(C.Calendar, { ...p, events: Object.fromEntries((p.events || []).map(e => [e.date, e.label])) }) },
|
|
150
96
|
/* ── Navigation / Layout ── */
|
|
151
97
|
SubNav: { render: ({ h, props: p }) => h('nav', { class: 'zg-subnav', 'aria-label': p.title }, p.title ? h('div', { class: 'zg-sidenav__group' }, p.title) : null, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', class: 'zg-navitem', 'aria-current': it.value === p.value ? 'page' : undefined, onClick: () => p.onChange && p.onChange(it.value) }, it.icon ? icon(h, it.icon, 16) : null, h('span', { style: { flex: 1, textAlign: 'start' } }, it.label), it.badge ? h('span', { class: 'zg-navitem__count' }, it.badge) : null))) },
|
|
152
|
-
ActionRail: { render: ({ C, h, props: p }) => h('div', { class: 'zg-rail zg-glass' }, ...(p.items || []).map((it, i) => it.separator ? h('span', { key: i, class: 'zg-cb__s' }) : h(C.IconButton, { key: i, icon: it.icon, label: it.label, ghost: it.value !== p.value, onClick: () => p.onChange && p.onChange(it.value) }))) },
|
|
153
98
|
SplitLayout: { render: ({ h, props: p, slots, children }) => h('div', { class: 'zg-split', style: { gridTemplateColumns: p.columns || '1fr 1fr' } }, h('div', null, slots.left || children[0] || null), h('div', null, slots.right || children[1] || null)) },
|
|
154
99
|
SettingsSection: { render: ({ h, props: p, children }) => h('section', { class: 'zg-setsec' }, h('div', null, h('b', { style: { fontSize: '16px' } }, p.title), p.description ? h('p', { class: 'zg-hint', style: { margin: '4px 0 0', fontSize: '13px' } }, p.description) : null), h('div', { class: 'zg-field', style: { gap: '14px' } }, ...children)) },
|
|
155
100
|
ScrollArea: { render: ({ h, props: p, children }) => h('div', { class: 'zg-scroll', style: { maxHeight: p.maxHeight || '240px' } }, ...children) },
|
|
156
101
|
/* ── CRM ── */
|
|
157
|
-
DealCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-lead zg-dm' }, h('div', { class: 'zg-lead__row' }, h('b', { style: { fontSize: dzf(14), flex: 1 } }, p.title), p.stage ? h(C.Badge, { tone: p.tone || 'info' }, p.stage) : null), h('div', { class: 'zg-num', style: { fontSize: dzf(26), fontWeight: 300 } }, p.amount), h(C.Progress, { value: p.probability ?? 0, label: t('DealCard', 'probability'), meta: (p.probability ?? 0) + '%' }), h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.owner, size: dzp(p, 24) }), h('span', { class: 'zg-hint' }, p.owner), h('span', { class: 'zg-hint', style: { marginInlineStart: 'auto' } }, p.due))) },
|
|
158
|
-
PipelineBoard: { state: { over: null }, render: ({ C, h, props: p, state, set }) => { const mv = p.onMove, stages = arr(p.stages).filter(Boolean);
|
|
159
|
-
const drop = (e, to) => { e.preventDefault(); const d = dragging; dragging = null; if (state.over !== null) set({ over: null }); if (d && d.from !== to) mv({ id: d.id, from: d.from, to }); };
|
|
160
|
-
return h('div', { style: { display: 'flex', gap: '14px', overflowX: 'auto', paddingBottom: '4px' } }, ...stages.map((s, si) => { const cards = arr(s.cards).filter(Boolean), sid = s.id ?? si;
|
|
161
|
-
const col = h(C.KanbanColumn, { key: sid, title: s.title, count: cards.length, size: p.size }, ...cards.map((c, i) => { const deal = h(C.DealCard, { key: c.id ?? i, size: p.size, ...c });
|
|
162
|
-
return mv ? h('div', { key: c.id ?? i, class: 'zg-drag', draggable: 'true', onDragStart: e => { dragging = { id: c.id ?? i, from: sid }; if (e.dataTransfer) { e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', String(c.id ?? i)); } }, onDragEnd: () => { dragging = null; if (state.over !== null) set({ over: null }); } }, deal) : deal; }));
|
|
163
|
-
return mv ? h('div', { key: sid, class: 'zg-drop', 'data-over': String(state.over === sid && !!dragging && dragging.from !== sid), onDragOver: e => { if (!dragging) return; e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = 'move'; if (state.over !== sid) set({ over: sid }); }, onDragLeave: e => { if (!e.currentTarget.contains(e.relatedTarget) && state.over === sid) set({ over: null }); }, onDrop: e => drop(e, sid) }, col) : col; })); } },
|
|
164
|
-
ContactPanel: { render: ({ C, h, props: p }) => h('div', { class: 'zg-card zg-glass zg-dm' }, h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.name, size: dzp(p, 56) }), h('div', null, h('b', { style: { fontSize: dzf(18) } }, p.name), h('div', { class: 'zg-hint' }, p.role))), h(C.Descriptions, { items: [{ label: t('ContactPanel', 'phone'), value: p.phone }, { label: t('ContactPanel', 'telegram'), value: p.telegram }, { label: t('ContactPanel', 'email'), value: p.email }, { label: t('ContactPanel', 'city'), value: p.city }] }), h('div', { style: { display: 'flex', gap: '8px' } }, h(C.Button, { variant: 'primary', size: 'sm', icon: 'phone', onClick: () => p.onCall && p.onCall() }, (p.labels && p.labels.call) || t('ContactPanel', 'call')), h(C.Button, { size: 'sm', icon: 'send', onClick: () => p.onMessage && p.onMessage() }, (p.labels && p.labels.message) || t('ContactPanel', 'message')))) },
|
|
165
|
-
SalesFunnel: { render: ({ h, props: p }) => { const st = arr(p.steps).map(s => s || {}), mx = Math.max(...st.map(s => num(s.value)), 1);
|
|
166
|
-
return h('div', { class: 'zg-field', style: { gap: '8px' } }, ...st.map((s, i) => h('div', { key: i, style: { display: 'grid', gridTemplateColumns: '120px minmax(0,1fr) 56px', gap: '12px', alignItems: 'center', fontSize: '14px' } }, h('span', null, s.label), h('span', { style: { display: 'flex', height: '32px', borderRadius: '12px', background: 'var(--zg-s2)' } }, h('span', { style: { width: (Math.max(0, num(s.value)) / mx * 100) + '%', borderRadius: '12px', background: i === st.length - 1 ? 'var(--zg-brand)' : 'color-mix(in oklch,var(--zg-sky) 40%,var(--zg-s2))' } })), h('b', { class: 'zg-mono', style: { textAlign: 'end' } }, fmt.num(s.value))))); } },
|
|
167
102
|
/* ── Hotels ── */
|
|
168
|
-
BookingCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-lead zg-dm' }, h('div', { class: 'zg-lead__row' }, h('b', { class: 'zg-mono', style: { fontSize: dzf(14), flex: 1 } }, p.code), h(C.Badge, { tone: { Confirmed: 'success', Pending: 'warning', Cancelled: 'danger' }[p.status], dot: true }, p.status)), h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.guest, size: dzp(p, 32) }), h('div', null, h('b', { style: { fontSize: dzf(14) } }, p.guest), h('div', { class: 'zg-hint' }, p.hotel))), h('div', { class: 'zg-lead__row zg-hint' }, icon(h, 'calendar', dzp(p, 14)), p.dates, h('span', { class: 'zg-num', style: { marginInlineStart: 'auto', fontSize: dzf(16), fontWeight: 600, color: 'var(--zg-text-1)' } }, fmt.sar(p.amount)))) },
|
|
169
|
-
GuestCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-card zg-glass zg-dm' }, h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.name, size: dzp(p, 48) }), h('div', { style: { flex: 1 } }, h('b', { style: { fontSize: dzf(16) } }, p.name), h('div', { class: 'zg-hint' }, p.country, ' · ', t('GuestCard', 'nights', { n: p.nights }))), p.vip ? h(C.Badge, { tone: 'brand' }, 'VIP') : null), h('div', { class: 'zg-chips' }, ...(p.tags || []).map(t => h('span', { key: t, class: 'zg-tag' }, t)))) },
|
|
170
|
-
RoomGrid: { render: ({ h, props: p }) => { const T = { ...ROOM_TONES, ...(p.statusTones || {}) }; return h('div', { class: 'zg-rooms zg-dm' }, ...arr(p.rooms).filter(Boolean).map((r, i) => { const tone = TONE_OK.includes(T[r.status]) ? T[r.status] : undefined; return h('button', { key: r.no ?? i, type: 'button', class: cx('zg-room', tone && 'zg-t-' + tone), 'data-s': r.status, 'data-tone': tone, onClick: () => p.onSelect && p.onSelect(r.no) }, h('b', { class: 'zg-mono' }, r.no), h('span', null, r.type)); })); } },
|
|
171
|
-
RoomCalendar: { render: ({ h, props: p }) => { const D = p.days || 14; return h('div', { class: 'zg-rc zg-dm', style: { '--d': D } }, h('span'), ...Array.from({ length: D }, (_, i) => h('span', { key: 'd' + i, class: 'zg-rc__h' }, i + 1)), ...arr(p.rooms).filter(Boolean).flatMap(r => [h('b', { key: r.no, class: 'zg-mono' }, r.no), ...Array.from({ length: D }, (_, i) => { const b = arr(r.bookings).find(x => i >= x.from && i < x.from + x.nights); return h('i', { key: r.no + '_' + i, class: 'zg-rc__c', 'data-b': b ? (i === b.from ? 'start' : 'mid') : undefined, title: b ? b.guest : undefined }); })])); } },
|
|
172
|
-
OccupancyChart: { render: ({ h, props: p }) => h('div', { class: 'zg-occ' }, ...arr(p.values).map((v, i) => { v = clamp(num(v), 0, 100); return h('div', { key: i, class: 'zg-occ__c' }, h('span', { class: 'zg-hint zg-mono' }, v + '%'), h('i', { style: { height: v + '%', background: v > 90 ? 'var(--zg-brand)' : 'color-mix(in oklch,var(--zg-sky) 55%,var(--zg-s3))' } }), h('span', { class: 'zg-hint' }, arr(p.labels)[i] || '')); })) },
|
|
173
103
|
};
|