@ziyarago/ui 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE +25 -0
  3. package/README.md +121 -2
  4. package/THIRD_PARTY_NOTICES.md +12 -0
  5. package/assets/3d/_sheet.png +0 -0
  6. package/assets/3d/archive.png +0 -0
  7. package/assets/3d/bag.png +0 -0
  8. package/assets/3d/cameraoff.png +0 -0
  9. package/assets/3d/comment.png +0 -0
  10. package/assets/3d/crowd.png +0 -0
  11. package/assets/3d/firstaid.png +0 -0
  12. package/assets/3d/flag.png +0 -0
  13. package/assets/3d/grain.png +0 -0
  14. package/assets/3d/guide.png +0 -0
  15. package/assets/3d/hotel.png +0 -0
  16. package/assets/3d/kaaba.png +0 -0
  17. package/assets/3d/logo.png +0 -0
  18. package/assets/3d/luggage.png +0 -0
  19. package/assets/3d/map.png +0 -0
  20. package/assets/3d/mosque.png +0 -0
  21. package/assets/3d/plane.png +0 -0
  22. package/assets/3d/profile.png +0 -0
  23. package/assets/3d/restaurant.png +0 -0
  24. package/assets/3d/ring360.png +0 -0
  25. package/assets/3d/shoes.png +0 -0
  26. package/assets/3d/star.png +0 -0
  27. package/assets/3d/store.png +0 -0
  28. package/assets/3d/taxi.png +0 -0
  29. package/assets/3d/threads.png +0 -0
  30. package/assets/3d/tour360.png +0 -0
  31. package/assets/3d/trash.png +0 -0
  32. package/assets/3d/umbrella.png +0 -0
  33. package/assets/3d/video.png +0 -0
  34. package/assets/3d/water.png +0 -0
  35. package/assets/brand/mark.png +0 -0
  36. package/assets/brand/ziyarago-logo-light.svg +21 -0
  37. package/assets/brand/ziyarago-logo.svg +21 -0
  38. package/assets/brand/ziyarago-mark-light.svg +13 -0
  39. package/assets/brand/ziyarago-mark.svg +13 -0
  40. package/assets/fonts/thmanyah/thmanyahsans-Black.otf +0 -0
  41. package/assets/fonts/thmanyah/thmanyahsans-Bold.otf +0 -0
  42. package/assets/fonts/thmanyah/thmanyahsans-Light.otf +0 -0
  43. package/assets/fonts/thmanyah/thmanyahsans-Medium.otf +0 -0
  44. package/assets/fonts/thmanyah/thmanyahsans-Regular.otf +0 -0
  45. package/assets/fonts/thmanyah/thmanyahserifdisplay-Black.otf +0 -0
  46. package/assets/fonts/thmanyah/thmanyahserifdisplay-Bold.otf +0 -0
  47. package/assets/fonts/thmanyah/thmanyahserifdisplay-Light.otf +0 -0
  48. package/assets/fonts/thmanyah/thmanyahserifdisplay-Medium.otf +0 -0
  49. package/assets/fonts/thmanyah/thmanyahserifdisplay-Regular.otf +0 -0
  50. package/assets/fonts/thmanyah/thmanyahseriftext-Black.otf +0 -0
  51. package/assets/fonts/thmanyah/thmanyahseriftext-Bold.otf +0 -0
  52. package/assets/fonts/thmanyah/thmanyahseriftext-Light.otf +0 -0
  53. package/assets/fonts/thmanyah/thmanyahseriftext-Medium.otf +0 -0
  54. package/assets/fonts/thmanyah/thmanyahseriftext-Regular.otf +0 -0
  55. package/dist/llms-full.txt +2293 -0
  56. package/dist/llms.txt +239 -0
  57. package/dist/manifest.json +10254 -0
  58. package/dist/style.css +1357 -0
  59. package/dist/tokens.css +48 -0
  60. package/package.json +118 -4
  61. package/src/catalog.js +4 -0
  62. package/src/core/components.js +124 -0
  63. package/src/core/components2.js +163 -0
  64. package/src/core/components3.js +35 -0
  65. package/src/core/components4.js +58 -0
  66. package/src/core/components6.js +48 -0
  67. package/src/core/components7.js +42 -0
  68. package/src/core/dialog.js +65 -0
  69. package/src/core/index.js +9 -0
  70. package/src/core/pages.js +51 -0
  71. package/src/meta/README.md +29 -0
  72. package/src/meta/actions.js +161 -0
  73. package/src/meta/data.js +327 -0
  74. package/src/meta/domain.js +212 -0
  75. package/src/meta/feedback.js +153 -0
  76. package/src/meta/forms.js +453 -0
  77. package/src/meta/groups.js +16 -0
  78. package/src/meta/index.js +14 -0
  79. package/src/meta/layout.js +159 -0
  80. package/src/meta/media.js +104 -0
  81. package/src/meta/navigation.js +99 -0
  82. package/src/meta/overlay.js +264 -0
  83. package/src/meta/pages.js +229 -0
  84. package/src/meta/templates.json +2422 -0
  85. package/src/meta/typography.js +58 -0
  86. package/src/nuxt/module.mjs +29 -0
  87. package/src/nuxt/plugin.mjs +9 -0
  88. package/src/react/index.js +31 -0
  89. package/src/styles/components.css +162 -0
  90. package/src/styles/components2.css +68 -0
  91. package/src/styles/components3.css +29 -0
  92. package/src/styles/components4.css +18 -0
  93. package/src/styles/components5.css +5 -0
  94. package/src/styles/components6.css +19 -0
  95. package/src/styles/components7.css +44 -0
  96. package/src/styles/components8.css +47 -0
  97. package/src/styles/effects.css +43 -0
  98. package/src/styles/fonts.css +17 -0
  99. package/src/styles/index.css +21 -0
  100. package/src/styles/sizes/actions.css +126 -0
  101. package/src/styles/sizes/data.css +161 -0
  102. package/src/styles/sizes/domain.css +35 -0
  103. package/src/styles/sizes/feedback.css +69 -0
  104. package/src/styles/sizes/forms.css +122 -0
  105. package/src/styles/sizes/layout.css +15 -0
  106. package/src/styles/sizes/media.css +28 -0
  107. package/src/styles/sizes/navigation.css +146 -0
  108. package/src/styles/sizes/overlay.css +79 -0
  109. package/src/styles/sizes/typography.css +12 -0
  110. package/src/styles/tokens.css +47 -0
  111. package/src/theme.js +52 -0
  112. package/src/vue/index.js +29 -0
  113. package/tokens/tokens.json +9 -0
  114. package/types/core.d.ts +4 -0
  115. package/types/nuxt.d.ts +11 -0
  116. package/types/react.d.ts +189 -0
  117. package/types/shared.d.ts +18 -0
  118. package/types/theme.d.ts +6 -0
  119. package/types/vue.d.ts +192 -0
@@ -0,0 +1,327 @@
1
+ // API metadata for the data group. Format: see src/meta/README.md
2
+ const SZ = ['xs', 'sm', 'md', 'lg', 'xl'];
3
+ const SIZE = "'xs' | 'sm' | 'md' | 'lg' | 'xl'";
4
+ const TONES = ['brand', 'sky', 'success', 'warning', 'danger', 'neutral'];
5
+ const TONE = "'brand' | 'sky' | 'success' | 'warning' | 'danger' | 'neutral'";
6
+ const size = (d, extra) => ({ type: SIZE, values: SZ, default: d || 'md', description: extra || 'Component scale.' });
7
+ const delta = (d) => ({ type: 'string', description: d || 'Change indicator such as "+12%" or "-3.4%". A leading "-" renders as a decrease, anything else as an increase.' });
8
+
9
+ export default {
10
+ Card: {
11
+ description: 'Glass surface container with an optional title header, used to group related content.',
12
+ props: {
13
+ title: { type: 'string', description: 'Header title. The header renders only when title or the actions slot is set.' },
14
+ subtitle: { type: 'string', description: 'Muted line under the title.' },
15
+ variant: { type: "'inverse' | 'gradient'", values: ['inverse', 'gradient'], description: 'inverse is a dark card regardless of the page theme; gradient is a tinted gradient card colored by tone. Omit for the default glass card.' },
16
+ tone: { type: "'brand' | 'sky' | 'success' | 'warning' | 'danger' | 'neutral' | 'rose' | 'violet'", values: ['brand', 'sky', 'success', 'warning', 'danger', 'neutral', 'rose', 'violet'], default: 'brand', description: 'Gradient color. Used only with variant="gradient".' },
17
+ intensity: { type: "'vivid'", values: ['vivid'], description: 'With variant="gradient", fills the card with a saturated color instead of a soft tint.' },
18
+ size: size('md', 'Padding, gap and title size.'),
19
+ },
20
+ slots: { default: 'Card body.', actions: 'Right side of the header, for buttons or menus.' },
21
+ notes: ['Vue slot names are React props (actions) and children (default).'],
22
+ },
23
+
24
+ DataTable: {
25
+ description: 'Table with sortable columns, custom cell rendering and optional row selection checkboxes.',
26
+ props: {
27
+ columns: { type: '{ key: string; label: string; sortable?: boolean; num?: boolean; render?: (value, row, h) => VNode | string }[]', required: true, default: [], description: 'Column definitions. key reads row[key]; num right-aligns numbers; render customizes the cell; sortable shows a sort button in the header.' },
28
+ rows: { type: '({ id: string | number; [key: string]: any })[]', required: true, default: [], description: 'Row objects. Every row needs a unique id; missing cell values render as an em dash.' },
29
+ selected: { type: '(string | number)[]', default: [], description: 'Ids of selected rows. Checkboxes show only when the select event is handled.' },
30
+ sortKey: { type: 'string', description: 'Key of the column currently sorted. The component does not sort rows itself.' },
31
+ sortDir: { type: "'asc' | 'desc'", values: ['asc', 'desc'], default: 'asc', description: 'Direction of the current sort, shown by the header arrow and aria-sort.' },
32
+ empty: { type: 'string', default: '—', description: 'Text shown when rows is empty.' },
33
+ labels: { type: '{ selectAll?: string; selectRow?: string }', default: "{ selectAll: 'Select all', selectRow: 'Select row' }", description: 'Accessible names of the selection checkboxes.' },
34
+ size: size('md', 'Row density and font size.'),
35
+ },
36
+ events: {
37
+ select: 'Called with the new array of selected row ids. Handling it enables the checkbox column and select-all.',
38
+ sort: 'Called with (key, dir) where dir is "asc" or "desc". Sort the rows yourself and pass sortKey/sortDir back.',
39
+ },
40
+ notes: ['Sorting and selection are fully controlled: the table never reorders rows or stores selection.', 'render receives (value, row) and, in React/Vue, the h function as third argument.'],
41
+ },
42
+
43
+ DataGroup: {
44
+ description: 'Table whose rows are collapsed into labeled groups, with optional per-row expandable details.',
45
+ props: {
46
+ columns: { type: '{ key: string; label: string; num?: boolean; render?: (value, row) => VNode | string }[]', required: true, default: [], description: 'Column definitions, same shape as DataTable without sorting.' },
47
+ groups: { type: '{ label: string; rows: ({ id: string | number; [key: string]: any })[] }[]', required: true, default: [], description: 'Groups shown as collapsible header rows with a row count. Group labels must be unique.' },
48
+ expand: { type: '(row) => VNode | string', description: 'Renders the detail panel under a row. When set, an expand chevron column appears.' },
49
+ size: size('md', 'Row density and font size.'),
50
+ },
51
+ notes: ['Open/collapsed state is internal; groups start expanded.'],
52
+ },
53
+
54
+ Descriptions: {
55
+ description: 'Definition list of label and value pairs for record details.',
56
+ props: {
57
+ items: { type: '{ label: string; value?: string | number | VNode }[]', required: true, default: [], description: 'Pairs rendered as dt/dd. Missing value renders an em dash.' },
58
+ size: size('md', 'Font size and spacing.'),
59
+ },
60
+ },
61
+
62
+ Timeline: {
63
+ description: 'Vertical list of events connected by a line, for activity history and status progress.',
64
+ props: {
65
+ items: { type: '{ title: string; text?: string; time: string }[]', required: true, default: [], description: 'Events from top to bottom. title is bold, text is optional detail, time is the caption.' },
66
+ size: size('md', 'Dot, spacing and font size.'),
67
+ },
68
+ },
69
+
70
+ TreeView: {
71
+ description: 'Expandable tree of nodes for hierarchies such as folders, categories or regions.',
72
+ props: {
73
+ nodes: { type: '{ id: string; label: string; icon?: string; children?: Node[] }[]', required: true, default: [], description: 'Root nodes. children nest recursively; icon is an icon name. Ids must be unique.' },
74
+ selected: { type: 'string', description: 'Id of the highlighted node.' },
75
+ size: size('md', 'Row height and font size.'),
76
+ },
77
+ events: { select: 'Called with the node id when a node is clicked. Nodes with children also toggle open.' },
78
+ notes: ['The first level is open by default; deeper levels start collapsed.'],
79
+ },
80
+
81
+ CalendarView: {
82
+ description: 'Month calendar that marks days with events, wrapping Calendar with a list-style events prop.',
83
+ props: {
84
+ events: { type: '{ date: string; label: string }[]', default: [], description: 'Marked days. date is "YYYY-MM-DD"; label shows as the day tooltip.' },
85
+ modelValue: { type: 'string | number | Date', model: true, forwarded: true, description: 'Selected date. Vue v-model, React modelValue + onChange.' },
86
+ locale: { type: 'string', default: 'en-GB', forwarded: true, description: 'BCP 47 locale for month and weekday names.' },
87
+ weekStartsOn: { type: '0 | 1 | 2 | 3 | 4 | 5 | 6', values: [0, 1, 2, 3, 4, 5, 6], default: 1, forwarded: true, description: 'First day of the week, 0 is Sunday and 1 is Monday.' },
88
+ },
89
+ events: { change: 'Called with the clicked Date (local time). Forwarded to Calendar.' },
90
+ },
91
+
92
+ KpiTile: {
93
+ description: 'Key metric tile with a label, large value and optional delta, for dashboards.',
94
+ props: {
95
+ label: { type: 'string', required: true, description: 'Metric name.' },
96
+ value: { type: 'string | number', required: true, description: 'Main value, shown large.' },
97
+ sub: { type: 'string', description: 'Small text next to the delta.' },
98
+ delta: delta(),
99
+ variant: { type: "'brand' | 'inverse'", values: ['brand', 'inverse'], description: 'Filled brand tile or dark inverse tile. Omit for the default glass tile.' },
100
+ size: size('md', 'Padding and value size.'),
101
+ },
102
+ },
103
+
104
+ KpiInline: {
105
+ description: 'Compact inline metric with a label, value and delta, for toolbars and card headers.',
106
+ props: {
107
+ label: { type: 'string', required: true, description: 'Metric name.' },
108
+ value: { type: 'string | number', required: true, description: 'Main value.' },
109
+ delta: delta(),
110
+ size: size('md', 'Value font size.'),
111
+ },
112
+ },
113
+
114
+ StatsCard: {
115
+ description: 'Metric card with an optional icon, value and an up or down trend.',
116
+ props: {
117
+ label: { type: 'string', required: true, description: 'Metric name.' },
118
+ value: { type: 'string | number', required: true, description: 'Main value.' },
119
+ icon: { type: 'string', description: 'Icon name shown before the label.' },
120
+ delta: { type: 'string', description: 'Change text such as "12%". The arrow is added automatically from trend.' },
121
+ trend: { type: "'up' | 'down'", values: ['up', 'down'], default: 'up', description: 'Direction and color of the delta.' },
122
+ size: size('md', 'Padding and value size.'),
123
+ },
124
+ },
125
+
126
+ StatRow: {
127
+ description: 'List row with an icon tile, title, subtitle and a right-aligned value with delta.',
128
+ props: {
129
+ title: { type: 'string', required: true, description: 'Primary text.' },
130
+ sub: { type: 'string', description: 'Muted secondary text.' },
131
+ value: { type: 'string | number', description: 'Right-aligned value.' },
132
+ delta: delta('Change shown under the value. A leading "-" is a decrease; the sign is stripped and an arrow added.'),
133
+ icon: { type: 'string', description: 'Icon name for the leading tile.' },
134
+ tone: { type: TONE, values: TONES, default: 'brand', description: 'Color of the icon tile. Unknown values fall back to brand.' },
135
+ size: { type: "'xs' | 'sm' | 'md' | 'lg' | 'xl' | number", values: SZ, default: 'md', description: 'Row height and icon tile size. Named sizes map to a 28/34/40/48/56 px tile; a number is used as the tile size in px.' },
136
+ },
137
+ },
138
+
139
+ StatLine: {
140
+ description: 'Card with a large value, icon tile, label and an accent bar, for a single headline number.',
141
+ props: {
142
+ label: { type: 'string', required: true, description: 'Metric name under the value.' },
143
+ value: { type: 'string | number', required: true, description: 'Main value.' },
144
+ icon: { type: 'string', description: 'Icon name for the tile.' },
145
+ tone: { type: TONE, values: TONES, default: 'brand', description: 'Accent color of the tile and bar. Unknown values fall back to brand.' },
146
+ delta: delta(),
147
+ size: { type: "'xs' | 'sm' | 'md' | 'lg' | 'xl' | number", values: SZ, default: 'md', description: 'Icon tile size. Named sizes map to 28/34/40/48/56 px; a number is used in px.' },
148
+ },
149
+ },
150
+
151
+ StatStrip: {
152
+ description: 'Horizontal strip of several icon, value and label stats in one card.',
153
+ props: {
154
+ items: { type: '{ icon?: string; tone?: ' + TONE + '; value: string | number; label: string }[]', required: true, default: [], description: 'Stats shown side by side. tone defaults to brand.' },
155
+ size: { type: "'xs' | 'sm' | 'md' | 'lg' | 'xl' | number", values: SZ, default: 'md', description: 'Icon tile size. Named sizes map to 30/36/44/52/60 px; a number is used in px.' },
156
+ },
157
+ },
158
+
159
+ CountryRow: {
160
+ description: 'Row with a country code badge, amount, country name and a percentage change.',
161
+ props: {
162
+ code: { type: 'string', required: true, description: 'Short code shown in the circle, such as "UZ".' },
163
+ name: { type: 'string', required: true, description: 'Country name.' },
164
+ amount: { type: 'string | number', required: true, description: 'Formatted amount shown in bold.' },
165
+ pct: delta('Percentage change such as "+8%" or "-2%".'),
166
+ },
167
+ },
168
+
169
+ VersusBar: {
170
+ description: 'Head-to-head comparison of two values with a proportional two-color bar.',
171
+ props: {
172
+ left: { type: '{ label: string; value: number }', required: true, description: 'Left side. Its share of the bar is value / (left.value + right.value) in the brand color.' },
173
+ right: { type: '{ label: string; value: number }', required: true, description: 'Right side, filling the remainder of the bar in sky.' },
174
+ size: size('md', 'Value font size and bar thickness.'),
175
+ },
176
+ notes: ['A missing side renders an empty label and 0; with both at 0 the bar is split with no brand share.'],
177
+ },
178
+
179
+ SegBar: {
180
+ description: 'Single stacked bar split into proportional segments with a legend, for distributions.',
181
+ props: {
182
+ segments: { type: '{ label: string; value: number; color?: string }[]', required: true, default: [], description: 'Segments; widths are value / sum of values. color is any CSS color; the default cycles brand, sky, green, amber, red.' },
183
+ },
184
+ },
185
+
186
+ MiniChart: {
187
+ description: 'Tiny sparkline in bar or line form for trends inside cards and tables.',
188
+ props: {
189
+ values: { type: 'number[]', required: true, default: [], description: 'Non-negative data points, scaled to the maximum value.' },
190
+ type: { type: "'bar' | 'line'", values: ['bar', 'line'], default: 'bar', description: 'Bar columns or a single line.' },
191
+ highlightLast: { type: 'boolean', default: false, description: 'Bar type only: paints the last bar in the brand color.' },
192
+ height: { type: 'number', description: 'Height in px. Overrides size when set.' },
193
+ size: { type: SIZE, values: SZ, default: 'md', description: 'Preset height: xs 28, sm 36, md 48, lg 64, xl 80 px.' },
194
+ },
195
+ notes: ['The chart is decorative (no axes or values); pair it with a visible number.'],
196
+ },
197
+
198
+ ChartCard: {
199
+ description: 'Card shell for a chart with title, subtitle and an optional overflow menu; put the chart in the default slot.',
200
+ props: {
201
+ title: { type: 'string', required: true, description: 'Card title.' },
202
+ subtitle: { type: 'string', description: 'Muted line under the title.' },
203
+ menu: { type: 'string[]', description: 'Labels of overflow menu items shown behind a three-dot button. An item named "Delete" is styled as danger.' },
204
+ size: size('md', 'Padding, gap and title size.'),
205
+ },
206
+ slots: { default: 'Chart or any content shown below the header.' },
207
+ events: { menu: 'Called with the clicked menu item label (the string from menu); the menu closes afterwards.' },
208
+ },
209
+
210
+ Ring: {
211
+ description: 'Circular progress gauge with a percentage in the center and a tick scale around it.',
212
+ props: {
213
+ value: { type: 'number', required: true, default: 0, description: 'Progress from 0 to 100; values are clamped.' },
214
+ label: { type: 'string', description: 'Caption under the percentage.' },
215
+ color: { type: 'string', default: 'var(--zg-brand)', description: 'Any CSS color for the arc and ticks.' },
216
+ thickness: { type: 'number', description: 'Stroke width in px. Defaults to about 8% of the diameter, minimum 8.' },
217
+ ticks: { type: 'boolean', default: true, description: 'Shows the outer 60-tick scale; false removes it and enlarges the arc.' },
218
+ valueSize: { type: 'number', description: 'Percentage font size in px. Defaults to 22% of the diameter.' },
219
+ size: { type: "'xs' | 'sm' | 'md' | 'lg' | 'xl' | number", values: SZ, default: 'md', description: 'Diameter: xs 72, sm 120, md 180, lg 240, xl 300 px; a number is used as px.' },
220
+ },
221
+ },
222
+
223
+ OccupancyChart: {
224
+ description: 'Bar chart of percentages by label, such as hotel occupancy per day or month.',
225
+ props: {
226
+ values: { type: 'number[]', required: true, default: [], description: 'Percentages from 0 to 100, one per bar. Values above 90 are highlighted in brand color.' },
227
+ labels: { type: 'string[]', default: [], description: 'Axis label under each bar, matched by index.' },
228
+ },
229
+ },
230
+
231
+ SalesFunnel: {
232
+ description: 'Horizontal funnel of steps with bars scaled to the largest value, for conversion flows.',
233
+ props: {
234
+ steps: { type: '{ label: string; value: number }[]', required: true, default: [], description: 'Steps from top to bottom. Bars scale to the max value; the last step is highlighted in brand color.' },
235
+ },
236
+ },
237
+
238
+ SystemStatus: {
239
+ description: 'List of services with a status dot and a value such as uptime or latency.',
240
+ props: {
241
+ items: { type: "{ name: string; value: string; state?: 'ok' | 'warn' | 'down' }[]", required: true, default: [], description: 'Services. state sets the color: ok green (default), warn amber, down red.' },
242
+ },
243
+ },
244
+
245
+ UserCell: {
246
+ description: 'Avatar with a name and optional subtitle, for table cells and lists of people.',
247
+ props: {
248
+ name: { type: 'string', required: true, description: 'Display name, also used for avatar initials.' },
249
+ sub: { type: 'string', description: 'Muted line under the name, such as an email or role.' },
250
+ src: { type: 'string', description: 'Avatar image URL. Initials are shown without it.' },
251
+ size: { type: "'xs' | 'sm' | 'md' | 'lg' | 'xl' | number", values: SZ, default: 'md', description: 'Avatar diameter: xs 24, sm 30, md 36, lg 44, xl 56 px; a number is used as px.' },
252
+ },
253
+ },
254
+
255
+ IconTile: {
256
+ description: 'Square or circular tinted tile holding an icon, used as a leading visual in lists and cards.',
257
+ props: {
258
+ icon: { type: 'string', default: 'circle', description: 'Icon name.' },
259
+ tone: { type: TONE, values: TONES, default: 'brand', description: 'Tint color. Unknown values fall back to brand.' },
260
+ shape: { type: "'square' | 'circle'", values: ['square', 'circle'], default: 'square', description: 'Rounded square or full circle. Only "circle" changes the shape.' },
261
+ size: { type: "'xs' | 'sm' | 'md' | 'lg' | 'xl' | number", values: SZ, default: 'md', description: 'Tile size: xs 28, sm 32, md 40, lg 48, xl 56 px; a number is used as px. The icon is half the tile.' },
262
+ },
263
+ },
264
+
265
+ StatusIcon: {
266
+ description: 'Round tinted icon with a hover and focus tooltip, for compact status markers.',
267
+ props: {
268
+ icon: { type: 'string', default: 'circle', description: 'Icon name.' },
269
+ tone: { type: TONE, values: TONES, default: 'brand', description: 'Tint color. Unknown values fall back to brand.' },
270
+ tip: { type: 'string', description: 'Tooltip text shown on hover and keyboard focus.' },
271
+ size: { type: "'xs' | 'sm' | 'md' | 'lg' | 'xl' | number", values: SZ, default: 'md', description: 'Diameter: xs 24, sm 30, md 36, lg 44, xl 52 px; a number is used as px.' },
272
+ },
273
+ notes: ['Focusable (tabindex 0) so the tooltip works from the keyboard; the icon itself is aria-hidden, so put the meaning in tip.'],
274
+ },
275
+
276
+ HeroBanner: {
277
+ description: 'Wide promotional banner with title, text, a big value, optional action and a background image.',
278
+ props: {
279
+ title: { type: 'string', required: true, description: 'Banner heading.' },
280
+ text: { type: 'string', description: 'Supporting paragraph.' },
281
+ value: { type: 'string | number', description: 'Large number or price shown under the text.' },
282
+ cta: { type: 'string', description: 'Label of the primary button. The button renders only when set.' },
283
+ image: { type: 'string', description: 'Image URL shown on the right side.' },
284
+ },
285
+ events: { cta: 'Called when the call-to-action button is clicked.' },
286
+ slots: { default: 'Extra content placed before the button.' },
287
+ },
288
+
289
+ TabTiles: {
290
+ description: 'Row of icon tiles that work as tabs, with an optional add button.',
291
+ props: {
292
+ items: { type: '{ value: string; label: string; icon?: string }[]', required: true, default: [], description: 'Tiles. icon defaults to circle.' },
293
+ value: { type: 'string', description: 'Value of the active tile. Controlled; change it in the change handler.' },
294
+ addable: { type: 'boolean', default: false, description: 'Shows a trailing plus tile that fires the add event.' },
295
+ },
296
+ events: { change: 'Called with the value of the clicked tile.', add: 'Called when the plus tile is clicked.' },
297
+ notes: ['Active state is exposed as aria-pressed on buttons, not as a tablist.'],
298
+ },
299
+
300
+ PricingCard: {
301
+ description: 'Pricing plan card with price, feature list and a select button.',
302
+ props: {
303
+ name: { type: 'string', required: true, description: 'Plan name.' },
304
+ price: { type: 'string | number', required: true, description: 'Price text, shown large.' },
305
+ period: { type: 'string', description: 'Suffix after the price, such as "/mo".' },
306
+ features: { type: 'string[]', default: [], description: 'Feature bullets with check icons.' },
307
+ badge: { type: 'string', description: 'Brand badge next to the name, such as "Popular".' },
308
+ featured: { type: 'boolean', default: false, description: 'Highlights the card and makes the button primary.' },
309
+ cta: { type: 'string', default: 'Choose', description: 'Button label.' },
310
+ size: size('md', 'Card padding and title size.'),
311
+ },
312
+ events: { select: 'Called when the button is clicked.' },
313
+ },
314
+
315
+ IntegrationCard: {
316
+ description: 'Card for a third-party integration showing its connection status and a connect or manage button.',
317
+ props: {
318
+ name: { type: 'string', required: true, description: 'Integration name.' },
319
+ description: { type: 'string', description: 'Short explanation under the name.' },
320
+ status: { type: "'connected' | 'error' | 'off'", values: ['connected', 'error', 'off'], default: 'off', description: 'Connection state. Sets the badge (Connected, Error, Not linked) and button (Manage, Reconnect, Link).' },
321
+ labels: { type: '{ connected?: string; error?: string; off?: string; manage?: string; reconnect?: string; link?: string }', default: "{ connected: 'Connected', error: 'Error', off: 'Not linked', manage: 'Manage', reconnect: 'Reconnect', link: 'Link' }", description: 'Badge and button texts per status; missing keys keep the English defaults.' },
322
+ size: size('md', 'Card padding and title size.'),
323
+ },
324
+ events: { toggle: 'Called when the button is clicked, for connecting, reconnecting or managing.' },
325
+ notes: ['Translate badge and button texts with labels.', 'Unknown status values behave as "off".'],
326
+ },
327
+ };
@@ -0,0 +1,212 @@
1
+ // API metadata for the domain group. Format: see src/meta/README.md
2
+ const SZ = "'xs' | 'sm' | 'md' | 'lg' | 'xl'";
3
+ const size = { type: SZ, values: ['xs', 'sm', 'md', 'lg', 'xl'], default: 'md', description: 'Scales padding, text and avatars.' };
4
+ const TONES = "'default' | 'brand' | 'success' | 'warning' | 'danger' | 'info'";
5
+ const toneValues = ['default', 'brand', 'success', 'warning', 'danger', 'info'];
6
+
7
+ export default {
8
+ HotelCard: {
9
+ description: 'Hotel listing card with photo, tags, stars, distance, rating and nightly price; use in catalogs and search results.',
10
+ props: {
11
+ name: { type: 'string', required: true, description: 'Hotel name.' },
12
+ image: { type: 'string', description: 'Photo URL, used as the card background image.' },
13
+ tags: { type: 'string[]', default: '[]', description: 'Brand badges over the photo, e.g. "Haram view".' },
14
+ stars: { type: 'number', default: 0, description: 'Star class 0-5, rendered as repeated stars.' },
15
+ distance: { type: 'string', description: 'Distance caption next to the stars, e.g. "120 m to the Haram".' },
16
+ rating: { type: 'string | number', description: 'Guest rating shown in a green badge, e.g. 9.2.' },
17
+ price: { type: 'number', description: 'Price per unit, formatted with thousand separators; the currency label is fixed to SAR.' },
18
+ night: { type: 'string', default: 'night', description: 'Unit after "SAR /", e.g. "night".' },
19
+ size,
20
+ },
21
+ notes: ['Currency is hard-coded to SAR.', 'Wrap in a clickable element yourself; the card has no click handler.'],
22
+ },
23
+ TourCard: {
24
+ description: 'Tour or virtual-tour card with session count and a seat-occupancy bar.',
25
+ props: {
26
+ name: { type: 'string', required: true, description: 'Tour title.' },
27
+ city: { type: 'string', description: 'City shown in the subtitle.' },
28
+ sessions: { type: 'number', description: 'Number of sessions, shown as "N sessions".' },
29
+ booked: { type: 'number', required: true, description: 'Seats already booked.' },
30
+ seats: { type: 'number', required: true, description: 'Total seats; the bar shows booked / seats.' },
31
+ size,
32
+ },
33
+ slots: { actions: 'Footer actions, usually Buttons.' },
34
+ notes: ['A fixed "360°" badge is always shown.', 'With seats = 0 or missing the bar is empty (0%) and the meta reads "0/0".'],
35
+ },
36
+ PrayerTimes: {
37
+ description: 'Row of daily prayer times with the upcoming prayer highlighted.',
38
+ props: {
39
+ items: { type: '{ name: string; time: string }[]', default: '[]', description: 'Prayers in display order; time is shown as-is, e.g. "05:12".' },
40
+ next: { type: 'number', description: 'Index in items of the next prayer; sets aria-current on it.' },
41
+ size,
42
+ },
43
+ },
44
+ RamadanTracker: {
45
+ description: 'Thirty-day Ramadan progress strip; the last ten days and odd nights are marked.',
46
+ props: {
47
+ day: { type: 'number', required: true, description: 'Current day 1-30. Earlier days render as past, later days as upcoming.' },
48
+ size,
49
+ },
50
+ notes: ['Exposed as role="img" with aria-label "Day N of 30".'],
51
+ },
52
+ BookingCard: {
53
+ description: 'Compact booking summary: code, status, guest, hotel, dates and total.',
54
+ props: {
55
+ code: { type: 'string', required: true, description: 'Booking reference, monospaced.' },
56
+ status: { type: "'Confirmed' | 'Pending' | 'Cancelled' | string", values: ['Confirmed', 'Pending', 'Cancelled'], description: 'Status text. Confirmed is green, Pending amber, Cancelled red; any other value gets a neutral badge.' },
57
+ guest: { type: 'string', description: 'Guest name, also drives the avatar initials.' },
58
+ hotel: { type: 'string', description: 'Hotel name under the guest.' },
59
+ dates: { type: 'string', description: 'Preformatted date range, e.g. "12-19 Mar".' },
60
+ amount: { type: 'number', description: 'Total, formatted as "SAR 1 200".' },
61
+ size,
62
+ },
63
+ },
64
+ GuestCard: {
65
+ description: 'Guest profile card with avatar, country, stay length and tags.',
66
+ props: {
67
+ name: { type: 'string', required: true, description: 'Guest name.' },
68
+ country: { type: 'string', description: 'Country shown in the subtitle.' },
69
+ nights: { type: 'number', description: 'Number of nights, shown as "N nights".' },
70
+ vip: { type: 'boolean', default: false, description: 'Shows a VIP badge.' },
71
+ tags: { type: 'string[]', default: '[]', description: 'Preference tags, e.g. "Halal", "Late check-in".' },
72
+ size,
73
+ },
74
+ },
75
+ RoomGrid: {
76
+ description: 'Tile grid of rooms colored by housekeeping status; use for front-desk room selection.',
77
+ props: {
78
+ rooms: { type: '{ no: string; type: string; status: "occupied" | "clean" | "dirty" | "blocked" }[]', default: '[]', description: 'Rooms; no is the room number and the unique key, type is the room class label, status sets the tile color (any other value renders neutral).' },
79
+ statusTones: { type: "Record<string, 'brand' | 'sky' | 'success' | 'warning' | 'danger' | 'neutral'>", default: "{ occupied: 'brand', clean: 'success', dirty: 'warning', blocked: 'danger' }", description: 'Maps a room status to a tile tone. Merged over the defaults, so you can add custom statuses (e.g. { maintenance: "sky" }) or recolor known ones. Statuses without a tone render neutral.' },
80
+ size,
81
+ },
82
+ events: { select: 'Called with the room number (room.no) of the clicked tile.' },
83
+ notes: ['Each tile is a button; status is conveyed by color only, so add a legend.'],
84
+ },
85
+ RoomCalendar: {
86
+ description: 'Gantt-style occupancy calendar: rooms as rows, days as columns, bookings as bars.',
87
+ props: {
88
+ rooms: { type: '{ no: string; bookings?: { from: number; nights: number; guest: string }[] }[]', default: '[]', description: 'Rooms with their bookings; from is the zero-based day index of check-in, nights its length. guest is the cell tooltip.' },
89
+ days: { type: 'number', default: 14, description: 'Number of day columns, labelled 1..days.' },
90
+ size,
91
+ },
92
+ notes: ['Day headers are plain numbers 1..days; offset them yourself for a month view.'],
93
+ },
94
+ LeadCard: {
95
+ description: 'CRM lead card with avatar, code, phone and amount.',
96
+ props: {
97
+ name: { type: 'string', required: true, description: 'Lead name.' },
98
+ code: { type: 'string', description: 'Lead or deal code under the name, monospaced.' },
99
+ tag: { type: 'string', description: 'Optional badge text, e.g. a source or status.' },
100
+ tone: { type: TONES, values: toneValues, description: 'Badge tone for tag; neutral when omitted.' },
101
+ phone: { type: 'string', description: 'Phone number with a phone icon.' },
102
+ amount: { type: 'string | number', description: 'Amount shown on the right, rendered as given (format it yourself).' },
103
+ size,
104
+ },
105
+ },
106
+ DealCard: {
107
+ description: 'CRM deal card with stage badge, amount, win probability and owner; the card type inside PipelineBoard.',
108
+ props: {
109
+ title: { type: 'string', required: true, description: 'Deal title.' },
110
+ stage: { type: 'string', description: 'Stage badge text; no badge when omitted.' },
111
+ tone: { type: "'default' | 'brand' | 'success' | 'warning' | 'danger' | 'info'", values: toneValues, default: 'info', description: 'Badge tone for the stage.' },
112
+ amount: { type: 'string | number', description: 'Large amount, rendered as given.' },
113
+ probability: { type: 'number', default: 0, description: 'Win probability 0-100, shown as a progress bar and percent.' },
114
+ owner: { type: 'string', description: 'Owner name with avatar.' },
115
+ due: { type: 'string', description: 'Due date caption, right aligned.' },
116
+ size,
117
+ },
118
+ },
119
+ PipelineBoard: {
120
+ description: 'Horizontally scrolling kanban pipeline of stage columns filled with DealCards.',
121
+ props: {
122
+ stages: { type: '{ id: string | number; title: string; cards: DealCardProps[] }[]', default: '[]', description: 'Columns in order; cards take the DealCard props (title, stage, tone, amount, probability, owner, due). The column count badge is cards.length. Give cards an id so onMove can report which one moved (the index is used otherwise).' },
123
+ size,
124
+ },
125
+ events: { move: 'Called with { id, from, to } (card id, source stage id, target stage id) when a card is dropped on another stage. The board does not reorder itself: update stages in your state.' },
126
+ notes: ['Drag and drop is enabled only when a move handler is passed; without it the board renders and behaves as before.', 'Uses HTML5 drag and drop (mouse and desktop browsers); there is no keyboard alternative, so also offer a stage select on the card if you need one.', 'Cards are fixed to DealCard; for custom cards compose KanbanColumn and KanbanCard.'],
127
+ },
128
+ ContactPanel: {
129
+ description: 'Contact detail card with avatar, role, contact fields and Call / Message buttons.',
130
+ props: {
131
+ name: { type: 'string', required: true, description: 'Contact name.' },
132
+ role: { type: 'string', description: 'Role or company under the name.' },
133
+ phone: { type: 'string', description: 'Phone row.' },
134
+ telegram: { type: 'string', description: 'Telegram handle row.' },
135
+ email: { type: 'string', description: 'Email row.' },
136
+ city: { type: 'string', description: 'City row.' },
137
+ labels: { type: '{ call?: string; message?: string }', description: 'Override built-in English strings.' },
138
+ size,
139
+ },
140
+ events: { call: 'Called with no arguments when Call is clicked.', message: 'Called with no arguments when Message is clicked.' },
141
+ },
142
+ KanbanColumn: {
143
+ description: 'Single kanban column with a title, count badge and stacked cards.',
144
+ props: {
145
+ title: { type: 'string', required: true, description: 'Column heading.' },
146
+ count: { type: 'number', description: 'Count badge; defaults to the number of children.' },
147
+ size,
148
+ },
149
+ slots: { default: 'Cards, usually KanbanCard or DealCard. React passes them as children.' },
150
+ },
151
+ KanbanCard: {
152
+ description: 'Simple kanban card with title, subtitle, free content and an optional tag.',
153
+ props: {
154
+ title: { type: 'string', required: true, description: 'Card title.' },
155
+ sub: { type: 'string', description: 'Subtitle under the title.' },
156
+ tag: { type: 'string', description: 'Badge at the bottom.' },
157
+ tone: { type: TONES, values: toneValues, description: 'Badge tone for tag.' },
158
+ size,
159
+ },
160
+ slots: { default: 'Extra content between the subtitle and the tag. React passes it as children.' },
161
+ },
162
+ CallRow: {
163
+ description: 'One-line call log row with avatar, status, duration and a play button.',
164
+ props: {
165
+ name: { type: 'string', required: true, description: 'Caller name.' },
166
+ status: { type: 'string', description: 'Status text before the duration, e.g. "Answered".' },
167
+ duration: { type: 'number', description: 'Duration in seconds, formatted as "3 m 05 s" (units from durUnits); shows a dash when missing.' },
168
+ durUnits: { type: '{ m?: string; s?: string }', default: "{ m: 'm', s: 's' }", description: 'Minute and second unit labels for localized durations, e.g. { m: "min", s: "sec" }.' },
169
+ labels: { type: '{ play?: string }', description: 'Override built-in English strings. play is the aria-label of the play button.' },
170
+ size,
171
+ },
172
+ events: { play: 'Called with no arguments when the play button is clicked.' },
173
+ },
174
+ CallCard: {
175
+ description: 'Call recording card with status badge, a playback progress bar and duration.',
176
+ props: {
177
+ name: { type: 'string', required: true, description: 'Caller name.' },
178
+ phone: { type: 'string', description: 'Phone number.' },
179
+ status: { type: "'Answered' | 'Missed' | 'Voicemail' | 'Busy' | 'Declined' | string", values: ['Answered', 'Missed', 'Voicemail', 'Busy', 'Declined'], description: 'Badge text. Known statuses (case-insensitive): Answered green, Missed and Declined red, Voicemail and Busy amber, Ringing blue. Any other value is neutral. The badge is hidden when empty.' },
180
+ statusTones: { type: "Record<string, 'success' | 'danger' | 'warning' | 'info' | 'brand'>", description: 'Extra or overriding status to tone mapping, e.g. { "No answer": "warning" }.' },
181
+ progress: { type: 'number', default: 0, description: 'Playback progress 0-100.' },
182
+ duration: { type: 'number', description: 'Duration in seconds.' },
183
+ durUnits: { type: '{ m?: string; s?: string }', default: "{ m: 'm', s: 's' }", description: 'Minute and second unit labels for the duration.' },
184
+ labels: { type: '{ play?: string }', description: 'Override built-in English strings. play is the aria-label of the play button.' },
185
+ size,
186
+ },
187
+ events: { play: 'Called with no arguments when the play button is clicked.' },
188
+ },
189
+ IncomingCall: {
190
+ description: 'Glass banner for an incoming call with Decline and Answer buttons.',
191
+ props: {
192
+ name: { type: 'string', required: true, description: 'Caller name.' },
193
+ phone: { type: 'string', description: 'Caller number.' },
194
+ label: { type: 'string', default: 'Incoming call', description: 'Caption above the name.' },
195
+ onDecline: { type: '() => void', description: 'Decline button click.' },
196
+ onAnswer: { type: '() => void', description: 'Answer button click.' },
197
+ size,
198
+ },
199
+ events: { decline: 'Decline button clicked.', answer: 'Answer button clicked.' },
200
+ },
201
+ AuthSplit: {
202
+ description: 'Two-column sign-in layout with a brand panel on the left and the form on the right.',
203
+ props: {
204
+ title: { type: 'string', required: true, description: 'Form heading.' },
205
+ subtitle: { type: 'string', description: 'Text under the heading.' },
206
+ logo: { type: 'VNode', description: 'Logo node above the heading.' },
207
+ aside: { type: 'VNode', description: 'Content of the left brand panel (hidden on narrow screens).' },
208
+ },
209
+ slots: { default: 'Form content under the heading. React passes it as children.' },
210
+ notes: ['logo and aside are props in both Vue and React (pass a VNode / element).'],
211
+ },
212
+ };