@cavulsqa/create 2.9.3 → 2.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +12 -1
  2. package/bin/create.mjs +18 -25
  3. package/lib/templates.mjs +56 -0
  4. package/package.json +13 -5
  5. package/templates/f7-app/auto-imports.d.ts +9 -0
  6. package/templates/f7-app/package.json +6 -3
  7. package/templates/f7-app/src/env.d.ts +1 -0
  8. package/templates/f7-app/src/main.ts +3 -0
  9. package/templates/f7-app/src/plugins/bootstrapError.ts +2 -0
  10. package/templates/f7-app/src/plugins/recorder.plugin.ts +155 -0
  11. package/templates/f7-app/src/shared/recorder/capacitorSink.ts +124 -0
  12. package/templates/f7-app/vite.config.ts +1 -0
  13. package/templates/m3e-app/.claude/rules/data-fetching.md +68 -0
  14. package/templates/m3e-app/.claude/rules/database.md +106 -0
  15. package/templates/m3e-app/.claude/rules/m3e-ui.md +48 -0
  16. package/templates/m3e-app/.claude/rules/modules.md +43 -0
  17. package/templates/m3e-app/.claude/rules/native.md +60 -0
  18. package/templates/m3e-app/.claude/skills/m3-expressive/SKILL.md +68 -0
  19. package/templates/m3e-app/.claude/skills/m3-expressive/color.md +44 -0
  20. package/templates/m3e-app/.claude/skills/m3-expressive/components.md +325 -0
  21. package/templates/m3e-app/.claude/skills/m3-expressive/layout.md +42 -0
  22. package/templates/m3e-app/.claude/skills/m3-expressive/motion.md +51 -0
  23. package/templates/m3e-app/.claude/skills/m3-expressive/shapes-type.md +48 -0
  24. package/templates/m3e-app/.claude/skills/m3-expressive/sources.md +53 -0
  25. package/templates/m3e-app/.claude/skills/module-architecture/SKILL.md +65 -0
  26. package/templates/m3e-app/.claude/skills/module-architecture/file-templates.md +178 -0
  27. package/templates/m3e-app/.claude/skills/reactive-data/SKILL.md +90 -0
  28. package/templates/m3e-app/.claude/skills/reactive-data/testing.md +55 -0
  29. package/templates/m3e-app/.env.example +32 -0
  30. package/templates/m3e-app/CLAUDE.md +93 -0
  31. package/templates/m3e-app/auto-imports.d.ts +851 -0
  32. package/templates/m3e-app/capacitor.config.ts +44 -0
  33. package/templates/m3e-app/components.d.ts +252 -0
  34. package/templates/m3e-app/index.html +16 -0
  35. package/templates/m3e-app/package.json +108 -0
  36. package/templates/m3e-app/src/App.vue +129 -0
  37. package/templates/m3e-app/src/app/pragmas.config.ts +35 -0
  38. package/templates/m3e-app/src/app/scroll.config.ts +20 -0
  39. package/templates/m3e-app/src/app/storage.config.ts +65 -0
  40. package/templates/m3e-app/src/app/tabs.ts +33 -0
  41. package/templates/m3e-app/src/app/theme.config.ts +51 -0
  42. package/templates/m3e-app/src/assets/css/app.css +18 -0
  43. package/templates/m3e-app/src/assets/css/base.css +53 -0
  44. package/templates/m3e-app/src/assets/css/layout/container-transform.css +113 -0
  45. package/templates/m3e-app/src/assets/css/layout/shell.css +69 -0
  46. package/templates/m3e-app/src/assets/css/layout/transitions.css +121 -0
  47. package/templates/m3e-app/src/assets/css/m3e.css +6 -0
  48. package/templates/m3e-app/src/assets/css/theme/framework7.css +18 -0
  49. package/templates/m3e-app/src/assets/css/theme/tailwind.css +365 -0
  50. package/templates/m3e-app/src/domains/benchmark/benchmark.dataset.ts +209 -0
  51. package/templates/m3e-app/src/domains/benchmark/benchmark.suite.ts +662 -0
  52. package/templates/m3e-app/src/domains/sales/sales.repository.ts +458 -0
  53. package/templates/m3e-app/src/env.d.ts +40 -0
  54. package/templates/m3e-app/src/locales/ar.json +1260 -0
  55. package/templates/m3e-app/src/locales/en.json +1260 -0
  56. package/templates/m3e-app/src/locales/fr.json +1260 -0
  57. package/templates/m3e-app/src/main.ts +41 -0
  58. package/templates/m3e-app/src/modules/demo/components/DemoBenchmark.vue +119 -0
  59. package/templates/m3e-app/src/modules/demo/components/DemoBusLog.vue +63 -0
  60. package/templates/m3e-app/src/modules/demo/components/DemoCreateOrderSheet.vue +203 -0
  61. package/templates/m3e-app/src/modules/demo/components/DemoMetricsSheet.vue +75 -0
  62. package/templates/m3e-app/src/modules/demo/components/DemoOrderList.vue +75 -0
  63. package/templates/m3e-app/src/modules/demo/components/DemoPipelineBenchmark.vue +65 -0
  64. package/templates/m3e-app/src/modules/demo/components/DemoStatCards.vue +71 -0
  65. package/templates/m3e-app/src/modules/demo/composables/useBenchmark.ts +139 -0
  66. package/templates/m3e-app/src/modules/demo/composables/useOrderStatus.ts +64 -0
  67. package/templates/m3e-app/src/modules/demo/composables/useReactiveDemo.ts +171 -0
  68. package/templates/m3e-app/src/modules/demo/router/routes/demo.routes.ts +22 -0
  69. package/templates/m3e-app/src/modules/demo/views/DemoView.vue +210 -0
  70. package/templates/m3e-app/src/modules/demo/views/OrderDetailView.vue +136 -0
  71. package/templates/m3e-app/src/modules/demo/views/OrderSearchView.vue +74 -0
  72. package/templates/m3e-app/src/modules/gallery/components/GalleryBlock.vue +16 -0
  73. package/templates/m3e-app/src/modules/gallery/components/GalleryCarouselTile.vue +71 -0
  74. package/templates/m3e-app/src/modules/gallery/components/GalleryCustomerForm.vue +76 -0
  75. package/templates/m3e-app/src/modules/gallery/components/GalleryProofOfDelivery.vue +69 -0
  76. package/templates/m3e-app/src/modules/gallery/components/agenda/AgendaDay.vue +106 -0
  77. package/templates/m3e-app/src/modules/gallery/components/agenda/AgendaMonth.vue +40 -0
  78. package/templates/m3e-app/src/modules/gallery/components/agenda/AgendaVisitList.vue +45 -0
  79. package/templates/m3e-app/src/modules/gallery/components/chat/ChatAttachSheet.vue +106 -0
  80. package/templates/m3e-app/src/modules/gallery/components/chat/ChatContactPicker.vue +85 -0
  81. package/templates/m3e-app/src/modules/gallery/components/chat/ChatInviteComposer.vue +131 -0
  82. package/templates/m3e-app/src/modules/gallery/components/chat/ChatPollComposer.vue +148 -0
  83. package/templates/m3e-app/src/modules/gallery/components/chat/ChatRecentPhotos.vue +62 -0
  84. package/templates/m3e-app/src/modules/gallery/components/inputs/InputsAccount.vue +79 -0
  85. package/templates/m3e-app/src/modules/gallery/components/inputs/InputsTags.vue +53 -0
  86. package/templates/m3e-app/src/modules/gallery/components/inputs/InputsVerification.vue +73 -0
  87. package/templates/m3e-app/src/modules/gallery/components/inputs/PasswordStrength.vue +37 -0
  88. package/templates/m3e-app/src/modules/gallery/components/sections/GalleryButtons.vue +128 -0
  89. package/templates/m3e-app/src/modules/gallery/components/sections/GalleryCarousels.vue +236 -0
  90. package/templates/m3e-app/src/modules/gallery/components/sections/GalleryCharts.vue +84 -0
  91. package/templates/m3e-app/src/modules/gallery/components/sections/GalleryFabs.vue +70 -0
  92. package/templates/m3e-app/src/modules/gallery/components/sections/GalleryInputs.vue +219 -0
  93. package/templates/m3e-app/src/modules/gallery/components/sections/GalleryNavigation.vue +141 -0
  94. package/templates/m3e-app/src/modules/gallery/components/sections/GalleryOverlays.vue +368 -0
  95. package/templates/m3e-app/src/modules/gallery/components/sections/GalleryPickers.vue +175 -0
  96. package/templates/m3e-app/src/modules/gallery/components/sections/GalleryProgress.vue +118 -0
  97. package/templates/m3e-app/src/modules/gallery/components/sections/GalleryScale.vue +103 -0
  98. package/templates/m3e-app/src/modules/gallery/components/sections/GallerySelection.vue +219 -0
  99. package/templates/m3e-app/src/modules/gallery/components/sections/GalleryShapes.vue +48 -0
  100. package/templates/m3e-app/src/modules/gallery/components/sections/GallerySurfaces.vue +213 -0
  101. package/templates/m3e-app/src/modules/gallery/components/sections/GalleryTables.vue +185 -0
  102. package/templates/m3e-app/src/modules/gallery/components/surfaces/SurfacesContainerTransform.vue +46 -0
  103. package/templates/m3e-app/src/modules/gallery/composables/chatTeam.ts +18 -0
  104. package/templates/m3e-app/src/modules/gallery/composables/composeKind.ts +2 -0
  105. package/templates/m3e-app/src/modules/gallery/composables/currentPosition.ts +35 -0
  106. package/templates/m3e-app/src/modules/gallery/composables/demoOrders.ts +29 -0
  107. package/templates/m3e-app/src/modules/gallery/composables/featuredStories.ts +73 -0
  108. package/templates/m3e-app/src/modules/gallery/composables/fieldFormats.ts +36 -0
  109. package/templates/m3e-app/src/modules/gallery/composables/openExternal.ts +11 -0
  110. package/templates/m3e-app/src/modules/gallery/composables/routePlan.ts +90 -0
  111. package/templates/m3e-app/src/modules/gallery/composables/useAgenda.ts +61 -0
  112. package/templates/m3e-app/src/modules/gallery/composables/useChatCustomers.ts +31 -0
  113. package/templates/m3e-app/src/modules/gallery/composables/useChatDemo.ts +299 -0
  114. package/templates/m3e-app/src/modules/gallery/composables/useGallerySections.ts +230 -0
  115. package/templates/m3e-app/src/modules/gallery/composables/useLocalAttachments.ts +71 -0
  116. package/templates/m3e-app/src/modules/gallery/composables/useLocationShare.ts +38 -0
  117. package/templates/m3e-app/src/modules/gallery/composables/usePhotoScenes.ts +82 -0
  118. package/templates/m3e-app/src/modules/gallery/composables/useTreeDemo.ts +67 -0
  119. package/templates/m3e-app/src/modules/gallery/composables/wilayas.ts +67 -0
  120. package/templates/m3e-app/src/modules/gallery/router/routes/gallery.routes.ts +52 -0
  121. package/templates/m3e-app/src/modules/gallery/views/GalleryAgendaView.vue +117 -0
  122. package/templates/m3e-app/src/modules/gallery/views/GalleryChatView.vue +292 -0
  123. package/templates/m3e-app/src/modules/gallery/views/GalleryContactsView.vue +77 -0
  124. package/templates/m3e-app/src/modules/gallery/views/GalleryFeaturedView.vue +60 -0
  125. package/templates/m3e-app/src/modules/gallery/views/GalleryLoginView.vue +135 -0
  126. package/templates/m3e-app/src/modules/gallery/views/GalleryOnboardingView.vue +116 -0
  127. package/templates/m3e-app/src/modules/gallery/views/GallerySectionView.vue +27 -0
  128. package/templates/m3e-app/src/modules/gallery/views/GalleryTabsView.vue +135 -0
  129. package/templates/m3e-app/src/modules/gallery/views/GalleryView.vue +50 -0
  130. package/templates/m3e-app/src/modules/home/components/HomeHero.vue +57 -0
  131. package/templates/m3e-app/src/modules/home/composables/useHomeFeatures.ts +128 -0
  132. package/templates/m3e-app/src/modules/home/composables/useOpenFeature.ts +22 -0
  133. package/templates/m3e-app/src/modules/home/router/routes/home.routes.ts +21 -0
  134. package/templates/m3e-app/src/modules/home/views/FeatureDetailView.vue +66 -0
  135. package/templates/m3e-app/src/modules/home/views/HomeView.vue +47 -0
  136. package/templates/m3e-app/src/modules/settings/components/RolePalette.vue +38 -0
  137. package/templates/m3e-app/src/modules/settings/components/SeedSwatches.vue +41 -0
  138. package/templates/m3e-app/src/modules/settings/components/SettingsChoice.vue +43 -0
  139. package/templates/m3e-app/src/modules/settings/components/StudioPreview.vue +52 -0
  140. package/templates/m3e-app/src/modules/settings/router/routes/settings.routes.ts +17 -0
  141. package/templates/m3e-app/src/modules/settings/types.ts +7 -0
  142. package/templates/m3e-app/src/modules/settings/views/ColorStudioView.vue +166 -0
  143. package/templates/m3e-app/src/modules/settings/views/SettingsView.vue +157 -0
  144. package/templates/m3e-app/src/plugins/bootstrapError.ts +62 -0
  145. package/templates/m3e-app/src/plugins/capacitor/index.ts +14 -0
  146. package/templates/m3e-app/src/plugins/capacitor/useAndroidBackButton.ts +36 -0
  147. package/templates/m3e-app/src/plugins/capacitor/useKeyboard.ts +48 -0
  148. package/templates/m3e-app/src/plugins/capacitor/useSplashScreen.ts +43 -0
  149. package/templates/m3e-app/src/plugins/capacitor/useStatusBar.ts +16 -0
  150. package/templates/m3e-app/src/plugins/framework7.plugin.ts +36 -0
  151. package/templates/m3e-app/src/plugins/i18n.plugin.ts +87 -0
  152. package/templates/m3e-app/src/plugins/m3e.plugin.ts +22 -0
  153. package/templates/m3e-app/src/plugins/seed.plugin.ts +7 -0
  154. package/templates/m3e-app/src/plugins/sqlite.plugin.ts +32 -0
  155. package/templates/m3e-app/src/router/global/global.routes.ts +12 -0
  156. package/templates/m3e-app/src/router/index.ts +20 -0
  157. package/templates/m3e-app/src/shared/components/error/404.vue +13 -0
  158. package/templates/m3e-app/src/shared/components/layout/EmptyState.vue +30 -0
  159. package/templates/m3e-app/src/shared/components/layout/SectionHeader.vue +10 -0
  160. package/templates/m3e-app/src/shared/components/page/AppPage.vue +104 -0
  161. package/templates/m3e-app/src/shared/composables/navigation/useActiveTab.ts +39 -0
  162. package/templates/m3e-app/src/shared/composables/navigation/useContainerTransform.ts +117 -0
  163. package/templates/m3e-app/src/shared/composables/navigation/useNavigationGuard.ts +48 -0
  164. package/templates/m3e-app/src/shared/composables/navigation/useNavigationVisibility.ts +64 -0
  165. package/templates/m3e-app/src/shared/composables/navigation/useViewRouter.ts +25 -0
  166. package/templates/m3e-app/src/shared/composables/navigation/useWindowClass.ts +22 -0
  167. package/templates/m3e-app/src/shared/composables/theme/useThemeSettings.ts +155 -0
  168. package/templates/m3e-app/src/shared/database/candidates/capacitorSqlite.ts +36 -0
  169. package/templates/m3e-app/src/shared/database/candidates/index.ts +4 -0
  170. package/templates/m3e-app/src/shared/database/candidates/opfsSahPool.ts +25 -0
  171. package/templates/m3e-app/src/shared/database/candidates/types.ts +56 -0
  172. package/templates/m3e-app/src/shared/database/candidates/waSqlite.ts +56 -0
  173. package/templates/m3e-app/src/shared/database/database.ts +219 -0
  174. package/templates/m3e-app/src/shared/database/index.ts +3 -0
  175. package/templates/m3e-app/src/shared/database/migrations.ts +223 -0
  176. package/templates/m3e-app/src/shared/database/opfs.worker.ts +5 -0
  177. package/templates/m3e-app/src/shared/database/queries.ts +26 -0
  178. package/templates/m3e-app/src/shared/database/schema.ts +153 -0
  179. package/templates/m3e-app/src/shared/database/storage.ts +64 -0
  180. package/templates/m3e-app/src/shared/database/wa.worker.ts +5 -0
  181. package/templates/m3e-app/src/shared/utils/lazyRoute.ts +41 -0
  182. package/templates/m3e-app/src/shared/utils/resolvers/resolvers.ts +42 -0
  183. package/templates/m3e-app/src/shared/utils/textDirection.ts +22 -0
  184. package/templates/m3e-app/src/shared/utils/theme/themeSettings.ts +59 -0
  185. package/templates/m3e-app/src/shared/utils/tone.ts +8 -0
  186. package/templates/m3e-app/tests/benchmark.suite.test.ts +104 -0
  187. package/templates/m3e-app/tests/containerTransform.test.ts +58 -0
  188. package/templates/m3e-app/tests/fieldFormats.test.ts +25 -0
  189. package/templates/m3e-app/tests/lazyRoute.test.ts +30 -0
  190. package/templates/m3e-app/tests/locales.test.ts +73 -0
  191. package/templates/m3e-app/tests/migrations.test.ts +74 -0
  192. package/templates/m3e-app/tests/navigationGuard.test.ts +82 -0
  193. package/templates/m3e-app/tests/openDatabase.test.ts +54 -0
  194. package/templates/m3e-app/tests/sales.repository.test.ts +329 -0
  195. package/templates/m3e-app/tests/storage.test.ts +117 -0
  196. package/templates/m3e-app/tests/textDirection.test.ts +11 -0
  197. package/templates/m3e-app/tooling/linkedPackages.ts +112 -0
  198. package/templates/m3e-app/tsconfig.json +21 -0
  199. package/templates/m3e-app/vite.config.ts +144 -0
@@ -0,0 +1,103 @@
1
+ <template>
2
+ <M3PullToRefresh :refresh="refresh" :label="t('gallery.scale.refreshing')" />
3
+
4
+ <GalleryBlock
5
+ :title="t('gallery.scale.virtual')"
6
+ :note="t('gallery.scale.virtualNote', { count: rows.length })"
7
+ >
8
+ <M3Chip :label="t('gallery.scale.rendered', { count: rendered }, rendered)">
9
+ <template #icon><i-ms-speed-rounded /></template>
10
+ </M3Chip>
11
+ <M3Chip :label="t('gallery.scale.refreshed', { count: refreshes })">
12
+ <template #icon><i-ms-refresh-rounded /></template>
13
+ </M3Chip>
14
+ </GalleryBlock>
15
+
16
+ <div class="h-3" />
17
+ <M3VirtualList
18
+ ref="list"
19
+ :items="rows"
20
+ :item-size="74"
21
+ :item-key="(row) => row.id"
22
+ inset
23
+ :label="t('gallery.scale.virtual')"
24
+ >
25
+ <template #default="{ item }">
26
+ <M3ListItem
27
+ as="div"
28
+ clickable
29
+ :headline="item.title"
30
+ :supporting="item.subtitle"
31
+ :trailing-text="item.amount"
32
+ >
33
+ <template #leading>
34
+ <M3Shape :shape="item.shape" class="size-10" :class="TONE_CLASSES[item.tone]">
35
+ <span class="type-label-large">{{ item.initials }}</span>
36
+ </M3Shape>
37
+ </template>
38
+ </M3ListItem>
39
+ </template>
40
+ </M3VirtualList>
41
+ </template>
42
+
43
+ <script setup lang="ts">
44
+ import type { MaterialShapeName } from "@cavulsqa/m3e";
45
+ import type { ComponentPublicInstance } from "vue";
46
+ import GalleryBlock from "@/modules/gallery/components/GalleryBlock.vue";
47
+ import { TONE_CLASSES, type Tone } from "@/shared/utils/tone";
48
+
49
+ const { t } = useI18n();
50
+ const snackbar = useSnackbar();
51
+
52
+ const SHAPES: readonly MaterialShapeName[] = [
53
+ "cookie9Sided",
54
+ "clover4Leaf",
55
+ "pentagon",
56
+ "sunny",
57
+ "gem",
58
+ ];
59
+ const TONES: readonly Tone[] = ["primary", "secondary", "tertiary"];
60
+ const CITIES = ["Alger", "Oran", "Constantine", "Annaba", "Blida", "Sétif", "Tlemcen", "Béjaïa"];
61
+ const COUNT = 5000;
62
+ const formatter = new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 });
63
+
64
+ const refreshes = ref(0);
65
+ const rows = shallowRef(build(0));
66
+
67
+ function build(seed: number) {
68
+ return Array.from({ length: COUNT }, (_, index) => {
69
+ const n = (index * 7919 + seed * 104729) % 100000;
70
+ const city = CITIES[n % CITIES.length]!;
71
+ return {
72
+ id: index,
73
+ title: `${t("gallery.scale.customer")} ${String(index + 1).padStart(4, "0")}`,
74
+ subtitle: `${city} · ${t("gallery.scale.orders", { count: (n % 40) + 1 }, (n % 40) + 1)}`,
75
+ amount: formatter.format((n % 9000) + 100),
76
+ initials: city.slice(0, 2).toUpperCase(),
77
+ shape: SHAPES[index % SHAPES.length]!,
78
+ tone: TONES[index % TONES.length]!,
79
+ };
80
+ });
81
+ }
82
+
83
+ /** How many rows are actually in the DOM - the number that proves virtualisation works. */
84
+ const list = useTemplateRef<ComponentPublicInstance>("list");
85
+ const rendered = ref(0);
86
+ let observer: MutationObserver | null = null;
87
+ onMounted(() => {
88
+ const element = list.value?.$el as HTMLElement | undefined;
89
+ if (!element) return;
90
+ const count = () => (rendered.value = element.childElementCount);
91
+ count();
92
+ observer = new MutationObserver(count);
93
+ observer.observe(element, { childList: true });
94
+ });
95
+ onBeforeUnmount(() => observer?.disconnect());
96
+
97
+ async function refresh() {
98
+ await new Promise((resolve) => setTimeout(resolve, 1400));
99
+ refreshes.value += 1;
100
+ rows.value = build(refreshes.value);
101
+ void snackbar.show(t("gallery.scale.updated"));
102
+ }
103
+ </script>
@@ -0,0 +1,219 @@
1
+ <template>
2
+ <GalleryBlock
3
+ :title="t('gallery.selection.switches')"
4
+ :note="t('gallery.selection.switchesNote')"
5
+ >
6
+ <M3Switch v-model="wifi" :label="t('gallery.selection.wifi')" />
7
+ <M3Switch v-model="bluetooth" icons :label="t('gallery.selection.bluetooth')" />
8
+ <M3Switch v-model="airplane" both-icons :label="t('gallery.selection.airplane')" />
9
+ <M3Switch :model-value="true" disabled :label="t('gallery.selection.locked')" />
10
+ </GalleryBlock>
11
+
12
+ <GalleryBlock
13
+ :title="t('gallery.selection.stepper')"
14
+ :note="t('gallery.selection.stepperNote')"
15
+ stack
16
+ >
17
+ <div class="flex items-center justify-between gap-4">
18
+ <span class="type-body-large text-on-surface">{{ t("gallery.selection.guests") }}</span>
19
+ <M3Stepper
20
+ v-model="guests"
21
+ :label="t('gallery.selection.guests')"
22
+ :min="1"
23
+ :max="12"
24
+ :decrement-label="t('gallery.selection.decrease')"
25
+ :increment-label="t('gallery.selection.increase')"
26
+ />
27
+ </div>
28
+ <div class="flex items-center justify-between gap-4">
29
+ <span class="type-body-large text-on-surface">{{ t("gallery.selection.weight") }}</span>
30
+ <M3Stepper
31
+ v-model="weight"
32
+ variant="tonal"
33
+ editable
34
+ :label="t('gallery.selection.weight')"
35
+ :min="0"
36
+ :max="50"
37
+ :step="0.5"
38
+ :format="(value) => t('gallery.selection.kilos', { value: value.toFixed(1) })"
39
+ :decrement-label="t('gallery.selection.decrease')"
40
+ :increment-label="t('gallery.selection.increase')"
41
+ />
42
+ </div>
43
+ <div class="flex items-center justify-between gap-4">
44
+ <span class="type-body-large text-on-surface">{{ t("gallery.selection.units") }}</span>
45
+ <M3Stepper
46
+ v-model="units"
47
+ size="m"
48
+ editable
49
+ :label="t('gallery.selection.units')"
50
+ :min="0"
51
+ :max="1000"
52
+ :decrement-label="t('gallery.selection.decrease')"
53
+ :increment-label="t('gallery.selection.increase')"
54
+ />
55
+ </div>
56
+ </GalleryBlock>
57
+
58
+ <GalleryBlock
59
+ :title="t('gallery.selection.smart')"
60
+ :note="t('gallery.selection.smartNote')"
61
+ stack
62
+ >
63
+ <M3List variant="segmented" class="-mx-1">
64
+ <M3SmartSelect
65
+ v-model="deliveryDays"
66
+ multiple
67
+ :label="t('gallery.selection.deliveryDays')"
68
+ :options="dayOptions"
69
+ :placeholder="t('gallery.selection.none')"
70
+ :done-label="t('gallery.selection.done')"
71
+ >
72
+ <template #leading><i-ms-calendar-month-outline-rounded /></template>
73
+ </M3SmartSelect>
74
+ <M3SmartSelect
75
+ v-model="zone"
76
+ :label="t('gallery.inputs.wilaya')"
77
+ :options="WILAYA_OPTIONS"
78
+ :placeholder="t('gallery.selection.none')"
79
+ :search-placeholder="t('gallery.selection.searchWilaya')"
80
+ :no-results-text="t('gallery.inputs.noMatches')"
81
+ >
82
+ <template #leading><i-ms-location-on-outline-rounded /></template>
83
+ </M3SmartSelect>
84
+ </M3List>
85
+ </GalleryBlock>
86
+
87
+ <GalleryBlock :title="t('gallery.selection.checks')" :note="t('gallery.selection.checksNote')">
88
+ <M3Checkbox v-model="terms" :label="t('gallery.selection.terms')" />
89
+ <M3Checkbox :model-value="false" indeterminate :label="t('gallery.selection.partial')" />
90
+ <M3Checkbox v-model="invalid" error :label="t('gallery.selection.required')" />
91
+ <span class="mx-2 h-8 w-px bg-outline-variant" />
92
+ <M3Radio
93
+ v-for="size in SIZES"
94
+ :key="size"
95
+ v-model="cup"
96
+ :value="size"
97
+ :label="t(`gallery.selection.cups.${size}`)"
98
+ />
99
+ </GalleryBlock>
100
+
101
+ <GalleryBlock
102
+ :title="t('gallery.selection.sliders')"
103
+ :note="t('gallery.selection.slidersNote')"
104
+ stack
105
+ >
106
+ <M3Slider v-model="volume" :label="t('gallery.selection.volume')" />
107
+ <M3Slider
108
+ v-model="steps"
109
+ :label="t('gallery.selection.steps')"
110
+ :min="0"
111
+ :max="10"
112
+ :step="1"
113
+ ticks
114
+ size="s"
115
+ />
116
+ <M3Slider v-model="brightness" :label="t('gallery.selection.brightness')" size="m">
117
+ <template #icon><i-ms-light-mode-outline-rounded /></template>
118
+ </M3Slider>
119
+ <M3Slider v-model="brightness" :label="t('gallery.selection.brightness')" size="l" />
120
+ </GalleryBlock>
121
+
122
+ <GalleryBlock
123
+ :title="t('gallery.selection.range')"
124
+ :note="t('gallery.selection.rangeNote')"
125
+ stack
126
+ >
127
+ <M3RangeSlider
128
+ v-model:start="priceFrom"
129
+ v-model:end="priceTo"
130
+ :min="0"
131
+ :max="20000"
132
+ :step="500"
133
+ :min-distance="1000"
134
+ :start-label="t('gallery.selection.priceFrom')"
135
+ :end-label="t('gallery.selection.priceTo')"
136
+ :format="(v) => money.format(v)"
137
+ />
138
+ <p class="type-body-medium m-0 text-on-surface-variant">
139
+ {{ money.format(priceFrom) }} – {{ money.format(priceTo) }}
140
+ </p>
141
+ <M3RangeSlider
142
+ v-model:start="hourFrom"
143
+ v-model:end="hourTo"
144
+ :min="6"
145
+ :max="22"
146
+ :step="1"
147
+ ticks
148
+ size="s"
149
+ :start-label="t('gallery.selection.opensAt')"
150
+ :end-label="t('gallery.selection.closesAt')"
151
+ :format="(v) => `${v}:00`"
152
+ />
153
+ </GalleryBlock>
154
+
155
+ <GalleryBlock :title="t('gallery.selection.chips')" :note="t('gallery.selection.chipsNote')">
156
+ <M3Chip
157
+ v-for="filter in FILTERS"
158
+ :key="filter"
159
+ kind="filter"
160
+ :label="t(`gallery.selection.filters.${filter}`)"
161
+ :selected="filters.has(filter)"
162
+ @update:selected="toggleFilter(filter)"
163
+ />
164
+ <M3Chip kind="assist" :label="t('gallery.selection.addEvent')" elevated>
165
+ <template #icon><i-ms-add-rounded /></template>
166
+ </M3Chip>
167
+ <M3Chip
168
+ v-for="tag in tags"
169
+ :key="tag"
170
+ kind="input"
171
+ removable
172
+ :label="tag"
173
+ :remove-label="t('gallery.selection.remove')"
174
+ @remove="tags = tags.filter((entry) => entry !== tag)"
175
+ />
176
+ </GalleryBlock>
177
+ </template>
178
+
179
+ <script setup lang="ts">
180
+ import GalleryBlock from "@/modules/gallery/components/GalleryBlock.vue";
181
+ import { WILAYA_OPTIONS } from "@/modules/gallery/composables/wilayas";
182
+
183
+ const { t } = useI18n();
184
+ const SIZES = ["small", "medium", "large"] as const;
185
+ const FILTERS = ["open", "nearby", "rated"] as const;
186
+
187
+ const wifi = ref(true);
188
+ const bluetooth = ref(false);
189
+ const airplane = ref(false);
190
+ const terms = ref(true);
191
+ const invalid = ref(false);
192
+ const cup = ref<(typeof SIZES)[number]>("medium");
193
+ const volume = ref(40);
194
+ const steps = ref(4);
195
+ const guests = ref(2);
196
+ const DAYS = ["sat", "sun", "mon", "tue", "wed", "thu", "fri"] as const;
197
+ const deliveryDays = ref<string[]>(["sun", "tue", "thu"]);
198
+ const zone = ref<number | null>(16);
199
+ const dayOptions = computed(() =>
200
+ DAYS.map((day) => ({ value: day, label: t(`gallery.selection.days.${day}`) })),
201
+ );
202
+ const weight = ref(2.5);
203
+ const units = ref(120);
204
+ const brightness = ref(70);
205
+ const priceFrom = ref(2000);
206
+ const priceTo = ref(12000);
207
+ const hourFrom = ref(8);
208
+ const hourTo = ref(18);
209
+ const money = new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 });
210
+ const filters = ref(new Set<(typeof FILTERS)[number]>(["open"]));
211
+ const tags = ref(["Alger", "Oran", "Constantine"]);
212
+
213
+ function toggleFilter(filter: (typeof FILTERS)[number]) {
214
+ const next = new Set(filters.value);
215
+ if (next.has(filter)) next.delete(filter);
216
+ else next.add(filter);
217
+ filters.value = next;
218
+ }
219
+ </script>
@@ -0,0 +1,48 @@
1
+ <template>
2
+ <GalleryBlock :title="t('gallery.shapes.morph')" :note="t('gallery.shapes.morphNote')">
3
+ <div class="grid w-full place-items-center py-2">
4
+ <M3ShapeMorph :shape="selected" :rotate="turns * 30" class="size-40 text-primary">
5
+ <span class="type-label-large text-on-primary">{{ selected }}</span>
6
+ </M3ShapeMorph>
7
+ </div>
8
+ </GalleryBlock>
9
+
10
+ <GalleryBlock :title="t('gallery.shapes.library')" :note="t('gallery.shapes.libraryNote')">
11
+ <div class="grid w-full grid-cols-[repeat(auto-fill,minmax(4.5rem,1fr))] gap-3">
12
+ <button
13
+ v-for="shape in MATERIAL_SHAPES"
14
+ :key="shape"
15
+ type="button"
16
+ class="m3-focus-ring flex flex-col items-center gap-1.5 rounded-md p-1"
17
+ :aria-pressed="shape === selected"
18
+ @click="pick(shape)"
19
+ >
20
+ <M3Shape
21
+ :shape="shape"
22
+ class="size-14 transition-colors duration-200"
23
+ :class="shape === selected ? 'bg-primary' : 'bg-secondary-container'"
24
+ />
25
+ <span class="type-label-small w-full truncate text-center text-on-surface-variant">{{
26
+ shape
27
+ }}</span>
28
+ </button>
29
+ </div>
30
+ </GalleryBlock>
31
+ </template>
32
+
33
+ <script setup lang="ts">
34
+ import { MATERIAL_SHAPES, type MaterialShapeName } from "@cavulsqa/m3e";
35
+ import GalleryBlock from "@/modules/gallery/components/GalleryBlock.vue";
36
+
37
+ const { t } = useI18n();
38
+ const selected = ref<MaterialShapeName>("cookie9Sided");
39
+ const turns = ref(0);
40
+ const haptics = useHaptics();
41
+
42
+ function pick(shape: MaterialShapeName) {
43
+ if (shape === selected.value) return;
44
+ selected.value = shape;
45
+ turns.value += 1;
46
+ haptics.tick();
47
+ }
48
+ </script>
@@ -0,0 +1,213 @@
1
+ <template>
2
+ <GalleryBlock :title="t('gallery.surfaces.cards')" :note="t('gallery.surfaces.cardsNote')" stack>
3
+ <div class="grid grid-cols-[repeat(auto-fill,minmax(10rem,1fr))] gap-3">
4
+ <M3Card
5
+ v-for="variant in CARDS"
6
+ :key="variant"
7
+ :variant="variant"
8
+ clickable
9
+ @click="tapped(variant)"
10
+ >
11
+ <div class="flex flex-col gap-3 p-4">
12
+ <M3Shape
13
+ shape="cookie4Sided"
14
+ class="size-10 bg-tertiary-container text-on-tertiary-container"
15
+ >
16
+ <i-ms-star-outline-rounded class="size-5" />
17
+ </M3Shape>
18
+ <span class="type-title-medium">{{ variant }}</span>
19
+ <span class="type-body-medium text-on-surface-variant">{{
20
+ t("gallery.surfaces.cardText")
21
+ }}</span>
22
+ </div>
23
+ </M3Card>
24
+ </div>
25
+ </GalleryBlock>
26
+
27
+ <SurfacesContainerTransform />
28
+
29
+ <SectionHeader :title="t('gallery.surfaces.segmented')" />
30
+ <M3List variant="segmented" inset>
31
+ <M3ListItem
32
+ v-for="item in ITEMS"
33
+ :key="item"
34
+ clickable
35
+ :headline="t(`gallery.surfaces.items.${item}.title`)"
36
+ :supporting="t(`gallery.surfaces.items.${item}.text`)"
37
+ :selected="selected === item"
38
+ @click="selected = item"
39
+ >
40
+ <template #leading><i-ms-inventory-2-outline-rounded /></template>
41
+ <template #trailing><M3Badge v-if="item === 'inbox'" :count="12" /></template>
42
+ </M3ListItem>
43
+ </M3List>
44
+
45
+ <SectionHeader :title="t('gallery.surfaces.accordion')" />
46
+ <p class="type-body-small m-0 px-8 pb-3 text-on-surface-variant">
47
+ {{ t("gallery.surfaces.accordionNote") }}
48
+ </p>
49
+ <M3List variant="segmented" inset accordion>
50
+ <M3ListItem
51
+ v-for="question in QUESTIONS"
52
+ :key="question"
53
+ :headline="t(`gallery.surfaces.faq.${question}.q`)"
54
+ >
55
+ <template #leading><i-ms-help-outline-rounded class="rtl:-scale-x-100" /></template>
56
+ <template #details>{{ t(`gallery.surfaces.faq.${question}.a`) }}</template>
57
+ </M3ListItem>
58
+ </M3List>
59
+
60
+ <SectionHeader :title="t('gallery.surfaces.sortable')" />
61
+ <p class="type-body-small m-0 px-8 pb-3 text-on-surface-variant">
62
+ {{ t("gallery.surfaces.sortableNote") }}
63
+ </p>
64
+ <M3List
65
+ variant="segmented"
66
+ inset
67
+ sortable
68
+ :reorder-label="t('gallery.surfaces.reorder')"
69
+ :moved-text="movedText"
70
+ @sort="(from, to) => (route = moveItem(route, from, to))"
71
+ >
72
+ <M3ListItem
73
+ v-for="(stop, index) in route"
74
+ :key="stop"
75
+ :headline="t(`gallery.surfaces.stops.${stop}`)"
76
+ :supporting="t('gallery.surfaces.stopOrder', { n: index + 1 })"
77
+ >
78
+ <template #leading><i-ms-local-shipping-outline-rounded /></template>
79
+ </M3ListItem>
80
+ </M3List>
81
+
82
+ <SectionHeader :title="t('gallery.surfaces.standard')" />
83
+ <M3List variant="standard">
84
+ <M3ListItem :headline="t('gallery.surfaces.wifi')" :supporting="t('gallery.surfaces.wifiText')">
85
+ <template #action><M3Switch v-model="wifi" :label="t('gallery.surfaces.wifi')" /></template>
86
+ </M3ListItem>
87
+ <M3Divider inset="start" />
88
+ <M3ListItem :headline="t('gallery.surfaces.storage')" trailing-text="12.4 GB" />
89
+ </M3List>
90
+
91
+ <SectionHeader :title="t('gallery.tree.title')" />
92
+ <p class="type-body-small m-0 px-8 pb-3 text-on-surface-variant">{{ t("gallery.tree.note") }}</p>
93
+ <div class="mx-4">
94
+ <M3Tree
95
+ v-model:selected="category"
96
+ v-model:expanded="categoriesOpen"
97
+ :items="categories"
98
+ :load="loadChildren"
99
+ :label="t('gallery.tree.categories')"
100
+ :loading-label="t('gallery.loading')"
101
+ >
102
+ <template #icon="{ node, expanded }">
103
+ <i-ms-folder-open-outline-rounded v-if="expanded" />
104
+ <i-ms-folder-outline-rounded v-else-if="node.children || node.lazy" />
105
+ <i-ms-inventory-2-outline-rounded v-else />
106
+ </template>
107
+ </M3Tree>
108
+ </div>
109
+ <SectionHeader :title="t('gallery.tree.permissions')" />
110
+ <div class="mx-4">
111
+ <M3Tree
112
+ v-model:checked="granted"
113
+ v-model:expanded="permissionsOpen"
114
+ mode="check"
115
+ :items="permissions"
116
+ :label="t('gallery.tree.permissions')"
117
+ />
118
+ </div>
119
+
120
+ <SectionHeader :title="t('gallery.surfaces.timeline')" />
121
+ <div class="px-6 pb-2">
122
+ <M3Timeline :label="t('gallery.surfaces.tracking')">
123
+ <M3TimelineItem
124
+ v-for="step in STEPS"
125
+ :key="step.id"
126
+ :title="t(`gallery.surfaces.steps.${step.id}.title`)"
127
+ :supporting="t(`gallery.surfaces.steps.${step.id}.text`)"
128
+ :time="step.time"
129
+ :state="step.state"
130
+ :state-label="t(`gallery.surfaces.states.${step.state}`)"
131
+ >
132
+ <template v-if="step.state === 'current'" #icon
133
+ ><i-ms-local-shipping-outline-rounded
134
+ /></template>
135
+ </M3TimelineItem>
136
+ </M3Timeline>
137
+ </div>
138
+
139
+ <SectionHeader :title="t('gallery.surfaces.infinite')" />
140
+ <p class="type-body-small m-0 px-8 pb-3 text-on-surface-variant">
141
+ {{ t("gallery.surfaces.infiniteNote") }}
142
+ </p>
143
+ <M3List variant="segmented" inset>
144
+ <M3ListItem
145
+ v-for="order in endless"
146
+ :key="order"
147
+ :headline="t('gallery.tabs.order', { n: order })"
148
+ :supporting="t('gallery.surfaces.page', { n: Math.ceil((order - 5000) / 15) })"
149
+ >
150
+ <template #leading><i-ms-receipt-long-outline-rounded /></template>
151
+ </M3ListItem>
152
+ </M3List>
153
+ <M3InfiniteScroll
154
+ :load="loadMore"
155
+ :loading-label="t('gallery.surfaces.loadingMore')"
156
+ :error-text="t('gallery.surfaces.loadFailed')"
157
+ :retry-label="t('gallery.surfaces.retry')"
158
+ :end-text="t('gallery.surfaces.allLoaded')"
159
+ />
160
+ </template>
161
+
162
+ <script setup lang="ts">
163
+ import { moveItem } from "@cavulsqa/m3e-vue";
164
+ import GalleryBlock from "@/modules/gallery/components/GalleryBlock.vue";
165
+ import SurfacesContainerTransform from "@/modules/gallery/components/surfaces/SurfacesContainerTransform.vue";
166
+ import { useTreeDemo } from "@/modules/gallery/composables/useTreeDemo";
167
+
168
+ const { t } = useI18n();
169
+ const snackbar = useSnackbar();
170
+ const CARDS = ["elevated", "filled", "outlined"] as const;
171
+ const ITEMS = ["inbox", "drafts", "archive"] as const;
172
+ const QUESTIONS = ["offline", "sync", "storage", "export"] as const;
173
+ const { categories, permissions, loadChildren } = useTreeDemo();
174
+ const category = ref<string | null>("orange");
175
+ const categoriesOpen = ref(["drinks", "juice"]);
176
+ const granted = ref(["sales.read", "sales.create", "stock.read"]);
177
+ const permissionsOpen = ref(["sales"]);
178
+ const STEPS = [
179
+ { id: "placed", time: "08:12", state: "done" },
180
+ { id: "confirmed", time: "08:40", state: "done" },
181
+ { id: "picked", time: "10:05", state: "done" },
182
+ { id: "transit", time: "11:20", state: "current" },
183
+ { id: "delivered", time: "", state: "upcoming" },
184
+ ] as const;
185
+ const PAGE = 15;
186
+ const PAGES = 5;
187
+ const endless = ref<number[]>([]);
188
+ let failedOnce = false;
189
+
190
+ async function loadMore(): Promise<boolean> {
191
+ await new Promise((resolve) => setTimeout(resolve, 900));
192
+ const page = endless.value.length / PAGE;
193
+ if (page === 2 && !failedOnce) {
194
+ failedOnce = true;
195
+ throw new Error("simulated network failure");
196
+ }
197
+ const start = 5001 + endless.value.length;
198
+ endless.value = [...endless.value, ...Array.from({ length: PAGE }, (_, i) => start + i)];
199
+ return page + 1 < PAGES;
200
+ }
201
+ const route = ref(["warehouse", "market", "pharmacy", "school", "bakery"]);
202
+
203
+ function movedText(label: string, position: number, count: number) {
204
+ return t("gallery.surfaces.moved", { label, position, count });
205
+ }
206
+
207
+ const selected = ref<(typeof ITEMS)[number]>("inbox");
208
+ const wifi = ref(true);
209
+
210
+ function tapped(variant: string) {
211
+ void snackbar.show(t("gallery.surfaces.tapped", { variant }));
212
+ }
213
+ </script>