srcdev-nuxt-components 9.6.18 → 9.6.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/consumer/css-relative-colour-calc.md +15 -0
- package/.claude/rules/consumer/ids-from-useid.md +3 -2
- package/.claude/rules/consumer/vue-dynamic-arg-no-spaces.md +16 -0
- package/.claude/skills/component-compliance-checklist.md +11 -1
- package/.claude/skills/component-dynamic-slots.md +2 -2
- package/.claude/skills/component-naming.md +1 -1
- package/.claude/skills/components/accordion-chat.md +99 -0
- package/.claude/skills/components/accordion-group.md +98 -0
- package/.claude/skills/components/action-menu.md +11 -5
- package/.claude/skills/components/auto-grid.md +3 -1
- package/.claude/skills/components/capture-qr-code.md +9 -5
- package/.claude/skills/components/carousel-flip.md +41 -30
- package/.claude/skills/components/column-flow-grid.md +2 -0
- package/.claude/skills/components/dashboard-quad-grid.md +7 -2
- package/.claude/skills/components/dashboard-stats-grid.md +4 -0
- package/.claude/skills/components/decode-qr-code.md +7 -2
- package/.claude/skills/components/deep-expanding-menu-classic.md +31 -30
- package/.claude/skills/components/deep-expanding-menu.md +22 -21
- package/.claude/skills/components/expanding-panel-classic.md +9 -7
- package/.claude/skills/components/expanding-panel.md +9 -8
- package/.claude/skills/components/grid-stack.md +2 -0
- package/.claude/skills/components/layout-grid-by-cols.md +2 -0
- package/.claude/skills/components/masonry-grid.md +9 -1
- package/.claude/skills/components/page-row.md +3 -0
- package/.claude/skills/components/samaritan-prompt.md +24 -8
- package/.claude/skills/components/site-header.md +16 -2
- package/.claude/skills/components/sparkle-effect.md +79 -0
- package/.claude/skills/index.md +16 -14
- package/.claude/skills/storybook-add-story.md +33 -0
- package/.claude/skills/testing-add-unit-test.md +26 -0
- package/.claude/skills/using-component-skills.md +1 -1
- package/.vscode/srcdev-component-accordion-chat.code-snippets +42 -0
- package/.vscode/srcdev-component-accordion-group.code-snippets +38 -0
- package/.vscode/srcdev-component-action-menu.code-snippets +3 -3
- package/.vscode/srcdev-component-capture-decode-qr-code.code-snippets +11 -2
- package/.vscode/srcdev-component-carousel-flip.code-snippets +22 -4
- package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +32 -2
- package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +31 -1
- package/.vscode/srcdev-component-samaritan-prompt.code-snippets +5 -4
- package/.vscode/srcdev-component-site-header.code-snippets +13 -1
- package/.vscode/srcdev-component-sparkle-effect.code-snippets +33 -0
- package/app/components/01.atoms/animations/sparkle-effect/CONSUMER-STYLING.md +75 -0
- package/app/components/01.atoms/animations/sparkle-effect/SparkleEffect.vue +213 -0
- package/app/components/01.atoms/animations/sparkle-effect/stories/SparkleEffect.stories.ts +303 -0
- package/app/components/01.atoms/animations/sparkle-effect/tests/SparkleEffect.spec.ts +150 -0
- package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +1 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +12 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue +19 -17
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts +42 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts +31 -44
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +13 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +40 -31
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts +47 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts +51 -53
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +4 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +47 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +36 -23
- package/app/components/01.atoms/grids/auto-grid/AutoGrid.vue +7 -3
- package/app/components/01.atoms/grids/auto-grid/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/grids/auto-grid/stories/AutoGrid.stories.ts +46 -1
- package/app/components/01.atoms/grids/auto-grid/tests/AutoGrid.spec.ts +40 -27
- package/app/components/01.atoms/grids/auto-grid/tests/__snapshots__/AutoGrid.spec.ts.snap +1 -1
- package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +9 -0
- package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +3 -2
- package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +46 -0
- package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +24 -11
- package/app/components/01.atoms/grids/grid-stack/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/grids/grid-stack/GridStack.vue +2 -0
- package/app/components/01.atoms/grids/grid-stack/stories/GridStack.stories.ts +41 -0
- package/app/components/01.atoms/grids/grid-stack/tests/GridStack.spec.ts +26 -13
- package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +15 -0
- package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +51 -31
- package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +48 -0
- package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +84 -29
- package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +6 -1
- package/app/components/01.atoms/page-row/PageRow.vue +16 -15
- package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +23 -0
- package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +6 -5
- package/app/components/02.molecules/action-menu/ActionMenu.vue +6 -2
- package/app/components/02.molecules/action-menu/ActionMenuItem.vue +6 -3
- package/app/components/02.molecules/action-menu/CONSUMER-STYLING.md +11 -4
- package/app/components/02.molecules/action-menu/stories/ActionMenu.stories.ts +130 -18
- package/app/components/02.molecules/expandable/accordion-chat/AccordionChat.vue +227 -0
- package/app/components/02.molecules/expandable/accordion-chat/CONSUMER-STYLING.md +150 -0
- package/app/components/02.molecules/expandable/accordion-chat/stories/AccordionChat.stories.ts +347 -0
- package/app/components/02.molecules/expandable/accordion-chat/tests/AccordionChat.spec.ts +205 -0
- package/app/components/02.molecules/expandable/accordion-chat/tests/__snapshots__/AccordionChat.spec.ts.snap +40 -0
- package/app/components/02.molecules/expandable/{accordian/AccordianCore.vue → accordion-group/AccordionGroup.vue} +32 -20
- package/app/components/02.molecules/expandable/accordion-group/CONSUMER-STYLING.md +66 -0
- package/app/components/02.molecules/expandable/accordion-group/stories/AccordionGroup.stories.ts +238 -0
- package/app/components/02.molecules/expandable/accordion-group/tests/AccordionGroup.spec.ts +172 -0
- package/app/components/02.molecules/expandable/accordion-group/tests/__snapshots__/AccordionGroup.spec.ts.snap +45 -0
- package/app/components/02.molecules/expandable/expanding-panel/CONSUMER-STYLING.md +1 -0
- package/app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue +11 -6
- package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +82 -1
- package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +24 -30
- package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +8 -8
- package/app/components/02.molecules/expandable/expanding-panel-classic/CONSUMER-STYLING.md +1 -0
- package/app/components/02.molecules/expandable/expanding-panel-classic/ExpandingPanelClassic.vue +10 -5
- package/app/components/02.molecules/expandable/expanding-panel-classic/stories/ExpandingPanelClassic.stories.ts +83 -1
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/ExpandingPanelClassic.spec.ts +24 -30
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/__snapshots__/ExpandingPanelClassic.spec.ts.snap +8 -8
- package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +63 -44
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +74 -37
- package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +90 -27
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +8 -8
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +58 -26
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +113 -57
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +140 -22
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +9 -9
- package/app/components/02.molecules/qr-code/CONSUMER-STYLING.md +22 -7
- package/app/components/02.molecules/qr-code/CaptureQrCode.vue +63 -71
- package/app/components/02.molecules/qr-code/DecodeQrCode.vue +47 -10
- package/app/components/02.molecules/qr-code/stories/CaptureQrCode.stories.ts +114 -26
- package/app/components/02.molecules/qr-code/stories/DecodeQrCode.stories.ts +115 -24
- package/app/components/02.molecules/qr-code/tests/CaptureQrCode.spec.ts +124 -51
- package/app/components/02.molecules/qr-code/tests/DecodeQrCode.spec.ts +74 -25
- package/app/components/02.molecules/samaritan-prompt/CONSUMER-STYLING.md +22 -10
- package/app/components/02.molecules/samaritan-prompt/SamaritanPrompt.vue +30 -12
- package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +34 -10
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPrompt.stories.ts +55 -4
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPromptMixed.stories.ts +67 -7
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPrompt.spec.ts +113 -29
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed.spec.ts +92 -45
- package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +82 -29
- package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +176 -113
- package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts +153 -23
- package/app/components/03.organisms/image-galleries/carousel-flip/tests/CarouselFlip.spec.ts +119 -63
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/NavigationItems.spec.ts.snap +2 -2
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +2 -2
- package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +8 -8
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -4
- package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +41 -23
- package/app/components/03.organisms/site-header/SiteHeader.vue +16 -21
- package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +138 -0
- package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +53 -26
- package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +2 -2
- package/app/types/components/qr-code.d.ts +12 -0
- package/app/utils/graphemes.ts +8 -0
- package/package.json +1 -1
- package/.claude/skills/components/accordian-core.md +0 -180
- package/.vscode/srcdev-component-accordian-core.code-snippets +0 -74
- package/app/components/01.atoms/animations/entry/.nuxt/app.config.mjs +0 -21
- package/app/components/01.atoms/animations/entry/.nuxt/components.d.ts +0 -66
- package/app/components/01.atoms/animations/entry/.nuxt/imports.d.ts +0 -30
- package/app/components/01.atoms/animations/entry/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/01.atoms/animations/entry/.nuxt/types/build.d.ts +0 -23
- package/app/components/01.atoms/animations/entry/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/components.d.ts +0 -71
- package/app/components/01.atoms/animations/entry/.nuxt/types/imports.d.ts +0 -386
- package/app/components/01.atoms/animations/entry/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/01.atoms/animations/entry/.nuxt/types/modules.d.ts +0 -79
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/01.atoms/animations/entry/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/01.atoms/animations/entry/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/01.atoms/animations/entry/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/01.atoms/animations/entry/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/01.atoms/animations/entry/.nuxtrc +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/vite/client/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/app/components/02.molecules/expandable/accordian/CONSUMER-STYLING.md +0 -100
- package/app/components/02.molecules/expandable/accordian/stories/AccordianCore.stories.ts +0 -272
- package/app/components/02.molecules/expandable/accordian/tests/AccordianCore.spec.ts +0 -257
- package/app/components/02.molecules/expandable/accordian/tests/__snapshots__/AccordianCore.spec.ts.snap +0 -95
- package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +0 -21
- package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +0 -66
- package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +0 -30
- package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +0 -23
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +0 -71
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +0 -386
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +0 -79
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
</template>
|
|
25
25
|
|
|
26
26
|
<script setup lang="ts">
|
|
27
|
+
import { usePreferredReducedMotion } from "@vueuse/core";
|
|
27
28
|
import type { SamaritanPromptEffect } from "~/types/components/samaritan-prompt";
|
|
28
29
|
|
|
29
30
|
interface Props {
|
|
@@ -66,6 +67,8 @@ const isPaused = ref(false);
|
|
|
66
67
|
const fadeDurationCss = computed(() => `${props.fadeDuration}ms`);
|
|
67
68
|
const cursorOpacity = computed(() => (props.hideCursorInCycle && !cursorVisible.value ? 0 : 1));
|
|
68
69
|
|
|
70
|
+
const reducedMotion = usePreferredReducedMotion();
|
|
71
|
+
|
|
69
72
|
const timer = useCancellableTimer();
|
|
70
73
|
const { wait, schedule, stop, start } = timer;
|
|
71
74
|
|
|
@@ -87,6 +90,7 @@ const startEffect = () => {
|
|
|
87
90
|
cursorVisible.value = true;
|
|
88
91
|
phase.value = "typing";
|
|
89
92
|
messageIndex.value = 0;
|
|
93
|
+
typedCount.value = 0;
|
|
90
94
|
if (props.effect === "typewriter") {
|
|
91
95
|
schedule(typeTick, props.typeSpeed);
|
|
92
96
|
} else {
|
|
@@ -98,18 +102,21 @@ const startEffect = () => {
|
|
|
98
102
|
type Phase = "typing" | "holding" | "deleting" | "pausing";
|
|
99
103
|
const phase = ref<Phase>("typing");
|
|
100
104
|
const messageIndex = ref(0);
|
|
105
|
+
const typedCount = ref(0);
|
|
101
106
|
|
|
102
107
|
const typeTick = () => {
|
|
103
108
|
const message = props.messages[messageIndex.value];
|
|
104
|
-
if (
|
|
109
|
+
if (message === undefined) return;
|
|
110
|
+
const characters = splitGraphemes(message);
|
|
105
111
|
|
|
106
112
|
switch (phase.value) {
|
|
107
113
|
case "typing":
|
|
108
|
-
if (
|
|
114
|
+
if (typedCount.value === 0 && props.hideCursorInCycle) {
|
|
109
115
|
cursorVisible.value = false;
|
|
110
116
|
}
|
|
111
|
-
if (
|
|
112
|
-
|
|
117
|
+
if (typedCount.value < characters.length) {
|
|
118
|
+
typedCount.value = reducedMotion.value === "reduce" ? characters.length : typedCount.value + 1;
|
|
119
|
+
displayText.value = characters.slice(0, typedCount.value).join("");
|
|
113
120
|
schedule(typeTick, props.typeSpeed);
|
|
114
121
|
} else {
|
|
115
122
|
phase.value = "holding";
|
|
@@ -125,8 +132,9 @@ const typeTick = () => {
|
|
|
125
132
|
break;
|
|
126
133
|
|
|
127
134
|
case "deleting":
|
|
128
|
-
if (
|
|
129
|
-
|
|
135
|
+
if (typedCount.value > 0) {
|
|
136
|
+
typedCount.value = reducedMotion.value === "reduce" ? 0 : typedCount.value - 1;
|
|
137
|
+
displayText.value = characters.slice(0, typedCount.value).join("");
|
|
130
138
|
schedule(typeTick, props.deleteSpeed);
|
|
131
139
|
} else {
|
|
132
140
|
phase.value = "pausing";
|
|
@@ -179,7 +187,7 @@ const runWordPulse = async () => {
|
|
|
179
187
|
};
|
|
180
188
|
|
|
181
189
|
watch(
|
|
182
|
-
() => props.effect
|
|
190
|
+
() => `${props.effect}:${JSON.stringify(props.messages)}`,
|
|
183
191
|
() => {
|
|
184
192
|
stop();
|
|
185
193
|
startEffect();
|
|
@@ -205,6 +213,7 @@ onUnmounted(stop);
|
|
|
205
213
|
display: flex;
|
|
206
214
|
flex-direction: column;
|
|
207
215
|
align-items: center;
|
|
216
|
+
max-inline-size: 100%;
|
|
208
217
|
row-gap: var(--samaritan-prompt-cursor-gap, 0.6rem);
|
|
209
218
|
font-family: var(--samaritan-prompt-font-family, "Mono MMM 5", "Nova Mono", "Courier New", monospace);
|
|
210
219
|
font-size: var(--samaritan-prompt-font-size, 2rem);
|
|
@@ -222,11 +231,20 @@ onUnmounted(stop);
|
|
|
222
231
|
display: flex;
|
|
223
232
|
justify-content: center;
|
|
224
233
|
min-height: 1.2em;
|
|
234
|
+
max-inline-size: 100%;
|
|
235
|
+
min-inline-size: 0;
|
|
225
236
|
|
|
226
237
|
.samaritan-prompt__text {
|
|
227
|
-
color: var(--samaritan-prompt-text-colour,
|
|
228
|
-
white-space:
|
|
238
|
+
color: var(--samaritan-prompt-text-colour, var(--theme-text));
|
|
239
|
+
white-space: var(--samaritan-prompt-text-white-space, normal);
|
|
240
|
+
overflow-wrap: anywhere;
|
|
241
|
+
text-align: center;
|
|
229
242
|
text-transform: var(--samaritan-prompt-text-transform, uppercase);
|
|
243
|
+
display: -webkit-box;
|
|
244
|
+
-webkit-box-orient: vertical;
|
|
245
|
+
overflow: hidden;
|
|
246
|
+
-webkit-line-clamp: var(--samaritan-prompt-text-line-clamp, none);
|
|
247
|
+
line-clamp: var(--samaritan-prompt-text-line-clamp, none);
|
|
230
248
|
}
|
|
231
249
|
}
|
|
232
250
|
|
|
@@ -234,12 +252,12 @@ onUnmounted(stop);
|
|
|
234
252
|
width: 100%;
|
|
235
253
|
min-width: 4ch;
|
|
236
254
|
height: var(--samaritan-prompt-underline-height, 0.15rem);
|
|
237
|
-
background: var(--samaritan-prompt-underline-colour,
|
|
255
|
+
background: var(--samaritan-prompt-underline-colour, var(--theme-text));
|
|
238
256
|
}
|
|
239
257
|
}
|
|
240
258
|
|
|
241
259
|
.samaritan-prompt__cursor {
|
|
242
|
-
color: var(--samaritan-prompt-cursor-colour,
|
|
260
|
+
color: var(--samaritan-prompt-cursor-colour, var(--theme-accent));
|
|
243
261
|
font-size: var(--samaritan-prompt-cursor-size, 2.4rem);
|
|
244
262
|
line-height: 1;
|
|
245
263
|
animation: samaritan-prompt-pulse var(--samaritan-prompt-cursor-pulse-duration, 2.5s) ease-in-out infinite;
|
|
@@ -279,7 +297,7 @@ onUnmounted(stop);
|
|
|
279
297
|
@keyframes samaritan-prompt-pulse {
|
|
280
298
|
0%,
|
|
281
299
|
100% {
|
|
282
|
-
color: var(--samaritan-prompt-cursor-colour,
|
|
300
|
+
color: var(--samaritan-prompt-cursor-colour, var(--theme-accent));
|
|
283
301
|
}
|
|
284
302
|
50% {
|
|
285
303
|
color: var(--samaritan-prompt-cursor-colour-off, transparent);
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
</template>
|
|
21
21
|
|
|
22
22
|
<script setup lang="ts">
|
|
23
|
+
import { usePreferredReducedMotion } from "@vueuse/core";
|
|
23
24
|
import type { SamaritanPromptEffect, SamaritanPromptMessageConfig } from "~/types/components/samaritan-prompt";
|
|
24
25
|
|
|
25
26
|
interface Props {
|
|
@@ -65,6 +66,10 @@ const activeFadeDuration = ref(props.fadeDuration);
|
|
|
65
66
|
const fadeDurationCss = computed(() => `${activeFadeDuration.value}ms`);
|
|
66
67
|
const cursorOpacity = computed(() => (cursorVisible.value ? 1 : 0));
|
|
67
68
|
|
|
69
|
+
const EMPTY_POLL_MS = 500;
|
|
70
|
+
|
|
71
|
+
const reducedMotion = usePreferredReducedMotion();
|
|
72
|
+
|
|
68
73
|
const timer = useCancellableTimer();
|
|
69
74
|
const { wait, stop, start } = timer;
|
|
70
75
|
|
|
@@ -97,8 +102,13 @@ const runTypewriter = async (config: ResolvedConfig) => {
|
|
|
97
102
|
|
|
98
103
|
if (hideCursorInCycle) cursorVisible.value = false;
|
|
99
104
|
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
const characters = splitGraphemes(text);
|
|
106
|
+
const instant = reducedMotion.value === "reduce";
|
|
107
|
+
const typedLengths = instant ? [characters.length] : characters.map((_, i) => i + 1);
|
|
108
|
+
const deletedLengths = instant ? [0] : characters.map((_, i) => characters.length - 1 - i);
|
|
109
|
+
|
|
110
|
+
for (const length of typedLengths) {
|
|
111
|
+
displayText.value = characters.slice(0, length).join("");
|
|
102
112
|
await wait(typeSpeed);
|
|
103
113
|
}
|
|
104
114
|
|
|
@@ -106,8 +116,8 @@ const runTypewriter = async (config: ResolvedConfig) => {
|
|
|
106
116
|
await wait(holdDuration);
|
|
107
117
|
announcedText.value = "";
|
|
108
118
|
|
|
109
|
-
|
|
110
|
-
displayText.value =
|
|
119
|
+
for (const length of deletedLengths) {
|
|
120
|
+
displayText.value = characters.slice(0, length).join("");
|
|
111
121
|
await wait(deleteSpeed);
|
|
112
122
|
}
|
|
113
123
|
|
|
@@ -149,6 +159,10 @@ const runWordPulse = async (config: ResolvedConfig) => {
|
|
|
149
159
|
const runLoop = async () => {
|
|
150
160
|
try {
|
|
151
161
|
while (true) {
|
|
162
|
+
if (!props.messageConfigs.length) {
|
|
163
|
+
await wait(Math.max(props.introDelay, EMPTY_POLL_MS));
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
152
166
|
if (props.introDelay > 0) await wait(props.introDelay);
|
|
153
167
|
|
|
154
168
|
for (const msg of props.messageConfigs) {
|
|
@@ -183,7 +197,7 @@ onUnmounted(stop);
|
|
|
183
197
|
src: url("/fonts/monoMMM_5.ttf") format("truetype");
|
|
184
198
|
font-weight: normal;
|
|
185
199
|
font-style: normal;
|
|
186
|
-
font-display:
|
|
200
|
+
font-display: swap;
|
|
187
201
|
}
|
|
188
202
|
|
|
189
203
|
@layer components {
|
|
@@ -191,6 +205,7 @@ onUnmounted(stop);
|
|
|
191
205
|
display: flex;
|
|
192
206
|
flex-direction: column;
|
|
193
207
|
align-items: center;
|
|
208
|
+
max-inline-size: 100%;
|
|
194
209
|
row-gap: var(--samaritan-prompt-cursor-gap, 0.6rem);
|
|
195
210
|
font-family: var(--samaritan-prompt-font-family, "Mono MMM 5", "Nova Mono", "Courier New", monospace);
|
|
196
211
|
font-size: var(--samaritan-prompt-font-size, 2rem);
|
|
@@ -208,11 +223,20 @@ onUnmounted(stop);
|
|
|
208
223
|
display: flex;
|
|
209
224
|
justify-content: center;
|
|
210
225
|
min-height: 1.2em;
|
|
226
|
+
max-inline-size: 100%;
|
|
227
|
+
min-inline-size: 0;
|
|
211
228
|
|
|
212
229
|
.samaritan-prompt__text {
|
|
213
|
-
color: var(--samaritan-prompt-text-colour,
|
|
214
|
-
white-space:
|
|
230
|
+
color: var(--samaritan-prompt-text-colour, var(--theme-text));
|
|
231
|
+
white-space: var(--samaritan-prompt-text-white-space, normal);
|
|
232
|
+
overflow-wrap: anywhere;
|
|
233
|
+
text-align: center;
|
|
215
234
|
text-transform: var(--samaritan-prompt-text-transform, uppercase);
|
|
235
|
+
display: -webkit-box;
|
|
236
|
+
-webkit-box-orient: vertical;
|
|
237
|
+
overflow: hidden;
|
|
238
|
+
-webkit-line-clamp: var(--samaritan-prompt-text-line-clamp, none);
|
|
239
|
+
line-clamp: var(--samaritan-prompt-text-line-clamp, none);
|
|
216
240
|
}
|
|
217
241
|
}
|
|
218
242
|
|
|
@@ -220,12 +244,12 @@ onUnmounted(stop);
|
|
|
220
244
|
width: 100%;
|
|
221
245
|
min-width: 4ch;
|
|
222
246
|
height: var(--samaritan-prompt-underline-height, 0.15rem);
|
|
223
|
-
background: var(--samaritan-prompt-underline-colour,
|
|
247
|
+
background: var(--samaritan-prompt-underline-colour, var(--theme-text));
|
|
224
248
|
}
|
|
225
249
|
}
|
|
226
250
|
|
|
227
251
|
.samaritan-prompt__cursor {
|
|
228
|
-
color: var(--samaritan-prompt-cursor-colour,
|
|
252
|
+
color: var(--samaritan-prompt-cursor-colour, var(--theme-accent));
|
|
229
253
|
font-size: var(--samaritan-prompt-cursor-size, 2.4rem);
|
|
230
254
|
line-height: 1;
|
|
231
255
|
animation: samaritan-prompt-pulse var(--samaritan-prompt-cursor-pulse-duration, 2.5s) ease-in-out infinite;
|
|
@@ -265,7 +289,7 @@ onUnmounted(stop);
|
|
|
265
289
|
@keyframes samaritan-prompt-pulse {
|
|
266
290
|
0%,
|
|
267
291
|
100% {
|
|
268
|
-
color: var(--samaritan-prompt-cursor-colour,
|
|
292
|
+
color: var(--samaritan-prompt-cursor-colour, var(--theme-accent));
|
|
269
293
|
}
|
|
270
294
|
50% {
|
|
271
295
|
color: var(--samaritan-prompt-cursor-colour-off, transparent);
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { computed } from "vue";
|
|
1
|
+
import { computed, ref } from "vue";
|
|
2
|
+
import CanvasSwitcher from "../../../01.atoms/canvas-switcher/CanvasSwitcher.vue";
|
|
3
|
+
import type { MediaCanvas } from "~/types/components";
|
|
2
4
|
import SamaritanPrompt from "../SamaritanPrompt.vue";
|
|
3
5
|
import type { Meta, StoryObj } from "@nuxtjs/storybook";
|
|
4
6
|
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
+
// Stories render the classic white-on-black look by setting the colour tokens on a dark stage (the
|
|
8
|
+
// defaults follow --theme-text and --theme-accent). Keying on the serialised args restarts the loop when a timing control changes, so the
|
|
7
9
|
// new value is visible straight away instead of after the current cycle.
|
|
8
10
|
const stageStyle =
|
|
9
|
-
"display: grid; place-items: center; min-block-size: 32rem; padding: 3.2rem; background: #000; color: #fff;"
|
|
11
|
+
"display: grid; place-items: center; min-block-size: 32rem; padding: 3.2rem; background: #000; color: #fff; " +
|
|
12
|
+
"--samaritan-prompt-text-colour: #fff; --samaritan-prompt-underline-colour: #fff; --samaritan-prompt-cursor-colour: #cc0000;";
|
|
10
13
|
|
|
11
14
|
const timingArgTypes = {
|
|
12
15
|
typeSpeed: {
|
|
@@ -127,3 +130,51 @@ export const CustomCursorAndTokens: Story = {
|
|
|
127
130
|
`,
|
|
128
131
|
}),
|
|
129
132
|
};
|
|
133
|
+
|
|
134
|
+
export const StressTest: Story = {
|
|
135
|
+
name: "Stress Test (Worst-Case Data)",
|
|
136
|
+
args: {
|
|
137
|
+
messages: [
|
|
138
|
+
"Die Überwachungssysteme sind vollständig aktiviert und melden ungewöhnliche Aktivitäten",
|
|
139
|
+
"https://example.com/a/very/long/unbroken/path/that/never/wraps/on/its/own",
|
|
140
|
+
"",
|
|
141
|
+
"👍🏽 👨👩👧 ok",
|
|
142
|
+
"<b>Not bold</b>",
|
|
143
|
+
"مرحبا بالعالم",
|
|
144
|
+
"x",
|
|
145
|
+
],
|
|
146
|
+
typeSpeed: 40,
|
|
147
|
+
deleteSpeed: 20,
|
|
148
|
+
holdDuration: 1500,
|
|
149
|
+
},
|
|
150
|
+
decorators: [
|
|
151
|
+
(story, context) => ({
|
|
152
|
+
components: { story, CanvasSwitcher },
|
|
153
|
+
setup() {
|
|
154
|
+
const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "fullWidthCanvas");
|
|
155
|
+
return { canvasName };
|
|
156
|
+
},
|
|
157
|
+
template: `
|
|
158
|
+
<div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
|
|
159
|
+
<CanvasSwitcher v-model:canvas-name="canvasName" />
|
|
160
|
+
</div>
|
|
161
|
+
<div :class="canvasName" style="margin-inline: auto; outline: 1px dashed currentColor;">
|
|
162
|
+
<story />
|
|
163
|
+
</div>
|
|
164
|
+
`,
|
|
165
|
+
}),
|
|
166
|
+
],
|
|
167
|
+
parameters: {
|
|
168
|
+
layout: "fullscreen",
|
|
169
|
+
initialCanvas: "mobileCanvas",
|
|
170
|
+
docs: {
|
|
171
|
+
story: { inline: false, height: "520px" },
|
|
172
|
+
description: {
|
|
173
|
+
story:
|
|
174
|
+
"Hostile messages at every `CanvasSwitcher` width, typed fast so the cycle comes round quickly. Check that: the long German message and the unbroken URL wrap inside the canvas and stay centred; " +
|
|
175
|
+
"the empty message is skipped over rather than stopping the loop (until 2026-10-21 it halted the cycle); emoji with a skin tone and a family emoji type as whole characters, never half a glyph; " +
|
|
176
|
+
"`<b>` types as text; the right-to-left message reads correctly; a single character still shows. Try `effect: word-pulse` too, and turn on reduced motion in your OS: each message then appears whole instead of typing.",
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
};
|
package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPromptMixed.stories.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { computed } from "vue";
|
|
1
|
+
import { computed, ref } from "vue";
|
|
2
|
+
import CanvasSwitcher from "../../../01.atoms/canvas-switcher/CanvasSwitcher.vue";
|
|
3
|
+
import type { MediaCanvas, SamaritanPromptMessageConfig } from "~/types/components";
|
|
2
4
|
import SamaritanPromptMixed from "../SamaritanPromptMixed.vue";
|
|
3
5
|
import type { Meta, StoryObj } from "@nuxtjs/storybook";
|
|
4
|
-
import type { SamaritanPromptMessageConfig } from "~/types/components";
|
|
5
6
|
|
|
6
|
-
// Dark stage
|
|
7
|
+
// Dark stage with the classic white-on-black tokens (the defaults follow --theme-text and --theme-accent). Keying on the serialised args restarts the
|
|
7
8
|
// loop when a control changes, so the new value is visible straight away.
|
|
8
9
|
const stageStyle =
|
|
9
|
-
"display: grid; place-items: center; min-block-size: 32rem; padding: 3.2rem; background: #000; color: #fff;"
|
|
10
|
+
"display: grid; place-items: center; min-block-size: 32rem; padding: 3.2rem; background: #000; color: #fff; " +
|
|
11
|
+
"--samaritan-prompt-text-colour: #fff; --samaritan-prompt-underline-colour: #fff; --samaritan-prompt-cursor-colour: #cc0000;";
|
|
10
12
|
|
|
11
13
|
const messageConfigs: SamaritanPromptMessageConfig[] = [
|
|
12
14
|
{ text: "Good evening", effect: "typewriter", holdDuration: 2500 },
|
|
@@ -107,8 +109,8 @@ type Story = StoryObj<typeof SamaritanPromptMixed>;
|
|
|
107
109
|
|
|
108
110
|
export const Default: Story = {};
|
|
109
111
|
|
|
110
|
-
export const
|
|
111
|
-
name: "
|
|
112
|
+
export const ThemeDefaults: Story = {
|
|
113
|
+
name: "Theme Defaults",
|
|
112
114
|
render: (args) => ({
|
|
113
115
|
components: { SamaritanPromptMixed },
|
|
114
116
|
setup() {
|
|
@@ -116,9 +118,67 @@ export const LightPage: Story = {
|
|
|
116
118
|
return { args, restartKey };
|
|
117
119
|
},
|
|
118
120
|
template: `
|
|
119
|
-
<div style="display: grid; place-items: center; min-block-size: 32rem; padding: 3.2rem;
|
|
121
|
+
<div style="display: grid; place-items: center; min-block-size: 32rem; padding: 3.2rem;">
|
|
120
122
|
<SamaritanPromptMixed :key="restartKey" v-bind="args" />
|
|
121
123
|
</div>
|
|
122
124
|
`,
|
|
123
125
|
}),
|
|
126
|
+
parameters: {
|
|
127
|
+
docs: {
|
|
128
|
+
description: {
|
|
129
|
+
story: "No colour tokens set: text and underline follow `--theme-text`, the cursor `--theme-accent`.",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const StressTest: Story = {
|
|
136
|
+
name: "Stress Test (Worst-Case Data)",
|
|
137
|
+
args: {
|
|
138
|
+
introDelay: 0,
|
|
139
|
+
messageConfigs: [
|
|
140
|
+
{
|
|
141
|
+
text: "Die Überwachungssysteme sind vollständig aktiviert und melden ungewöhnliche Aktivitäten",
|
|
142
|
+
effect: "typewriter",
|
|
143
|
+
typeSpeed: 30,
|
|
144
|
+
holdDuration: 1500,
|
|
145
|
+
},
|
|
146
|
+
{ text: "https://example.com/a/very/long/unbroken/path/that/never/wraps/on/its/own", effect: "word-pulse" },
|
|
147
|
+
{ text: "", effect: "typewriter", holdDuration: 300 },
|
|
148
|
+
{ text: "👍🏽 👨👩👧 ok", effect: "typewriter", holdDuration: 1500 },
|
|
149
|
+
{ text: "<b>Not bold</b>", effect: "word-pulse" },
|
|
150
|
+
{ text: "مرحبا بالعالم", effect: "typewriter", holdDuration: 1500 },
|
|
151
|
+
{ text: "x", effect: "typewriter", typeSpeed: 0, deleteSpeed: 0, holdDuration: 800, pauseDuration: 0 },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
decorators: [
|
|
155
|
+
(story, context) => ({
|
|
156
|
+
components: { story, CanvasSwitcher },
|
|
157
|
+
setup() {
|
|
158
|
+
const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "fullWidthCanvas");
|
|
159
|
+
return { canvasName };
|
|
160
|
+
},
|
|
161
|
+
template: `
|
|
162
|
+
<div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
|
|
163
|
+
<CanvasSwitcher v-model:canvas-name="canvasName" />
|
|
164
|
+
</div>
|
|
165
|
+
<div :class="canvasName" style="margin-inline: auto; outline: 1px dashed currentColor;">
|
|
166
|
+
<story />
|
|
167
|
+
</div>
|
|
168
|
+
`,
|
|
169
|
+
}),
|
|
170
|
+
],
|
|
171
|
+
parameters: {
|
|
172
|
+
layout: "fullscreen",
|
|
173
|
+
initialCanvas: "mobileCanvas",
|
|
174
|
+
docs: {
|
|
175
|
+
story: { inline: false, height: "520px" },
|
|
176
|
+
description: {
|
|
177
|
+
story:
|
|
178
|
+
"Hostile message configs at every `CanvasSwitcher` width, with `introDelay: 0`. Check that: the long German message and the unbroken URL wrap inside the canvas and stay centred; " +
|
|
179
|
+
"the empty message passes without stopping the loop; emoji with a skin tone and a family emoji type as whole characters; `<b>` renders as text; the right-to-left message reads correctly; " +
|
|
180
|
+
"the zero-speed single character still shows. Set `messageConfigs` to `[]` in Controls: the component idles instead of freezing the tab (until 2026-10-21 an empty list with `introDelay: 0` locked up the page).",
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
124
184
|
};
|