@ziyarago/ui 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +7 -7
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/assets/fonts/jetbrains-mono/jetbrains-mono-cyrillic-ext.woff2 +0 -0
- package/assets/fonts/jetbrains-mono/jetbrains-mono-cyrillic.woff2 +0 -0
- package/assets/fonts/jetbrains-mono/jetbrains-mono-latin-ext.woff2 +0 -0
- package/assets/fonts/jetbrains-mono/jetbrains-mono-latin.woff2 +0 -0
- package/assets/fonts/manrope/manrope-cyrillic-ext.woff2 +0 -0
- package/assets/fonts/manrope/manrope-cyrillic.woff2 +0 -0
- package/assets/fonts/manrope/manrope-latin-ext.woff2 +0 -0
- package/assets/fonts/manrope/manrope-latin.woff2 +0 -0
- package/dist/llms-full.txt +210 -2
- package/dist/llms.txt +15 -3
- package/dist/manifest.json +2808 -989
- package/dist/style.css +33 -5
- package/docs/INTEGRATION.md +3 -3
- package/docs/MIGRATION.md +3 -1
- package/package.json +2 -2
- package/src/compat/index.js +3 -2
- package/src/compat/vite.js +1 -1
- package/src/core/bools.js +1 -1
- package/src/core/components2.js +3 -3
- package/src/core/components7.js +13 -0
- package/src/core/pages.js +17 -6
- package/src/i18n/components/QiblaCompass.js +5 -0
- package/src/i18n/components/index.js +2 -0
- package/src/meta/domain.js +10 -0
- package/src/meta/forms.js +4 -0
- package/src/meta/groups.js +2 -2
- package/src/meta/pages.js +139 -0
- package/src/meta/templates.json +1391 -1
- package/src/react/index.js +12 -0
- package/src/styles/components5.css +1 -1
- package/src/styles/components8.css +20 -1
- package/src/styles/effects.css +2 -1
- package/src/styles/fonts.css +9 -1
- package/src/vue/index.js +1 -1
- package/types/react.d.ts +686 -0
- package/types/vue-global.d.ts +12 -0
- package/types/vue.d.ts +478 -0
package/dist/style.css
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
/* @ziyarago/ui · generated by scripts/build.mjs — do not edit */
|
|
2
|
-
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
|
|
3
2
|
|
|
4
|
-
/* ./fonts.css */
|
|
5
3
|
|
|
4
|
+
/* ./fonts.css */
|
|
5
|
+
/* Manrope and JetBrains Mono (SIL OFL 1.1), self-hosted variable fonts from Google Fonts, latin and cyrillic subsets. No request to Google, works under a strict CSP. */
|
|
6
|
+
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400 600;font-display:swap;src:url('../assets/fonts/jetbrains-mono/jetbrains-mono-cyrillic-ext.woff2') format('woff2');unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F}
|
|
7
|
+
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400 600;font-display:swap;src:url('../assets/fonts/jetbrains-mono/jetbrains-mono-cyrillic.woff2') format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116}
|
|
8
|
+
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400 600;font-display:swap;src:url('../assets/fonts/jetbrains-mono/jetbrains-mono-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF}
|
|
9
|
+
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400 600;font-display:swap;src:url('../assets/fonts/jetbrains-mono/jetbrains-mono-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
|
|
10
|
+
@font-face{font-family:'Manrope';font-style:normal;font-weight:200 800;font-display:swap;src:url('../assets/fonts/manrope/manrope-cyrillic-ext.woff2') format('woff2');unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F}
|
|
11
|
+
@font-face{font-family:'Manrope';font-style:normal;font-weight:200 800;font-display:swap;src:url('../assets/fonts/manrope/manrope-cyrillic.woff2') format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116}
|
|
12
|
+
@font-face{font-family:'Manrope';font-style:normal;font-weight:200 800;font-display:swap;src:url('../assets/fonts/manrope/manrope-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF}
|
|
13
|
+
@font-face{font-family:'Manrope';font-style:normal;font-weight:200 800;font-display:swap;src:url('../assets/fonts/manrope/manrope-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
|
|
6
14
|
/* Thmanyah — free incl. commercial, https://font.thmanyah.com. Files in assets/fonts/thmanyah. Adjust URLs to your bundler.
|
|
7
15
|
Thmanyah is visually heavier than Manrope at the same weight and has no 600 face. Weight ranges map the UI weights (300-900) so Arabic does not look bold:
|
|
8
16
|
200-300 Light, 400-600 Regular, 700 Medium, 800 Bold, 900 Black. */
|
|
@@ -372,7 +380,7 @@
|
|
|
372
380
|
.zg-pgroot{display:block;min-width:0;width:100%}.zg-pg{display:flex;flex-direction:column;gap:16px;min-width:0}.zg-pg__r{min-width:0}
|
|
373
381
|
.zg-pg__auto{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,var(--min,340px)),1fr));gap:16px;align-items:start;min-width:0}
|
|
374
382
|
.zg-pg__two{display:flex;flex-wrap:wrap;gap:16px;align-items:flex-start;min-width:0}.zg-pg__main{flex:999 1 460px;min-width:0;display:flex;flex-direction:column;gap:16px}.zg-pg__side{flex:1 1 300px;min-width:0;display:flex;flex-direction:column;gap:16px}.zg-pg__aside{flex:1 1 260px;min-width:0;max-width:100%;display:flex;flex-direction:column;gap:16px}
|
|
375
|
-
.zg-pgauth{position:relative;min-height:
|
|
383
|
+
.zg-pgauth{position:relative;min-height:100vh;min-height:100dvh;display:grid}.zg-pgauth__bg{position:absolute;inset:0;overflow:hidden}.zg-pgauth__c{position:relative;display:grid;place-items:center;padding:24px;min-height:inherit}.zg-pgauth .zg-auth{min-height:0;padding:0;width:100%;background:transparent;display:grid;place-items:center}.zg-pgauth .zg-auth__card{margin-inline:auto}
|
|
376
384
|
|
|
377
385
|
|
|
378
386
|
/* ./components6.css */
|
|
@@ -494,8 +502,10 @@
|
|
|
494
502
|
/* CommandPalette active row, dialog focus */
|
|
495
503
|
.zg-menu__i.is-active,.zg-menu__i[aria-selected="true"]{background:var(--zg-s2)}
|
|
496
504
|
.zg-modal:focus,.zg-sheet:focus,.zg-fsd:focus,.zg-saver:focus{outline:0}
|
|
505
|
+
/* auth page templates fill the viewport */
|
|
506
|
+
.zg-split2{min-height:100vh;min-height:100dvh}
|
|
497
507
|
/* AuthShowcase: photo (dark framed card), light (gradient card + proof pill + glass caption), tint (coloured panel, pill fields) */
|
|
498
|
-
.zg-ash{min-height:
|
|
508
|
+
.zg-ash{min-height:100vh;min-height:100dvh;padding:clamp(12px,3vw,40px);display:grid;place-items:center;--ash-r:28px;--ash-acc:var(--zg-brand)}
|
|
499
509
|
.zg-ash--tint{--ash-acc:oklch(.7 .13 215)}
|
|
500
510
|
.zg-ash__title{margin:0;font:300 clamp(28px,3.4vw,40px)/1.1 var(--zg-font);letter-spacing:-.03em}
|
|
501
511
|
.zg-ash__sub{margin:10px 0 0;color:var(--zg-text-2);line-height:1.55}
|
|
@@ -541,6 +551,23 @@
|
|
|
541
551
|
.zg-ash__media[style*="url("]::after{display:none}
|
|
542
552
|
.zg-ash--light .zg-ash__media{--ash-acc:oklch(.72 .08 80)}
|
|
543
553
|
|
|
554
|
+
/* page templates: sticky region */
|
|
555
|
+
.zg-pg__two--sticky{align-items:stretch}.zg-pg__stick{position:sticky;inset-block-start:16px;align-self:flex-start;width:100%}
|
|
556
|
+
|
|
557
|
+
/* QiblaCompass */
|
|
558
|
+
.zg-qb{display:flex;flex-direction:column;align-items:center;gap:8px;max-width:100%;direction:ltr}
|
|
559
|
+
.zg-qb__svg{width:100%;height:auto;display:block}
|
|
560
|
+
.zg-qb__ring{fill:var(--zg-s1);stroke:var(--zg-stroke);stroke-width:1}
|
|
561
|
+
.zg-qb__tk{stroke:var(--zg-text-3);stroke-width:1;stroke-linecap:round}.zg-qb__tk--m{stroke:var(--zg-text-2);stroke-width:1.8}
|
|
562
|
+
.zg-qb__n{fill:var(--zg-red)}.zg-qb__ln{stroke:var(--zg-brand);stroke-width:3;stroke-linecap:round}
|
|
563
|
+
.zg-qb__kb{fill:var(--zg-bg);stroke:var(--zg-brand);stroke-width:2}.zg-qb__kg{fill:var(--zg-brand)}
|
|
564
|
+
.zg-qb__hub{fill:var(--zg-brand)}
|
|
565
|
+
.zg-qb__dial,.zg-qb__ptr{transition:transform .6s cubic-bezier(.2,.8,.2,1)}
|
|
566
|
+
.zg-qb__txt{display:flex;flex-direction:column;align-items:center;line-height:1.2;color:var(--zg-text-2);font-size:calc(13px*var(--dz,1))}
|
|
567
|
+
.zg-qb__txt b{font-size:calc(22px*var(--dz,1));font-weight:600;color:var(--zg-text-1)}.zg-qb__txt small{color:var(--zg-text-3)}
|
|
568
|
+
/* Numbers: tabular figures with a plain zero (no slashed or dotted zero) in KPI values, tables and code cells */
|
|
569
|
+
.zg-num,.zg-mono,.zg-kpi__value,.zg-table .num{font-feature-settings:"zero" 0,"tnum" 1}
|
|
570
|
+
|
|
544
571
|
|
|
545
572
|
/* ./sizes/typography.css */
|
|
546
573
|
/* size overrides for the typography group. Only [data-size] selectors, see the size scale at the end of tokens.css */
|
|
@@ -1430,7 +1457,7 @@
|
|
|
1430
1457
|
[data-motion="off"] *,[data-motion="off"] *::before,[data-motion="off"] *::after{animation-duration:.001ms!important;animation-delay:0s!important;animation-iteration-count:1!important;transition-duration:.001ms!important;transition-delay:0s!important;scroll-behavior:auto!important}
|
|
1431
1458
|
|
|
1432
1459
|
/* ── effects lite: flat surfaces for weak browsers and devices ── */
|
|
1433
|
-
[data-fx="lite"] *,[data-fx="lite"] *::before,[data-fx="lite"] *::after{-webkit-backdrop-filter:none!important;backdrop-filter:none!important}
|
|
1460
|
+
[data-fx="lite"]:not([data-effects="full"]) *,[data-fx="lite"]:not([data-effects="full"]) *::before,[data-fx="lite"]:not([data-effects="full"]) *::after{-webkit-backdrop-filter:none!important;backdrop-filter:none!important}
|
|
1434
1461
|
[data-fx="lite"]{--zg-shadow-1:0 2px 8px oklch(0 0 0 / .18);--zg-shadow-2:0 6px 20px oklch(0 0 0 / .28);--zg-brand-glow:transparent}
|
|
1435
1462
|
[data-fx="lite"][data-theme="light"]{--zg-shadow-1:0 2px 8px oklch(0.4 0.03 60 / .12);--zg-shadow-2:0 6px 20px oklch(0.4 0.03 60 / .18)}
|
|
1436
1463
|
[data-fx="lite"] .zg-glass{background:var(--zg-pop)}
|
|
@@ -1448,6 +1475,7 @@ html[data-bg]:not([data-bg="none"]),.zg-bg:not([data-bg="none"]){background-colo
|
|
|
1448
1475
|
.zg-bg:not([data-bg="none"]){background-attachment:scroll}
|
|
1449
1476
|
html[data-bg]:not([data-bg="none"]) .zg-root{background:transparent}
|
|
1450
1477
|
html[data-motion="on"][data-bg]:not([data-bg="none"]){background-image:none}
|
|
1478
|
+
html[data-motion="on"][data-bg]:not([data-bg="none"]){overflow-x:clip}
|
|
1451
1479
|
html[data-motion="on"][data-bg]:not([data-bg="none"])::before{content:"";position:fixed;inset:-25%;z-index:-1;pointer-events:none;background-image:var(--zg-bg-image);background-repeat:no-repeat;animation:zg-bg-float 18s ease-in-out infinite alternate,zg-bg-hue 40s linear infinite;will-change:transform,filter}
|
|
1452
1480
|
@keyframes zg-bg-hue{from{filter:hue-rotate(0deg) saturate(1.15)}to{filter:hue-rotate(360deg) saturate(1.15)}}
|
|
1453
1481
|
@keyframes zg-bg-float{0%{transform:translate3d(-3%,-2%,0) scale(1) rotate(0deg)}50%{transform:translate3d(4%,3%,0) scale(1.18) rotate(3deg)}100%{transform:translate3d(-2%,5%,0) scale(1.08) rotate(-3deg)}}
|
package/docs/INTEGRATION.md
CHANGED
|
@@ -78,7 +78,7 @@ With Tailwind in Nuxt, use the layered import from section 3: set `css: false` i
|
|
|
78
78
|
Tailwind 4 puts preflight in `@layer base` and utilities in `@layer utilities`. CSS outside any layer beats every layer, regardless of specificity. So an unlayered `@ziyarago/ui/style.css` next to Tailwind has two problems (both reproduced):
|
|
79
79
|
|
|
80
80
|
1. Utilities on our components lose: `<UiButton class="px-10 rounded-none bg-red-500">` keeps the padding, radius and background of `.zg-btn`.
|
|
81
|
-
2. Order matters: `@import "tailwindcss"; @import "@ziyarago/ui/style.css";` silently dropped the Google Fonts `@import url(...)
|
|
81
|
+
2. Order matters: `@import "tailwindcss"; @import "@ziyarago/ui/style.css";` silently dropped the old Google Fonts `@import url(...)`. Since 0.7 the fonts are bundled (`@font-face` with local files), so there is no `@import` to lose.
|
|
82
82
|
|
|
83
83
|
Recipe (single CSS entry, Vite or Nuxt):
|
|
84
84
|
|
|
@@ -87,7 +87,7 @@ Recipe (single CSS entry, Vite or Nuxt):
|
|
|
87
87
|
@import "@ziyarago/ui/style.css" layer(components);
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
Result: preflight < our styles < utilities. `class="w-48"`, `px-10`, `rounded-none`, `bg-red-500` on `UiButton`/`UiCard` all apply; the
|
|
90
|
+
Result: preflight < our styles < utilities. `class="w-48"`, `px-10`, `rounded-none`, `bg-red-500` on `UiButton`/`UiCard` all apply; the fonts load from your own origin. Do not also import the stylesheet from `main.ts`.
|
|
91
91
|
|
|
92
92
|
Put `class="zg-root"` on your app root (`<div id="app" class="zg-root">` or body). It sets font, text color, background and the reduced-motion rule.
|
|
93
93
|
|
|
@@ -105,7 +105,7 @@ Compared computed styles of every element of every documented example (182 compo
|
|
|
105
105
|
|
|
106
106
|
After the fix the remaining differences between the two setups are `appearance: button`, `img { max-width: 100% }`, `svg { display: block }` and running animation frames; none is visible.
|
|
107
107
|
|
|
108
|
-
Fonts:
|
|
108
|
+
Fonts: Manrope, JetBrains Mono (latin and cyrillic) and Thmanyah (Arabic) ship inside the package (`assets/fonts`) and `style.css` loads them from there. No request goes to Google, so a strict CSP (`style-src 'self'`, `font-src 'self'`) works. Your bundler must resolve the `url(../assets/fonts/...)` references; Vite and Nuxt do it by default.
|
|
109
109
|
|
|
110
110
|
## 4. vue-i18n, pinia, vue-router
|
|
111
111
|
|
package/docs/MIGRATION.md
CHANGED
|
@@ -45,6 +45,8 @@ npm i @ziyarago/ui
|
|
|
45
45
|
|
|
46
46
|
You can later remove `@floating-ui/vue`, `class-variance-authority` and (when no page imports it) `tailwind-merge` from the UI layer; the pages themselves still use `clsx`, `lucide-vue-next` and Tailwind.
|
|
47
47
|
|
|
48
|
+
**CSP warning:** icons are fetched at runtime. Under `connect-src 'self'` they disappear unless you self-host them as shown below (`setIconBase`). Do this before shipping to a server with a strict CSP.
|
|
49
|
+
|
|
48
50
|
Keep `lucide-vue-next`. The new kit takes icons by NAME (`icon="trash-2"`) and fetches Lucide SVGs from `unpkg.com/lucide-static@0.460.0/icons/` at runtime; the compat layer converts your Lucide components to names for you (`iconName()`), including the 9 icons that lucide-vue-next 1.x renamed (`funnel` to `filter`, `circle-question-mark` to `circle-help`, `text-align-start` to `align-left`, ...). To avoid the CDN (offline, CSP, flash on first paint) self-host once:
|
|
49
51
|
|
|
50
52
|
```bash
|
|
@@ -58,7 +60,7 @@ setIconBase("/lucide/");
|
|
|
58
60
|
|
|
59
61
|
## 3. CSS
|
|
60
62
|
|
|
61
|
-
In `src/styles/main.css`, directly after Tailwind, import the kit INTO the `components` layer (so Tailwind utilities such as `w-44`, `p-0`, `h-20`, `bg-red-500` on kit components still win, and the
|
|
63
|
+
In `src/styles/main.css`, directly after Tailwind, import the kit INTO the `components` layer (so Tailwind utilities such as `w-44`, `p-0`, `h-20`, `bg-red-500` on kit components still win, and the bundled `@font-face` rules stay valid inside the layer; since 0.7 there is no remote `@import` in `style.css`):
|
|
62
64
|
|
|
63
65
|
```css
|
|
64
66
|
@import "tailwindcss";
|
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.10.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, 208 components and 40 page templates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui",
|
|
7
7
|
"design-system",
|
package/src/compat/index.js
CHANGED
|
@@ -438,6 +438,8 @@ export const UiMultilangInput = defineComponent({
|
|
|
438
438
|
|
|
439
439
|
// ───────────────────────── theme ─────────────────────────
|
|
440
440
|
const LEGACY_KEY = 'glass-ui-theme', themeRef = ref('light');
|
|
441
|
+
// Read the storage BEFORE the kit's theme controller initialises: it writes its defaults (dark) to "zg-ui" on first use, which would hide the old choice.
|
|
442
|
+
const boot = (() => { try { return typeof localStorage === 'undefined' ? {} : { legacy: localStorage.getItem(LEGACY_KEY), hasNew: !!JSON.parse(localStorage.getItem('zg-ui') || '{}').theme }; } catch { return {}; } })();
|
|
441
443
|
let themeInit = false;
|
|
442
444
|
/** Drop-in for the old useTheme(): { theme, setTheme, toggleTheme }. Also toggles the .dark class (for Tailwind dark:) next to the new data-theme attribute. Reads the old localStorage key once. */
|
|
443
445
|
export function useTheme() {
|
|
@@ -445,8 +447,7 @@ export function useTheme() {
|
|
|
445
447
|
if (!themeInit && typeof window !== 'undefined') {
|
|
446
448
|
themeInit = true;
|
|
447
449
|
const sync = tm => { themeRef.value = tm; document.documentElement.classList.toggle('dark', tm === 'dark'); try { localStorage.setItem(LEGACY_KEY, tm); } catch { /* storage blocked */ } };
|
|
448
|
-
|
|
449
|
-
try { legacy = localStorage.getItem(LEGACY_KEY); hasNew = !!JSON.parse(localStorage.getItem('zg-ui') || '{}').theme; } catch { /* storage blocked */ }
|
|
450
|
+
const legacy = boot.legacy, hasNew = boot.hasNew;
|
|
450
451
|
if (!hasNew) ui.setTheme(legacy === 'light' || legacy === 'dark' ? legacy : (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'));
|
|
451
452
|
sync(ui.theme);
|
|
452
453
|
ui.subscribe(s => sync(s.theme));
|
package/src/compat/vite.js
CHANGED
|
@@ -11,7 +11,7 @@ export function ziyaragoCompat(options = {}) {
|
|
|
11
11
|
enforce: 'pre',
|
|
12
12
|
resolveId(source, importer) {
|
|
13
13
|
const norm = source.replace(/\\/g, '/');
|
|
14
|
-
if (!importer || norm.startsWith('\0') || norm.startsWith(from)) return null;
|
|
14
|
+
if (!importer || importer.includes('node_modules') || norm.startsWith('\0') || norm.startsWith(from) || !norm.includes('components/ui')) return null;
|
|
15
15
|
if (importer.replace(/\\/g, '/').includes('/components/ui/')) return null;
|
|
16
16
|
if (exclude.some(r => r.test(norm))) return null;
|
|
17
17
|
const m = ui.exec(norm);
|
package/src/core/bools.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Names of props that are boolean in EVERY component that documents them. The Vue adapter turns the bare-attribute form (<UiButton block>, which Vue delivers as '') into true for these.
|
|
2
2
|
// Names that are boolean somewhere and text elsewhere (modelValue, image, brand) are not listed: '' is a legitimate value there.
|
|
3
3
|
// scripts/check-meta.mjs keeps this list in sync with src/meta.
|
|
4
|
-
export const BOOL_PROPS = new Set(['addable', 'allowEmpty', 'autoResize', 'autofocus', 'banner', 'block', 'bold', 'checked', 'clearable', 'closable', 'closeOnBackdrop', 'closeOnEsc', 'collapsed', 'collapsible', 'disabled', 'divider', 'dot', 'external', 'featured', 'frame', 'ghost', 'glass', 'hideArrows', 'hideIcon', 'highlightLast', 'iconOnly', 'indeterminate', 'interactive', 'labelLeft', 'last', 'loading', 'multi', 'multiple', 'open', 'pill', 'plain', 'readonly', 'required', 'resize', 'ring', 'sanitize', 'selectGroups', 'showCount', 'showForgot', 'showValue', 'spellcheck', 'ticks', 'truncate', 'vertical', 'vip', 'wrap']);
|
|
4
|
+
export const BOOL_PROPS = new Set(['addable', 'allowEmpty', 'autoResize', 'autofocus', 'banner', 'block', 'bold', 'checked', 'clearable', 'closable', 'closeOnBackdrop', 'closeOnEsc', 'collapsed', 'collapsible', 'disabled', 'divider', 'dot', 'external', 'featured', 'frame', 'ghost', 'glass', 'hideArrows', 'hideIcon', 'highlightLast', 'iconOnly', 'indeterminate', 'interactive', 'labelLeft', 'last', 'loading', 'multi', 'multiple', 'open', 'pill', 'plain', 'readonly', 'required', 'resize', 'ring', 'sanitize', 'selectGroups', 'showCount', 'showForgot', 'showRemember', 'showValue', 'spellcheck', 'ticks', 'truncate', 'vertical', 'vip', 'wrap']);
|
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, fmt, dzp, dzf, dzb, ddRef, pick } from './components.js';
|
|
2
|
+
import { icon, cx, fmt, dzp, dzf, dzb, ddRef, pick, INPUT_ATTRS } from './components.js';
|
|
3
3
|
import { trap } from './dialog.js';
|
|
4
4
|
import { t, getUiLocale, intlLocale } from '../i18n/index.js';
|
|
5
5
|
const dloc = p => p.locale || (getUiLocale() === 'en' ? 'en-GB' : intlLocale());
|
|
@@ -84,7 +84,7 @@ export const more = {
|
|
|
84
84
|
p.showCount ? h('span', { class: 'zg-hint zg-ta__count' }, String((p.modelValue ?? '').length) + (p.maxlength ? ' / ' + p.maxlength : '')) : null]) },
|
|
85
85
|
NumberInput: { state: { draft: null }, render: ({ C, h, props: p, state, set: put }) => { const v = p.modelValue ?? null, st = p.step || 1, lo = p.min ?? -Infinity, hi = p.max ?? Infinity, parse = s => { const t = String(s).trim().replace(',', '.'); if (t === '') return null; const n = Number(t); return Number.isFinite(n) ? n : NaN; }, emit = n => p.onChange && p.onChange(n), bump = d => { put({ draft: null }); emit(clamp(Math.round(((v ?? 0) + d) * 1e10) / 1e10, lo, hi)); };
|
|
86
86
|
return lab(h, p, h('span', { class: 'zg-input zg-num-in' }, h(C.IconButton, { icon: 'minus', label: t('NumberInput', 'decrease'), size: 'sm', onClick: () => bump(-st) }), h('input', { value: state.draft ?? (v ?? ''), inputMode: 'decimal', placeholder: p.placeholder, style: { textAlign: 'center' }, onInput: e => { const raw = e.target.value, n = parse(raw); put({ draft: raw }); if (n === null || !Number.isNaN(n)) emit(n); }, onBlur: e => { const n = parse(e.target.value); put({ draft: null }); if (n === null) { if (v !== null) emit(null); } else if (!Number.isNaN(n)) { const c = clamp(n, lo, hi); if (c !== v) emit(c); } } }), h(C.IconButton, { icon: 'plus', label: t('NumberInput', 'increase'), size: 'sm', onClick: () => bump(st) }))); } },
|
|
87
|
-
PasswordInput: { state: { show: false }, render: ({ C, h, props: p, state, set }) => lab(h, p, field(h, p, icon(h, 'lock-keyhole', 18), h('input', { type: state.show ? 'text' : 'password', value: p.modelValue ?? '', placeholder: p.placeholder, autoComplete: p.autocomplete || 'current-password', onInput: e => p.onChange && p.onChange(e.target.value) }), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': state.show ? t('PasswordInput', 'hide') : t('PasswordInput', 'show'), onClick: () => set({ show: !state.show }) }, icon(h, state.show ? 'eye-off' : 'eye', 16)))) },
|
|
87
|
+
PasswordInput: { state: { show: false }, render: ({ C, h, props: p, state, set }) => lab(h, p, field(h, p, icon(h, 'lock-keyhole', 18), h('input', { ...pick(p, INPUT_ATTRS.filter(k => k !== 'autocomplete')), type: state.show ? 'text' : 'password', value: p.modelValue ?? '', placeholder: p.placeholder, autoComplete: p.autocomplete || 'current-password', onInput: e => p.onChange && p.onChange(e.target.value) }), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': state.show ? t('PasswordInput', 'hide') : t('PasswordInput', 'show'), onClick: () => set({ show: !state.show }) }, icon(h, state.show ? 'eye-off' : 'eye', 16)))) },
|
|
88
88
|
SearchInput: { render: ({ h, props: p }) => h('span', { class: 'zg-input', style: { '--h': 'var(--zg-h-md)' } }, icon(h, 'search', 16), h('input', { type: 'search', value: p.modelValue ?? '', placeholder: p.placeholder || t('SearchInput', 'placeholder'), onInput: e => p.onChange && p.onChange(e.target.value), onKeyDown: e => e.key === 'Enter' && p.onSearch && p.onSearch(p.modelValue) }), p.modelValue ? h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': t('SearchInput', 'clear'), onClick: () => p.onChange && p.onChange('') }, icon(h, 'x', 14)) : null) },
|
|
89
89
|
RadioGroup: { render: ({ h, props: p }) => h('div', { class: 'zg-field', role: 'radiogroup', 'aria-disabled': p.disabled ? 'true' : undefined, style: { gap: '10px' } }, p.label ? h('span', { class: 'zg-label' }, p.label) : null,
|
|
90
90
|
h('div', { class: cx('zg-rg', p.orientation === 'horizontal' && 'zg-rg--row') }, ...(p.options || []).map(o => h('button', { key: o.value, type: 'button', role: 'radio', class: 'zg-check', 'aria-checked': String(o.value === p.modelValue), disabled: p.disabled || o.disabled, 'aria-disabled': p.disabled || o.disabled ? 'true' : undefined, onClick: () => !(p.disabled || o.disabled) && p.onChange && p.onChange(o.value) }, h('span', { class: 'zg-check__box zg-radio' }, o.value === p.modelValue ? h('i') : null), h('span', null, o.label, o.hint ? h('span', { class: 'zg-hint', style: { display: 'block' } }, o.hint) : null)))),
|
|
@@ -123,7 +123,7 @@ export const more = {
|
|
|
123
123
|
FormField: { render: ({ h, props: p, children }) => h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label, p.required ? h('span', { style: { color: 'var(--zg-red)' } }, ' *') : null) : null, ...children, p.error ? h('span', { class: 'zg-hint zg-hint--error' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null) },
|
|
124
124
|
LoginForm: { state: { e: '', pw: '', rm: true }, render: ({ C, h, props: p, state, set }) => { const L = { email: t('LoginForm', 'email'), password: t('LoginForm', 'password'), remember: t('LoginForm', 'remember'), forgot: t('LoginForm', 'forgot'), submit: t('LoginForm', 'submit'), ...(p.labels || {}) }; return h('form', { class: 'zg-field', style: { gap: '14px' }, onSubmit: ev => { ev.preventDefault(); p.onSubmit && p.onSubmit({ email: state.e, password: state.pw, remember: state.rm }); } },
|
|
125
125
|
p.error ? h(C.Alert, { tone: 'danger' }, p.error) : null, h(C.TextField, { label: L.email, icon: 'user-round', modelValue: state.e, onChange: v => set({ e: v }) }), h(C.PasswordInput, { label: L.password, modelValue: state.pw, onChange: v => set({ pw: v }) }),
|
|
126
|
-
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } }, h(C.Checkbox, { label: L.remember, modelValue: state.rm, onChange: v => set({ rm: v }) }), p.showForgot !== false ? h('a', { href: '#', onClick: e => { e.preventDefault(); p.onForgot && p.onForgot(); }, style: { color: 'var(--zg-brand-text)', fontWeight: 600, fontSize: '14px' } }, L.forgot) : null),
|
|
126
|
+
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),
|
|
127
127
|
h('button', { type: 'submit', class: 'zg-btn zg-btn--primary zg-btn--lg zg-btn--block', disabled: p.loading }, p.loading ? h('span', { class: 'zg-spin' }) : null, p.submitLabel || L.submit)); } },
|
|
128
128
|
StepForm: { state: { i: 0 }, render: ({ C, h, props: p, state, set }) => { const st = p.steps || [], i = clamp(state.i, 0, st.length - 1);
|
|
129
129
|
return h('div', { class: 'zg-field', style: { gap: '18px' } }, h(C.Stepper, { steps: st.map(s => s.label), current: i }), h('div', null, st[i] && st[i].content), h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' } }, h(C.Button, { size: p.size, variant: 'ghost', disabled: i === 0, onClick: () => set({ i: i - 1 }) }, t('StepForm', 'back')), h(C.Button, { size: p.size, variant: 'primary', onClick: () => i === st.length - 1 ? p.onFinish && p.onFinish() : set({ i: i + 1 }) }, i === st.length - 1 ? (p.finishLabel || t('StepForm', 'finish')) : t('StepForm', 'next')))); } },
|
package/src/core/components7.js
CHANGED
|
@@ -7,6 +7,19 @@ const sp = v => (typeof v === 'number' ? v + 'px' : v);
|
|
|
7
7
|
const stack = (dir) => ({ render: ({ h, props: p, children }) => h(p.as || 'div', { class: 'zg-stack', style: { flexDirection: dir, gap: sp(p.gap ?? 12), alignItems: p.align, justifyContent: p.justify, flexWrap: p.wrap ? 'wrap' : undefined, ...(p.style || {}) } }, ...children) });
|
|
8
8
|
|
|
9
9
|
export const more7 = {
|
|
10
|
+
QiblaCompass: { render: ({ h, props: p }) => {
|
|
11
|
+
const px = { xs: 120, sm: 160, md: 200, lg: 250, xl: 300 }[p.size] || (Number(p.size) > 0 ? Number(p.size) : 200), norm = v => ((Number(v) || 0) % 360 + 360) % 360, b = norm(p.bearing), hd = norm(p.heading), deg = Math.round(b), ticks = [];
|
|
12
|
+
for (let i = 0; i < 72; i++) { const major = i % 9 === 0, a = i * 5 * Math.PI / 180, r1 = 92, r2 = major ? 80 : i % 3 ? 87 : 84; ticks.push(h('line', { key: i, class: major ? 'zg-qb__tk zg-qb__tk--m' : 'zg-qb__tk', x1: +(100 + Math.sin(a) * r1).toFixed(2), y1: +(100 - Math.cos(a) * r1).toFixed(2), x2: +(100 + Math.sin(a) * r2).toFixed(2), y2: +(100 - Math.cos(a) * r2).toFixed(2) })); }
|
|
13
|
+
return h('div', { class: 'zg-qb zg-dm', role: 'img', 'aria-label': t('QiblaCompass', 'label', { deg }), style: { width: px + 'px' } },
|
|
14
|
+
h('svg', { class: 'zg-qb__svg', viewBox: '0 0 200 200', 'aria-hidden': 'true', focusable: 'false' },
|
|
15
|
+
h('circle', { class: 'zg-qb__ring', cx: 100, cy: 100, r: 98 }),
|
|
16
|
+
h('g', { class: 'zg-qb__dial', transform: `rotate(${-hd} 100 100)` }, ...ticks, h('path', { class: 'zg-qb__n', d: 'M100 6 L106 20 L94 20 Z' })),
|
|
17
|
+
h('g', { class: 'zg-qb__ptr', transform: `rotate(${b - hd} 100 100)` },
|
|
18
|
+
h('line', { class: 'zg-qb__ln', x1: 100, y1: 100, x2: 100, y2: 46 }),
|
|
19
|
+
h('rect', { class: 'zg-qb__kb', x: 88, y: 24, width: 24, height: 24, rx: 5 }),
|
|
20
|
+
h('rect', { class: 'zg-qb__kg', x: 88, y: 31, width: 24, height: 4 })),
|
|
21
|
+
h('circle', { class: 'zg-qb__hub', cx: 100, cy: 100, r: 5 })),
|
|
22
|
+
h('div', { class: 'zg-qb__txt' }, h('b', { class: 'zg-num' }, deg + '°'), h('span', null, t('QiblaCompass', 'qibla')), p.distance ? h('small', null, p.distance) : null)); } },
|
|
10
23
|
Heading: { render: ({ h, props: p, children }) => h('h' + clamp(p.level || 2, 1, 6), { class: cx('zg-heading', 'zg-heading--' + (p.size || 'lg'), p.brand && 'zg-heading--brand') }, ...children) },
|
|
11
24
|
Text: { render: ({ h, props: p, children }) => h(p.as || 'p', { class: cx('zg-text', 'zg-text--' + (p.size || 'md'), p.tone && 'zg-text--' + p.tone, p.bold && 'zg-text--bold', p.truncate && 'zg-text--truncate') }, ...children) },
|
|
12
25
|
Box: { render: ({ h, props: p, children }) => h(p.as || 'div', { class: cx('zg-box', p.glass && 'zg-glass'), style: { padding: sp(p.p), ...(p.style || {}) } }, ...children) },
|
package/src/core/pages.js
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
// React: <UiPageDashboard title="Dashboard" kpis={<…/>} />
|
|
4
4
|
import { cx } from './components.js';
|
|
5
5
|
|
|
6
|
-
const region = (h, id, kids) => (kids == null || (Array.isArray(kids) && !kids.length)) ? null : h('div', { key: id, class: 'zg-pg__r', 'data-slot': id }, kids);
|
|
6
|
+
const region = (h, id, kids, stick) => (kids == null || (Array.isArray(kids) && !kids.length)) ? null : h('div', { key: id, class: stick ? 'zg-pg__r zg-pg__stick' : 'zg-pg__r', 'data-slot': id }, kids);
|
|
7
7
|
|
|
8
8
|
const draw = (h, node, ctx) => {
|
|
9
|
-
if (typeof node === 'string') return node === 'header' ? ctx.header() : region(h, node, ctx.slots[node]);
|
|
10
|
-
const [kind, ...a] = node, go = n => draw(h, n, ctx);
|
|
9
|
+
if (typeof node === 'string') return node === 'header' ? ctx.header() : region(h, node, ctx.slots[node], ctx.stick === node);
|
|
10
|
+
const [kind, ...a] = node, opt = kind === 'two' ? a[2] : kind === 'wrap' ? a[3] : null, st = typeof opt === 'string' && opt.startsWith('sticky:') ? opt.slice(7) : null, go = n => draw(h, n, st ? { ...ctx, stick: st } : ctx), cls = st ? 'zg-pg__two zg-pg__two--sticky' : 'zg-pg__two';
|
|
11
11
|
if (kind === 'col') return h('div', { class: 'zg-pg' }, ...a.map(go));
|
|
12
12
|
if (kind === 'auto') { const min = a.shift(); return h('div', { class: 'zg-pg__auto', style: { '--min': min + 'px' } }, ...a.map(go)); }
|
|
13
|
-
if (kind === 'two') return h('div', { class:
|
|
14
|
-
if (kind === 'wrap') return h('div', { class:
|
|
13
|
+
if (kind === 'two') return h('div', { class: cls }, h('div', { class: 'zg-pg__main' }, ...a[0].map(go)), h('div', { class: 'zg-pg__side' }, ...a[1].map(go)));
|
|
14
|
+
if (kind === 'wrap') return h('div', { class: cls }, h('div', { class: 'zg-pg__aside', style: { flexBasis: (a[2] || 300) + 'px' } }, ...a[0].map(go)), h('div', { class: 'zg-pg__main' }, ...a[1].map(go)));
|
|
15
15
|
return null;
|
|
16
16
|
};
|
|
17
17
|
|
|
@@ -49,7 +49,18 @@ const T = Object.fromEntries([
|
|
|
49
49
|
page('PageBilling', ['header', 'plan', 'usage', 'invoices', 'methods', 'upgrade'], ['col', 'header', ['two', ['plan', 'usage', 'invoices'], ['methods', 'upgrade']]]),
|
|
50
50
|
page('PageTeam', ['header', 'stats', 'members', 'roles', 'invite'], ['col', 'header', 'stats', ['two', ['members', 'roles'], ['invite']]]),
|
|
51
51
|
page('PageIntegrations', ['header', 'filters', 'connections', 'keys', 'webhooks'], ['col', 'header', 'filters', 'connections', ['auto', 420, 'keys', 'webhooks']]),
|
|
52
|
-
page('PageSearch', ['header', 'search', 'filters', 'results', 'empty'], ['col', 'header', 'search', ['wrap', ['filters'], ['results', 'empty'], 260]])
|
|
52
|
+
page('PageSearch', ['header', 'search', 'filters', 'results', 'empty'], ['col', 'header', 'search', ['wrap', ['filters'], ['results', 'empty'], 260]]),
|
|
53
|
+
page('PageArticle', ['header', 'cover', 'body', 'toc', 'share', 'related'], ['col', 'header', 'cover', ['two', ['body', 'share', 'related'], ['toc'], 'sticky:toc']]),
|
|
54
|
+
page('PageChangelog', ['header', 'filters', 'timeline', 'subscribe'], ['col', 'header', ['two', ['filters', 'timeline'], ['subscribe'], 'sticky:subscribe']]),
|
|
55
|
+
page('PageHelp', ['header', 'search', 'categories', 'popular', 'contact'], ['col', 'header', 'search', 'categories', ['auto', 360, 'popular', 'contact']]),
|
|
56
|
+
page('PageStates', ['header', 'empty', 'noresults', 'forbidden', 'offline', 'error', 'loading', 'success'], ['col', 'header', ['auto', 300, 'empty', 'noresults', 'forbidden'], ['auto', 300, 'offline', 'error', 'loading'], 'success']),
|
|
57
|
+
page('PageLegal', ['header', 'meta', 'toc', 'document', 'contact'], ['col', 'header', 'meta', ['wrap', ['toc'], ['document', 'contact'], 260, 'sticky:toc']]),
|
|
58
|
+
page('PageLocations', ['header', 'filters', 'list', 'map'], ['col', 'header', 'filters', ['wrap', ['list'], ['map'], 380, 'sticky:map']]),
|
|
59
|
+
page('PageBookingFlow', ['header', 'steps', 'content', 'nav', 'summary'], ['col', 'header', 'steps', ['two', ['content', 'nav'], ['summary'], 'sticky:summary']]),
|
|
60
|
+
page('PageItinerary', ['header', 'days', 'timeline', 'transport', 'hotel', 'summary'], ['col', 'header', 'days', ['two', ['timeline', 'transport', 'hotel'], ['summary'], 'sticky:summary']]),
|
|
61
|
+
page('PagePrayer', ['header', 'location', 'today', 'month', 'qibla'], ['col', 'header', 'location', ['two', ['today', 'month'], ['qibla'], 'sticky:qibla']]),
|
|
62
|
+
page('PageGroupManager', ['header', 'trip', 'toolbar', 'bulk', 'pilgrims', 'rooms', 'payments'], ['col', 'header', 'trip', ['two', ['toolbar', 'bulk', 'pilgrims', 'rooms'], ['payments'], 'sticky:payments']]),
|
|
63
|
+
page('PageTourPublic', ['header', 'gallery', 'highlights', 'itinerary', 'included', 'reviews', 'booking'], ['col', 'header', 'gallery', ['two', ['highlights', 'itinerary', 'included', 'reviews'], ['booking'], 'sticky:booking']])
|
|
53
64
|
]);
|
|
54
65
|
|
|
55
66
|
T.PageAuth = { slots: ['background', 'logo', 'aside', 'form', 'footer'], render: ({ C, h, props: p, slots: s }) => {
|
|
@@ -44,6 +44,7 @@ import Pagination from './Pagination.js';
|
|
|
44
44
|
import PasswordInput from './PasswordInput.js';
|
|
45
45
|
import Popconfirm from './Popconfirm.js';
|
|
46
46
|
import PricingCard from './PricingCard.js';
|
|
47
|
+
import QiblaCompass from './QiblaCompass.js';
|
|
47
48
|
import RamadanTracker from './RamadanTracker.js';
|
|
48
49
|
import RangeCalendar from './RangeCalendar.js';
|
|
49
50
|
import RangeSlider from './RangeSlider.js';
|
|
@@ -112,6 +113,7 @@ defineMessages('Pagination', Pagination);
|
|
|
112
113
|
defineMessages('PasswordInput', PasswordInput);
|
|
113
114
|
defineMessages('Popconfirm', Popconfirm);
|
|
114
115
|
defineMessages('PricingCard', PricingCard);
|
|
116
|
+
defineMessages('QiblaCompass', QiblaCompass);
|
|
115
117
|
defineMessages('RamadanTracker', RamadanTracker);
|
|
116
118
|
defineMessages('RangeCalendar', RangeCalendar);
|
|
117
119
|
defineMessages('RangeSlider', RangeSlider);
|
package/src/meta/domain.js
CHANGED
|
@@ -49,6 +49,16 @@ export default {
|
|
|
49
49
|
},
|
|
50
50
|
notes: ['Exposed as role="img" with aria-label "Day N of 30".'],
|
|
51
51
|
},
|
|
52
|
+
QiblaCompass: {
|
|
53
|
+
description: 'Compass dial with a Kaaba marker pointing to the Qibla and the bearing in degrees below it.',
|
|
54
|
+
props: {
|
|
55
|
+
bearing: { type: 'number', required: true, default: 0, description: 'Qibla bearing in degrees clockwise from true north, 0 to 360; other values wrap.' },
|
|
56
|
+
heading: { type: 'number', default: 0, description: 'Direction the device points to, in degrees. The dial turns against it so the marker follows the real Qibla; 0 keeps north up.' },
|
|
57
|
+
distance: { type: 'string', description: 'Optional small caption under the bearing, such as "4 210 km to the Kaaba".' },
|
|
58
|
+
size: { type: "'xs' | 'sm' | 'md' | 'lg' | 'xl' | number", values: ['xs', 'sm', 'md', 'lg', 'xl'], default: 'md', description: 'Diameter: xs 120, sm 160, md 200, lg 250, xl 300 px; a number is used as px.' },
|
|
59
|
+
},
|
|
60
|
+
notes: ['Exposed as role="img" with a label that states the bearing. Compute the bearing and heading yourself (geolocation and DeviceOrientation); the component only draws them.'],
|
|
61
|
+
},
|
|
52
62
|
BookingCard: {
|
|
53
63
|
description: 'Compact booking summary: code, status, guest, hotel, dates and total.',
|
|
54
64
|
props: {
|
package/src/meta/forms.js
CHANGED
|
@@ -78,6 +78,9 @@ export default {
|
|
|
78
78
|
props: {
|
|
79
79
|
modelValue: { type: 'string', model: true, default: '', description: 'Current password.' },
|
|
80
80
|
label, placeholder: placeholder(), hint, error,
|
|
81
|
+
required: { type: 'boolean', default: false, description: 'Adds a red asterisk to the label and the native required attribute.' },
|
|
82
|
+
name: { type: 'string', forwarded: true, description: 'Native name attribute of the input.' },
|
|
83
|
+
autofocus: { type: 'boolean', forwarded: true, description: 'Focuses the input on mount.' },
|
|
81
84
|
autocomplete: { type: 'string', default: 'current-password', description: 'Native autocomplete token. Use "new-password" on sign-up and password-change forms so managers offer to generate one.' },
|
|
82
85
|
size: size(),
|
|
83
86
|
},
|
|
@@ -465,6 +468,7 @@ export default {
|
|
|
465
468
|
error: { type: 'string', description: 'Failure message shown in a danger alert above the fields.' },
|
|
466
469
|
loading: { type: 'boolean', default: false, description: 'Disables the submit button and shows a spinner.' },
|
|
467
470
|
showForgot: { type: 'boolean', default: true, description: 'Show the "Forgot password?" link.' },
|
|
471
|
+
showRemember: { type: 'boolean', default: true, description: 'Show the "Remember device" checkbox. Hide it when your backend has no such option; the submit payload then keeps remember: true.' },
|
|
468
472
|
submitLabel: { type: 'string', default: 'Sign in', description: 'Submit button text. Wins over labels.submit.' },
|
|
469
473
|
labels: { type: '{ email?: string; password?: string; remember?: string; forgot?: string; submit?: string }', default: "{ email: 'Email or phone', password: 'Password', remember: 'Remember device', forgot: 'Forgot password?', submit: 'Sign in' }", description: 'Texts of the form; any missing key keeps its English default.' },
|
|
470
474
|
size: size(),
|
package/src/meta/groups.js
CHANGED
|
@@ -9,8 +9,8 @@ export const GROUPS = [
|
|
|
9
9
|
{ id: 'overlay', title: 'Overlay', blurb: 'Dialogs, sheets and popovers.', names: ['Modal', 'Drawer', 'ConfirmDialog', 'Popconfirm', 'StepDialog', 'FullscreenDialog', 'BottomSheet', 'Actionsheet', 'Popover', 'HoverCard', 'Tooltip', 'Dropdown', 'Menu', 'ContextMenu', 'CommandBar', 'CommandPalette', 'ImageViewer', 'Tour', 'TourPopover', 'Screensaver'] },
|
|
10
10
|
{ id: 'navigation', title: 'Navigation', blurb: 'Menus, tabs and paging.', names: ['TopNav', 'SideNav', 'SubNav', 'Tabs', 'PillTabs', 'Breadcrumbs', 'Pagination', 'Accordion'] },
|
|
11
11
|
{ id: 'media', title: 'Media and icons', blurb: 'Avatars, images, maps and icons.', names: ['Avatar', 'Icon', 'LazyImage', 'Gallery', 'Carousel', 'Map', 'ProfileHeader', 'ChatAi'] },
|
|
12
|
-
{ id: 'domain', title: 'Domain', blurb: 'Umrah, Hajj, hotel and CRM blocks.', names: ['HotelCard', 'TourCard', 'PrayerTimes', 'RamadanTracker', 'BookingCard', 'GuestCard', 'RoomGrid', 'RoomCalendar', 'LeadCard', 'DealCard', 'PipelineBoard', 'ContactPanel', 'KanbanColumn', 'KanbanCard', 'CallRow', 'CallCard', 'IncomingCall', 'AuthSplit', 'AuthShowcase', 'SocialAuth'] },
|
|
13
|
-
{ id: 'pages', title: 'Page templates', blurb: 'Ready layouts with named slots.', names: ['PageDashboard', 'PagePatterns', 'PageCrm', 'PageBookings', 'PageCatalog', 'PageDetail', 'PageSettings', 'PageUserList', 'PageProfile', 'PageForm', 'PageOrder', 'PageAccount', 'PagePricing', 'PageFaq', 'PageAnalytics', 'PageCalendar', 'PageKanban', 'PageBilling', 'PageTeam', 'PageIntegrations', 'PageSearch', 'PageNotifications', 'PageInbox', 'PageCheckout', 'PageOnboarding', 'PageLanding', 'PageAuth', 'PageStatus'] },
|
|
12
|
+
{ id: 'domain', title: 'Domain', blurb: 'Umrah, Hajj, hotel and CRM blocks.', names: ['HotelCard', 'TourCard', 'PrayerTimes', 'RamadanTracker', 'QiblaCompass', 'BookingCard', 'GuestCard', 'RoomGrid', 'RoomCalendar', 'LeadCard', 'DealCard', 'PipelineBoard', 'ContactPanel', 'KanbanColumn', 'KanbanCard', 'CallRow', 'CallCard', 'IncomingCall', 'AuthSplit', 'AuthShowcase', 'SocialAuth'] },
|
|
13
|
+
{ id: 'pages', title: 'Page templates', blurb: 'Ready layouts with named slots.', names: ['PageDashboard', 'PagePatterns', 'PageCrm', 'PageBookings', 'PageCatalog', 'PageDetail', 'PageSettings', 'PageUserList', 'PageProfile', 'PageForm', 'PageOrder', 'PageAccount', 'PagePricing', 'PageFaq', 'PageAnalytics', 'PageCalendar', 'PageKanban', 'PageBilling', 'PageTeam', 'PageIntegrations', 'PageSearch', 'PageArticle', 'PageChangelog', 'PageHelp', 'PageStates', 'PageLegal', 'PageLocations', 'PageBookingFlow', 'PageItinerary', 'PagePrayer', 'PageGroupManager', 'PageTourPublic', 'PageNotifications', 'PageInbox', 'PageCheckout', 'PageOnboarding', 'PageLanding', 'PageAuth', 'PageStatus'] },
|
|
14
14
|
];
|
|
15
15
|
|
|
16
16
|
export const groupOf = name => GROUPS.find(g => g.names.includes(name));
|
package/src/meta/pages.js
CHANGED
|
@@ -339,6 +339,145 @@ export default {
|
|
|
339
339
|
},
|
|
340
340
|
notes: [hdrNote, "Filters sit left of the results and stack above them under 860 px. Fill empty only when there are no results."],
|
|
341
341
|
},
|
|
342
|
+
PageArticle: {
|
|
343
|
+
description: "Article template: cover, post body, sticky table of contents, share row and related posts.",
|
|
344
|
+
props: header,
|
|
345
|
+
slots: {
|
|
346
|
+
...headSlots,
|
|
347
|
+
cover: "Cover image area with title and author line; a gradient Card or LazyImage.",
|
|
348
|
+
body: "Article text; Heading, Text, Code, Alert.",
|
|
349
|
+
toc: "Sticky table of contents; Card with a list of anchor links.",
|
|
350
|
+
share: "Share and reaction row; Button, IconButton, CopyButton.",
|
|
351
|
+
related: "Related posts; Card grid.",
|
|
352
|
+
},
|
|
353
|
+
notes: [hdrNote, "Body, share and related share the main column; the table of contents stays visible on the right while the body scrolls and drops under the body below 860 px."],
|
|
354
|
+
},
|
|
355
|
+
PageChangelog: {
|
|
356
|
+
description: "Changelog template: release timeline grouped by version with new, fix and improved tags, a filter and a subscribe card.",
|
|
357
|
+
props: header,
|
|
358
|
+
slots: {
|
|
359
|
+
...headSlots,
|
|
360
|
+
filters: "Tag filter; FilterChips, SearchInput.",
|
|
361
|
+
timeline: "Releases grouped by version; Card, Chip, Divider.",
|
|
362
|
+
subscribe: "Sticky subscribe card; Card, TextField, Button.",
|
|
363
|
+
},
|
|
364
|
+
notes: [hdrNote, "Filters and timeline share the main column; the subscribe card is a sticky side column."],
|
|
365
|
+
},
|
|
366
|
+
PageHelp: {
|
|
367
|
+
description: "Help center home: search, category grid, popular articles and a contact card.",
|
|
368
|
+
props: header,
|
|
369
|
+
slots: {
|
|
370
|
+
...headSlots,
|
|
371
|
+
search: "Large search field; SearchInput.",
|
|
372
|
+
categories: "Category tiles; Card with IconTile.",
|
|
373
|
+
popular: "Popular articles list; Card.",
|
|
374
|
+
contact: "Contact options; Card, Button.",
|
|
375
|
+
},
|
|
376
|
+
notes: [hdrNote, "Popular articles and contact share a row and wrap on narrow screens. For a single question list use PageFaq."],
|
|
377
|
+
},
|
|
378
|
+
PageStates: {
|
|
379
|
+
description: "State gallery: empty list, no results, no permission, offline, error with retry, loading and success on one page.",
|
|
380
|
+
props: header,
|
|
381
|
+
slots: {
|
|
382
|
+
...headSlots,
|
|
383
|
+
empty: "Empty list; EmptyState with a primary action.",
|
|
384
|
+
noresults: "No search results; EmptyState with a reset action.",
|
|
385
|
+
forbidden: "No permission; EmptyState with a request-access action.",
|
|
386
|
+
offline: "Offline; DataState state=\"offline\".",
|
|
387
|
+
error: "Error with retry; DataState state=\"error\".",
|
|
388
|
+
loading: "Loading; Skeleton or DataState state=\"loading\".",
|
|
389
|
+
success: "Success confirmation; Alert tone=\"success\" or EmptyState.",
|
|
390
|
+
},
|
|
391
|
+
notes: [hdrNote, "The first six slots sit in two rows of auto-fit cards; success spans the full width."],
|
|
392
|
+
},
|
|
393
|
+
PageLegal: {
|
|
394
|
+
description: "Legal document template: last-updated line, sticky table of contents, numbered sections and a contact card.",
|
|
395
|
+
props: header,
|
|
396
|
+
slots: {
|
|
397
|
+
...headSlots,
|
|
398
|
+
meta: "Version, last-updated date and print or download buttons; Chip, Button.",
|
|
399
|
+
toc: "Sticky table of contents; Card with anchor links.",
|
|
400
|
+
document: "Document sections; Card, Heading, Text.",
|
|
401
|
+
contact: "Questions card; Card, Button.",
|
|
402
|
+
},
|
|
403
|
+
notes: [hdrNote, "The table of contents sits left of the document and stacks above it under 860 px."],
|
|
404
|
+
},
|
|
405
|
+
PageLocations: {
|
|
406
|
+
description: "Locations template: filters, a scrollable list of places and a sticky map panel.",
|
|
407
|
+
props: header,
|
|
408
|
+
slots: {
|
|
409
|
+
...headSlots,
|
|
410
|
+
filters: "Search and category filters; SearchInput, FilterChips.",
|
|
411
|
+
list: "Places list; Card rows.",
|
|
412
|
+
map: "Map panel; Map, or a gradient placeholder.",
|
|
413
|
+
},
|
|
414
|
+
notes: [hdrNote, "The list is a fixed-width column left of the map; under 860 px the map goes below the list."],
|
|
415
|
+
},
|
|
416
|
+
PageBookingFlow: {
|
|
417
|
+
description: "Booking flow template: stepper, the active step content, back and continue buttons and a sticky summary card.",
|
|
418
|
+
props: header,
|
|
419
|
+
slots: {
|
|
420
|
+
...headSlots,
|
|
421
|
+
steps: "Progress; Stepper.",
|
|
422
|
+
content: "Content of the active step (search, pick, guests, confirm); Card, DatePicker, OptionCards, NumberInput.",
|
|
423
|
+
nav: "Back and Continue buttons.",
|
|
424
|
+
summary: "Booking summary; Card, Descriptions.",
|
|
425
|
+
},
|
|
426
|
+
notes: [hdrNote, "Render only the active step in content. The summary stays visible on the right and goes below the step under 860 px."],
|
|
427
|
+
},
|
|
428
|
+
PageItinerary: {
|
|
429
|
+
description: "Itinerary template: day tabs, a timeline of stops for the chosen day, transport and hotel blocks and a sticky trip summary card.",
|
|
430
|
+
props: header,
|
|
431
|
+
slots: {
|
|
432
|
+
...headSlots,
|
|
433
|
+
days: "Day switcher; Tabs or PillTabs.",
|
|
434
|
+
timeline: "Stops of the active day with times and places; Card, Timeline, StatRow.",
|
|
435
|
+
transport: "Flights, buses and transfers of the day; Card, StatRow.",
|
|
436
|
+
hotel: "Where the group stays that night; Card, Badge, IconTile.",
|
|
437
|
+
summary: "Trip summary; Card, Descriptions, Progress.",
|
|
438
|
+
},
|
|
439
|
+
notes: [hdrNote, "Render only the active day in timeline, transport and hotel. The summary stays visible on the right and goes below the day under 860 px."],
|
|
440
|
+
},
|
|
441
|
+
PagePrayer: {
|
|
442
|
+
description: "Prayer times template: a location picker, today's times with the next prayer highlighted, a month table and a sticky Qibla compass.",
|
|
443
|
+
props: header,
|
|
444
|
+
slots: {
|
|
445
|
+
...headSlots,
|
|
446
|
+
location: "City and method pickers; Select, PillSelect, SegmentedControl.",
|
|
447
|
+
today: "Today's prayers and the countdown to the next one; Card, PrayerTimes.",
|
|
448
|
+
month: "Prayer times for the whole month; Card, DataTable.",
|
|
449
|
+
qibla: "Qibla direction; Card, QiblaCompass.",
|
|
450
|
+
},
|
|
451
|
+
notes: [hdrNote, "The Qibla card stays visible on the right and goes below the times under 860 px."],
|
|
452
|
+
},
|
|
453
|
+
PageGroupManager: {
|
|
454
|
+
description: "Group manager template: trip summary, pilgrims table with document and visa status, bulk actions, rooming and a sticky payments summary.",
|
|
455
|
+
props: header,
|
|
456
|
+
slots: {
|
|
457
|
+
...headSlots,
|
|
458
|
+
trip: "Trip header with dates, route and counters; Card, StatStrip, Progress.",
|
|
459
|
+
toolbar: "Search and status filters; SearchInput, FilterChips.",
|
|
460
|
+
bulk: "Actions for the selected pilgrims; Button, Badge.",
|
|
461
|
+
pilgrims: "Pilgrims table with documents and visa; Card, DataTable, Badge.",
|
|
462
|
+
rooms: "Rooming assignment; Card, RoomGrid.",
|
|
463
|
+
payments: "Payments summary and manager contact; Card, Progress, ContactPanel.",
|
|
464
|
+
},
|
|
465
|
+
notes: [hdrNote, "The payments card stays visible on the right and goes below the table under 860 px."],
|
|
466
|
+
},
|
|
467
|
+
PageTourPublic: {
|
|
468
|
+
description: "Public tour page: header, gradient gallery area, highlights, itinerary accordion, included and excluded lists, reviews and a sticky price and booking card. Has no shell and follows the theme.",
|
|
469
|
+
props: header,
|
|
470
|
+
slots: {
|
|
471
|
+
...headSlots,
|
|
472
|
+
gallery: "Hero area made of gradients or photos; Gallery, Card.",
|
|
473
|
+
highlights: "Key facts and highlights; StatStrip, IconTile.",
|
|
474
|
+
itinerary: "Day by day program; Accordion.",
|
|
475
|
+
included: "What the price includes and excludes; Card, Descriptions.",
|
|
476
|
+
reviews: "Pilgrim reviews; Card, Rating, Avatar.",
|
|
477
|
+
booking: "Price, dates, seats and the booking button; Card, Button.",
|
|
478
|
+
},
|
|
479
|
+
notes: ["Render the page on its own, without AppShell: it supplies its own width and padding in your layout.", "The booking card stays visible on the right and goes below the content under 860 px."],
|
|
480
|
+
},
|
|
342
481
|
PageAuth: {
|
|
343
482
|
description: 'Sign-in page template without the app shell, as a centered card or a split layout.',
|
|
344
483
|
props: {
|