@stackonward/cli 1.5.3 → 1.6.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 (74) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/index.js.map +1 -1
  3. package/dist/resources/components/nuxt/site-source/app/assets/css/site-shell.css +112 -56
  4. package/dist/resources/components/nuxt/site-source/app/assets/icons/brand/android.svg +10 -0
  5. package/dist/resources/components/nuxt/site-source/app/assets/icons/brand/apple.svg +3 -0
  6. package/dist/resources/components/nuxt/site-source/app/assets/icons/brand/whatsapp.svg +5 -0
  7. package/dist/resources/components/nuxt/site-source/app/components/app/Sidebar.vue +1 -1
  8. package/dist/resources/components/nuxt/site-source/app/components/checkout/Status.vue +3 -3
  9. package/dist/resources/components/nuxt/site-source/app/components/consent/Banner.vue +23 -8
  10. package/dist/resources/components/nuxt/site-source/app/components/content/CallToAction.vue +59 -0
  11. package/dist/resources/components/nuxt/site-source/app/components/content/Card.vue +35 -0
  12. package/dist/resources/components/nuxt/site-source/app/components/content/Compatibility.vue +10 -0
  13. package/dist/resources/components/nuxt/site-source/app/components/content/Faq.vue +56 -0
  14. package/dist/resources/components/nuxt/site-source/app/components/content/Features.vue +8 -0
  15. package/dist/resources/components/nuxt/site-source/app/components/content/List.vue +1 -8
  16. package/dist/resources/components/nuxt/site-source/app/components/content/Reviews.vue +93 -0
  17. package/dist/resources/components/nuxt/site-source/app/components/countdown/Timer.vue +112 -0
  18. package/dist/resources/components/nuxt/site-source/app/components/locale/Menu.vue +3 -3
  19. package/dist/resources/components/nuxt/site-source/app/components/site/BackToTop.vue +30 -23
  20. package/dist/resources/components/nuxt/site-source/app/components/site/Header.vue +2 -2
  21. package/dist/resources/components/nuxt/site-source/app/components/steps/Progress.vue +36 -0
  22. package/dist/resources/components/nuxt/site-source/app/components/steps/Showcase.vue +340 -0
  23. package/dist/resources/components/nuxt/site-source/app/components/theme/Menu.vue +4 -4
  24. package/dist/resources/components/nuxt/site-source/app/components/ui/Button.vue +28 -2
  25. package/dist/resources/components/nuxt/site-source/app/components/ui/GradientBorder.vue +5 -5
  26. package/dist/resources/components/nuxt/site-source/app/components/ui/Icon.vue +14 -0
  27. package/dist/resources/components/nuxt/site-source/app/components/ui/Image.vue +126 -0
  28. package/dist/resources/components/nuxt/site-source/app/components/ui/Marquee.vue +99 -0
  29. package/dist/resources/components/nuxt/site-source/app/components/ui/Modal.vue +26 -88
  30. package/dist/resources/components/nuxt/site-source/app/components/ui/Popover.vue +87 -93
  31. package/dist/resources/components/nuxt/site-source/app/components/ui/Text.vue +84 -12
  32. package/dist/resources/components/nuxt/site-source/app/components/ui/Video.vue +105 -0
  33. package/dist/resources/components/nuxt/site-source/app/components/whatsapp/Chat.vue +126 -0
  34. package/dist/resources/components/nuxt/site-source/app/components/youtube/Embed.vue +286 -73
  35. package/dist/resources/components/nuxt/site-source/app/composables/useFloatingBottomStack.ts +44 -0
  36. package/dist/resources/components/nuxt/site-source/app/composables/useNavigation.ts +6 -8
  37. package/dist/resources/components/nuxt/site-source/app/consent/types.ts +1 -4
  38. package/dist/resources/components/nuxt/site-source/app/content/types.ts +3 -0
  39. package/dist/resources/components/nuxt/site-source/app/theme/accordion.ts +5 -4
  40. package/dist/resources/components/nuxt/site-source/app/theme/breadcrumb.ts +14 -4
  41. package/dist/resources/components/nuxt/site-source/app/theme/button.ts +138 -28
  42. package/dist/resources/components/nuxt/site-source/app/theme/carousel.ts +11 -7
  43. package/dist/resources/components/nuxt/site-source/app/theme/checkbox.ts +9 -3
  44. package/dist/resources/components/nuxt/site-source/app/theme/drawer.ts +6 -0
  45. package/dist/resources/components/nuxt/site-source/app/theme/form-field.ts +8 -0
  46. package/dist/resources/components/nuxt/site-source/app/theme/icon.ts +6 -0
  47. package/dist/resources/components/nuxt/site-source/app/theme/index.ts +8 -1
  48. package/dist/resources/components/nuxt/site-source/app/theme/input.ts +34 -18
  49. package/dist/resources/components/nuxt/site-source/app/theme/modal.ts +9 -5
  50. package/dist/resources/components/nuxt/site-source/app/theme/pagination.ts +9 -2
  51. package/dist/resources/components/nuxt/site-source/app/theme/radio-group.ts +7 -0
  52. package/dist/resources/components/nuxt/site-source/app/theme/select.ts +19 -7
  53. package/dist/resources/components/nuxt/site-source/app/theme/stepper.ts +15 -6
  54. package/dist/resources/components/nuxt/site-source/app/theme/textarea.ts +6 -0
  55. package/dist/resources/components/nuxt/site-source/app/theme/toast.ts +1 -4
  56. package/dist/resources/components/nuxt/site-source/app/theme/tooltip.ts +5 -1
  57. package/dist/resources/components/registry.json +133 -45
  58. package/dist/resources/components/vue/ui/BottomSheet.vue +5 -1
  59. package/dist/resources/components/vue/ui/ScrollArea.vue +2 -1
  60. package/dist/resources/registry.json +1 -1
  61. package/dist/resources/templates/base/.claude/docs/architecture-template.md +2 -1
  62. package/dist/resources/templates/base/AGENTS.md +7 -0
  63. package/dist/resources/templates/base/CLAUDE.md +7 -0
  64. package/dist/resources/templates/nuxt/.claude/docs/stack.md +14 -1
  65. package/dist/resources/templates/nuxt/DESIGN.md +146 -0
  66. package/dist/resources/templates/nuxt/app/app.config.ts +2 -0
  67. package/dist/resources/templates/nuxt/app/pages/app/index.vue +9 -7
  68. package/dist/resources/templates/nuxt/app/pages/index.vue +12 -27
  69. package/dist/resources/templates/nuxt/app/pages/ui/index.vue +219 -3
  70. package/dist/resources/templates/nuxt/nuxt.config.ts +2 -4
  71. package/dist/resources/templates/nuxt/package.json +2 -1
  72. package/dist/resources/templates/nuxt/stylelint.config.mjs +12 -0
  73. package/package.json +3 -3
  74. package/dist/resources/components/nuxt/site-source/app/assets/icons/common/.gitkeep +0 -0
@@ -40,9 +40,9 @@ onClickOutside(dropdown, () => dropdown.value?.removeAttribute("open"));
40
40
  <ClientOnly>
41
41
  <details ref="dropdown" v-bind="$attrs" class="site-preference" @keydown="handleKeydown">
42
42
  <summary :aria-label="t('theme.current', { theme: t(current.labelKey) })">
43
- <UIcon :name="current.icon" aria-hidden="true" />
43
+ <UiIcon :name="current.icon" aria-hidden="true" />
44
44
  <span class="site-preference__current">{{ t(current.labelKey) }}</span>
45
- <UIcon class="site-preference__chevron" name="i-lucide-chevron-down" aria-hidden="true" />
45
+ <UiIcon class="site-preference__chevron" name="i-lucide-chevron-down" aria-hidden="true" />
46
46
  </summary>
47
47
  <div class="site-preference__menu" :aria-label="t('theme.select')">
48
48
  <button
@@ -52,9 +52,9 @@ onClickOutside(dropdown, () => dropdown.value?.removeAttribute("open"));
52
52
  :aria-pressed="option.value === preference"
53
53
  @click="selectTheme(option.value)"
54
54
  >
55
- <UIcon :name="option.icon" aria-hidden="true" />
55
+ <UiIcon :name="option.icon" aria-hidden="true" />
56
56
  <span>{{ t(option.labelKey) }}</span>
57
- <UIcon
57
+ <UiIcon
58
58
  v-if="option.value === preference"
59
59
  class="site-preference__check"
60
60
  name="i-lucide-check"
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  type ButtonColor = "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral";
3
3
  type ButtonVariant = "solid" | "outline" | "soft" | "subtle" | "ghost" | "link";
4
- type ButtonSize = "xs" | "sm" | "md" | "lg" | "xl";
4
+ type ButtonSize = "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
5
5
 
6
6
  const props = withDefaults(
7
7
  defineProps<{
@@ -21,6 +21,8 @@ const props = withDefaults(
21
21
  leadingIcon?: string;
22
22
  trailingIcon?: string;
23
23
  square?: boolean;
24
+ ripple?: boolean;
25
+ loadingIcon?: string;
24
26
  customClass?: string;
25
27
  ui?: Record<string, unknown>;
26
28
  }>(),
@@ -34,6 +36,7 @@ const props = withDefaults(
34
36
  loading: false,
35
37
  external: false,
36
38
  square: false,
39
+ ripple: false,
37
40
  customClass: "",
38
41
  ui: () => ({}),
39
42
  },
@@ -61,11 +64,12 @@ const resolvedIcon = computed(() => props.leadingIcon || props.icon);
61
64
  :block="isBlock"
62
65
  :disabled="disabled"
63
66
  :loading="loading"
67
+ :loading-icon="loadingIcon"
64
68
  :icon="resolvedIcon"
65
69
  :trailing-icon="trailingIcon"
66
70
  :square="square"
67
71
  :ui="ui"
68
- :class="customClass"
72
+ :class="[customClass, { 'button-pulse': ripple }]"
69
73
  @click="emit('click', $event)"
70
74
  >
71
75
  <template v-if="$slots.leading" #leading>
@@ -77,3 +81,25 @@ const resolvedIcon = computed(() => props.leadingIcon || props.icon);
77
81
  </template>
78
82
  </UButton>
79
83
  </template>
84
+
85
+ <style scoped>
86
+ @keyframes button-pulse {
87
+ from {
88
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--site-color-primary) 20%, transparent);
89
+ }
90
+
91
+ to {
92
+ box-shadow: 0 0 0 35px transparent;
93
+ }
94
+ }
95
+
96
+ .button-pulse {
97
+ animation: button-pulse 3s infinite;
98
+ }
99
+
100
+ @media (prefers-reduced-motion: reduce) {
101
+ .button-pulse {
102
+ animation: none;
103
+ }
104
+ }
105
+ </style>
@@ -8,11 +8,11 @@ const props = withDefaults(
8
8
  direction?: string;
9
9
  }>(),
10
10
  {
11
- padding: "1px",
12
- radius: "10px",
13
- from: "rgb(168 101 80 / 0.26)",
14
- to: "rgb(168 101 80 / 0.14)",
15
- direction: "135deg",
11
+ padding: "2px",
12
+ radius: "20px",
13
+ from: "color-mix(in srgb, var(--site-color-brand) 26%, transparent)",
14
+ to: "color-mix(in srgb, var(--site-color-brand) 14%, transparent)",
15
+ direction: "180deg",
16
16
  },
17
17
  );
18
18
 
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ defineProps<{ name: string; size?: string | number; label?: string }>();
3
+ </script>
4
+
5
+ <template>
6
+ <UIcon
7
+ :name="name"
8
+ :size="size"
9
+ :aria-hidden="label ? false : true"
10
+ :aria-label="label"
11
+ :role="label ? 'img' : undefined"
12
+ focusable="false"
13
+ />
14
+ </template>
@@ -0,0 +1,126 @@
1
+ <script setup lang="ts">
2
+ import type { ComponentPublicInstance, CSSProperties } from "vue";
3
+ import type { ConfiguredImageProviders } from "@nuxt/image";
4
+
5
+ defineOptions({ inheritAttrs: false });
6
+ const props = withDefaults(
7
+ defineProps<{
8
+ src: string;
9
+ alt: string;
10
+ width?: string | number;
11
+ height?: string | number;
12
+ loading?: "lazy" | "eager";
13
+ preset?: string;
14
+ quality?: number;
15
+ fit?: "cover" | "contain" | "fill" | "inside" | "outside" | "none";
16
+ provider?: keyof ConfiguredImageProviders;
17
+ format?: string;
18
+ preload?: boolean;
19
+ placeholder?: boolean | string | number | [number, number];
20
+ position?: string;
21
+ mode?: "normal" | "sprite";
22
+ densities?: string;
23
+ viewportLoad?: boolean;
24
+ rootMargin?: string;
25
+ sizes?: string;
26
+ }>(),
27
+ {
28
+ loading: "lazy",
29
+ quality: 90,
30
+ fit: "cover",
31
+ format: "webp",
32
+ preload: false,
33
+ placeholder: false,
34
+ position: "center",
35
+ mode: "normal",
36
+ viewportLoad: true,
37
+ rootMargin: "50px",
38
+ },
39
+ );
40
+ const emit = defineEmits<{ load: [event: Event]; error: [event: Event | string] }>();
41
+ const element = ref<HTMLImageElement | ComponentPublicInstance | null>(null);
42
+ const visible = ref(false);
43
+ const image = useImage();
44
+ const native = computed(
45
+ () => (props.provider ?? image.options.provider) === "none" || /^(data:|blob:)/.test(props.src),
46
+ );
47
+ const shouldLoad = computed(
48
+ () => props.preload || props.loading === "eager" || !props.viewportLoad || visible.value,
49
+ );
50
+ const { stop } = useIntersectionObserver(
51
+ element,
52
+ (entries) => {
53
+ if (entries.some((entry) => entry.isIntersecting)) {
54
+ visible.value = true;
55
+ stop();
56
+ }
57
+ },
58
+ { rootMargin: props.rootMargin },
59
+ );
60
+ const style = computed<CSSProperties>(() => ({
61
+ objectFit:
62
+ props.mode === "sprite"
63
+ ? "none"
64
+ : props.fit === "inside"
65
+ ? "contain"
66
+ : props.fit === "outside"
67
+ ? "cover"
68
+ : props.fit,
69
+ objectPosition: props.position,
70
+ }));
71
+ useHead(() => ({
72
+ link:
73
+ props.preload && native.value
74
+ ? [{ rel: "preload", as: "image", href: props.src, fetchpriority: "high" as const }]
75
+ : [],
76
+ }));
77
+ </script>
78
+
79
+ <template>
80
+ <img
81
+ v-if="!shouldLoad"
82
+ ref="element"
83
+ v-bind="$attrs"
84
+ src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
85
+ :alt="alt"
86
+ :width="width"
87
+ :height="height"
88
+ :style="style"
89
+ />
90
+ <img
91
+ v-else-if="native"
92
+ ref="element"
93
+ v-bind="$attrs"
94
+ :src="src"
95
+ :alt="alt"
96
+ :width="width"
97
+ :height="height"
98
+ :loading="visible ? 'eager' : loading"
99
+ :sizes="sizes"
100
+ :style="style"
101
+ @load="emit('load', $event)"
102
+ @error="emit('error', $event)"
103
+ />
104
+ <NuxtImg
105
+ v-else
106
+ ref="element"
107
+ v-bind="$attrs"
108
+ :src="src"
109
+ :alt="alt"
110
+ :width="width"
111
+ :height="height"
112
+ :loading="visible ? 'eager' : loading"
113
+ :sizes="sizes"
114
+ :quality="quality"
115
+ :fit="fit"
116
+ :provider="provider"
117
+ :format="format"
118
+ :preload="preload"
119
+ :placeholder="placeholder"
120
+ :preset="preset"
121
+ :densities="densities"
122
+ :style="style"
123
+ @load="emit('load', $event)"
124
+ @error="emit('error', $event)"
125
+ />
126
+ </template>
@@ -0,0 +1,99 @@
1
+ <script setup lang="ts">
2
+ const props = withDefaults(
3
+ defineProps<{
4
+ direction?: "left" | "right";
5
+ duration?: number;
6
+ pauseOnHover?: boolean;
7
+ gap?: string;
8
+ rowClass?: string;
9
+ contentClass?: string;
10
+ }>(),
11
+ {
12
+ direction: "left",
13
+ duration: 40,
14
+ pauseOnHover: true,
15
+ gap: "30px",
16
+ rowClass: "",
17
+ contentClass: "",
18
+ },
19
+ );
20
+ const host = ref<HTMLElement | null>(null);
21
+ const rtl = ref(false);
22
+ onMounted(() => {
23
+ if (host.value) rtl.value = getComputedStyle(host.value).direction === "rtl";
24
+ });
25
+ const reverse = computed(() =>
26
+ rtl.value ? props.direction === "left" : props.direction === "right",
27
+ );
28
+ </script>
29
+
30
+ <template>
31
+ <div
32
+ ref="host"
33
+ class="marquee"
34
+ :class="{ 'marquee--pause': pauseOnHover, 'marquee--reverse': reverse }"
35
+ :style="{ '--marquee-duration': `${duration}s`, '--marquee-gap': gap }"
36
+ >
37
+ <div dir="ltr" class="marquee__row" :class="rowClass">
38
+ <div
39
+ v-for="group in 2"
40
+ :key="group"
41
+ class="marquee__group"
42
+ :class="contentClass"
43
+ :aria-hidden="group === 2 ? true : undefined"
44
+ :inert="group === 2"
45
+ >
46
+ <slot :group-index="group" />
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </template>
51
+
52
+ <style scoped>
53
+ .marquee {
54
+ width: 100%;
55
+ overflow: hidden;
56
+ }
57
+
58
+ .marquee__row {
59
+ display: flex;
60
+ width: max-content;
61
+ animation: marquee var(--marquee-duration) linear infinite;
62
+ }
63
+
64
+ .marquee__group {
65
+ display: flex;
66
+ flex-shrink: 0;
67
+ gap: var(--marquee-gap);
68
+ padding-inline-start: var(--marquee-gap);
69
+ }
70
+
71
+ .marquee--reverse .marquee__row {
72
+ animation-direction: reverse;
73
+ }
74
+
75
+ .marquee--pause:hover .marquee__row,
76
+ .marquee:focus-within .marquee__row {
77
+ animation-play-state: paused;
78
+ }
79
+
80
+ @keyframes marquee {
81
+ to {
82
+ transform: translateX(-50%);
83
+ }
84
+ }
85
+
86
+ @media (prefers-reduced-motion: reduce) {
87
+ .marquee {
88
+ overflow-x: auto;
89
+ }
90
+
91
+ .marquee__row {
92
+ animation: none;
93
+ }
94
+
95
+ .marquee__group[aria-hidden="true"] {
96
+ display: none;
97
+ }
98
+ }
99
+ </style>
@@ -6,12 +6,14 @@ const props = withDefaults(
6
6
  title?: string;
7
7
  description?: string;
8
8
  closeLabel?: string;
9
+ closeIcon?: string;
9
10
  ui?: Record<string, unknown>;
10
11
  }>(),
11
12
  {
12
13
  modelValue: undefined,
13
14
  open: undefined,
14
15
  closeLabel: "Close modal",
16
+ closeIcon: "i-lucide-x",
15
17
  ui: () => ({}),
16
18
  },
17
19
  );
@@ -35,97 +37,33 @@ const isOpen = computed({
35
37
  }
36
38
  },
37
39
  });
38
-
39
- function close() {
40
- isOpen.value = false;
41
- }
42
40
  </script>
43
41
 
44
42
  <template>
45
- <UModal v-bind="$attrs" v-model:open="isOpen" :ui="ui">
46
- <slot name="trigger" />
47
-
48
- <template #content>
49
- <section class="base-modal-panel" role="dialog" aria-modal="true">
50
- <header v-if="title || description || $slots.header" class="base-modal-panel__header">
51
- <slot name="header" :close="close">
52
- <div class="base-modal-panel__copy">
53
- <h2 v-if="title">{{ title }}</h2>
54
- <p v-if="description">{{ description }}</p>
55
- </div>
56
- </slot>
57
- <UButton
58
- color="neutral"
59
- variant="ghost"
60
- size="sm"
61
- square
62
- icon="i-lucide-x"
63
- :aria-label="closeLabel"
64
- @click="close"
65
- />
66
- </header>
67
- <div class="base-modal-panel__body">
68
- <slot name="content" :close="close">
69
- <slot :close="close" />
70
- </slot>
71
- </div>
72
- <footer v-if="$slots.footer" class="base-modal-panel__footer">
73
- <slot name="footer" :close="close" />
74
- </footer>
75
- </section>
43
+ <UModal v-bind="$attrs" v-model:open="isOpen" :title="title" :description="description" :ui="ui">
44
+ <template #close="{ ui: modalUi }">
45
+ <UiButton
46
+ color="neutral"
47
+ variant="ghost"
48
+ :icon="closeIcon"
49
+ size="sm"
50
+ square
51
+ :aria-label="closeLabel"
52
+ :class="modalUi.close()"
53
+ />
54
+ </template>
55
+ <template v-if="$slots.trigger" #default><slot name="trigger" /></template>
56
+ <template v-if="$slots.content" #content="slotProps">
57
+ <slot name="content" v-bind="slotProps" />
58
+ </template>
59
+ <template v-if="$slots.header" #header="slotProps">
60
+ <slot name="header" v-bind="slotProps" />
61
+ </template>
62
+ <template v-if="$slots.body || $slots.default" #body="slotProps">
63
+ <slot name="body" v-bind="slotProps"><slot v-bind="slotProps" /></slot>
64
+ </template>
65
+ <template v-if="$slots.footer" #footer="slotProps">
66
+ <slot name="footer" v-bind="slotProps" />
76
67
  </template>
77
68
  </UModal>
78
69
  </template>
79
-
80
- <style scoped>
81
- .base-modal-panel {
82
- display: grid;
83
- max-height: min(720px, calc(100dvh - 48px));
84
- overflow: hidden;
85
- color: var(--site-color-text);
86
- }
87
-
88
- .base-modal-panel__header,
89
- .base-modal-panel__footer {
90
- display: flex;
91
- align-items: flex-start;
92
- justify-content: space-between;
93
- gap: 16px;
94
- padding: 20px;
95
- }
96
-
97
- .base-modal-panel__header {
98
- border-bottom: 1px solid var(--site-color-border);
99
- }
100
-
101
- .base-modal-panel__footer {
102
- border-top: 1px solid var(--site-color-border);
103
- }
104
-
105
- .base-modal-panel__copy {
106
- display: grid;
107
- min-width: 0;
108
- gap: 4px;
109
- }
110
-
111
- .base-modal-panel__copy h2,
112
- .base-modal-panel__copy p {
113
- margin: 0;
114
- }
115
-
116
- .base-modal-panel__copy h2 {
117
- font-size: 18px;
118
- line-height: 1.35;
119
- }
120
-
121
- .base-modal-panel__copy p {
122
- color: var(--site-color-muted);
123
- line-height: 1.6;
124
- }
125
-
126
- .base-modal-panel__body {
127
- min-width: 0;
128
- overflow: auto;
129
- padding: 20px;
130
- }
131
- </style>