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
|
@@ -1,18 +1,27 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { ref, type ComponentPublicInstance } from "vue";
|
|
2
|
+
import type { Meta, StoryObj } from "@nuxtjs/storybook";
|
|
3
|
+
import CaptureQrCode from "../CaptureQrCode.vue";
|
|
4
|
+
import CanvasSwitcher from "../../../01.atoms/canvas-switcher/CanvasSwitcher.vue";
|
|
5
|
+
import type { MediaCanvas } from "~/types/components";
|
|
3
6
|
|
|
4
|
-
const meta: Meta<typeof
|
|
7
|
+
const meta: Meta<typeof CaptureQrCode> = {
|
|
5
8
|
title: "Molecules/QR Code/CaptureQrCode",
|
|
6
|
-
component:
|
|
9
|
+
component: CaptureQrCode,
|
|
7
10
|
argTypes: {
|
|
8
11
|
cameraStoppedLabel: {
|
|
9
12
|
control: { type: "text" },
|
|
10
13
|
description: "Message shown while the camera is stopped (e.g. the tab is hidden)",
|
|
11
|
-
table: { category: "Content" },
|
|
14
|
+
table: { category: "Content", defaultValue: { summary: "Camera stopped" } },
|
|
12
15
|
},
|
|
13
16
|
resetCameraLabel: {
|
|
14
17
|
control: { type: "text" },
|
|
15
18
|
description: "Reset button text in the error state",
|
|
19
|
+
table: { category: "Content", defaultValue: { summary: "Reset camera" } },
|
|
20
|
+
},
|
|
21
|
+
errorMessages: {
|
|
22
|
+
control: "object",
|
|
23
|
+
description:
|
|
24
|
+
"Error copy keyed by camera error name (`NotAllowedError`, `NotFoundError`, ...) plus `default`. Merged over the built-in English messages",
|
|
16
25
|
table: { category: "Content" },
|
|
17
26
|
},
|
|
18
27
|
styleClassPassthrough: { table: { disable: true } },
|
|
@@ -20,37 +29,116 @@ const meta: Meta<typeof CaptureQrCodeComponent> = {
|
|
|
20
29
|
args: {
|
|
21
30
|
cameraStoppedLabel: "Camera stopped",
|
|
22
31
|
resetCameraLabel: "Reset camera",
|
|
23
|
-
|
|
32
|
+
errorMessages: {},
|
|
24
33
|
},
|
|
34
|
+
decorators: [
|
|
35
|
+
(story, context) => ({
|
|
36
|
+
components: { story, CanvasSwitcher },
|
|
37
|
+
setup() {
|
|
38
|
+
const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "fullWidthCanvas");
|
|
39
|
+
return { canvasName };
|
|
40
|
+
},
|
|
41
|
+
template: `
|
|
42
|
+
<div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
|
|
43
|
+
<CanvasSwitcher v-model:canvas-name="canvasName" />
|
|
44
|
+
</div>
|
|
45
|
+
<div :class="canvasName" style="margin-inline: auto; padding: 2rem; outline: 1px dashed currentColor;">
|
|
46
|
+
<story />
|
|
47
|
+
</div>
|
|
48
|
+
`,
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
25
51
|
parameters: {
|
|
52
|
+
layout: "fullscreen",
|
|
26
53
|
docs: {
|
|
27
54
|
description: {
|
|
28
55
|
component:
|
|
29
|
-
"Live QR code scanner using the device camera. Needs camera permission; the camera stops
|
|
56
|
+
"Live QR code scanner using the device camera. Needs camera permission; the camera stops while the tab is hidden and restarts when it's shown again, and stops on route leave. " +
|
|
57
|
+
"Results are announced through a polite live region. Copy props: `cameraStoppedLabel`, `resetCameraLabel`, `errorMessages`; the `#error` slot replaces the error block.",
|
|
30
58
|
},
|
|
31
59
|
},
|
|
32
60
|
},
|
|
33
61
|
};
|
|
34
62
|
|
|
35
63
|
export default meta;
|
|
64
|
+
type Story = StoryObj<typeof CaptureQrCode>;
|
|
36
65
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<p style="margin: 0 0
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
66
|
+
export const Default: Story = {
|
|
67
|
+
render: (args) => ({
|
|
68
|
+
components: { CaptureQrCode },
|
|
69
|
+
setup() {
|
|
70
|
+
return { args };
|
|
71
|
+
},
|
|
72
|
+
template: `
|
|
73
|
+
<p style="margin: 0 0 1.6rem;">Allow camera access when prompted, then hold a QR code up to the camera.</p>
|
|
74
|
+
<CaptureQrCode v-bind="args" />
|
|
75
|
+
`,
|
|
76
|
+
}),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
interface CaptureSetupState {
|
|
80
|
+
onDetect: (codes: { rawValue: string }[]) => void;
|
|
81
|
+
onError: (err: Error) => void;
|
|
82
|
+
state: { cameraOn: boolean };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const namedError = (name: string, message = "") => Object.assign(new Error(message), { name });
|
|
55
86
|
|
|
56
|
-
|
|
87
|
+
const hostileResults = [
|
|
88
|
+
"https://example.com/a/very/long/unbroken/path/that/never/wraps/on/its/own?utm_source=northtown&utm_medium=print&utm_campaign=autumn",
|
|
89
|
+
"WIFI:T:WPA;S:Alex Morgan's very long network name for the upstairs studio;P:correcthorsebatterystaple;;",
|
|
90
|
+
"<script>alert('not run')</script>",
|
|
91
|
+
"🙂🙂🙂",
|
|
92
|
+
"مرحبا بالعالم",
|
|
93
|
+
"x",
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
export const StressTest: Story = {
|
|
97
|
+
name: "Stress Test (Worst-Case Data)",
|
|
98
|
+
args: {
|
|
99
|
+
cameraStoppedLabel:
|
|
100
|
+
"Die Kamera wurde angehalten, weil dieser Tab im Hintergrund ist. Kehren Sie zu diesem Tab zurück, um fortzufahren.",
|
|
101
|
+
resetCameraLabel: "Kamerazugriff erneut anfordern und den Scanvorgang neu starten",
|
|
102
|
+
errorMessages: {
|
|
103
|
+
default: "Supercalifragilisticexpialidociousandthensomemoretextwithoutanyspacesatall",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
render: (args) => ({
|
|
107
|
+
components: { CaptureQrCode },
|
|
108
|
+
setup() {
|
|
109
|
+
// Story-only: drive the component's internal handlers, since a camera can't be faked here.
|
|
110
|
+
const captureRef = ref<ComponentPublicInstance | null>(null);
|
|
111
|
+
const internals = () => (captureRef.value?.$ as unknown as { setupState: CaptureSetupState } | undefined)?.setupState;
|
|
112
|
+
const simulateResults = () => internals()?.onDetect(hostileResults.map((rawValue) => ({ rawValue })));
|
|
113
|
+
const simulateKnownError = () => internals()?.onError(namedError("NotAllowedError"));
|
|
114
|
+
const simulateUnknownError = () => internals()?.onError(namedError("SomethingElse", "raw library text"));
|
|
115
|
+
const simulateStopped = () => {
|
|
116
|
+
const state = internals()?.state;
|
|
117
|
+
if (state) state.cameraOn = false;
|
|
118
|
+
};
|
|
119
|
+
return { args, captureRef, simulateResults, simulateKnownError, simulateUnknownError, simulateStopped };
|
|
120
|
+
},
|
|
121
|
+
template: `
|
|
122
|
+
<div style="display: flex; flex-wrap: wrap; gap: 0.8rem; margin-block-end: 1.6rem;">
|
|
123
|
+
<button type="button" @click="simulateResults">Simulate hostile results</button>
|
|
124
|
+
<button type="button" @click="simulateKnownError">Simulate NotAllowedError</button>
|
|
125
|
+
<button type="button" @click="simulateUnknownError">Simulate unknown error</button>
|
|
126
|
+
<button type="button" @click="simulateStopped">Simulate stopped camera</button>
|
|
127
|
+
</div>
|
|
128
|
+
<CaptureQrCode ref="captureRef" v-bind="args" />
|
|
129
|
+
`,
|
|
130
|
+
}),
|
|
131
|
+
parameters: {
|
|
132
|
+
initialCanvas: "mobileCanvas",
|
|
133
|
+
docs: {
|
|
134
|
+
story: { inline: false, height: "720px" },
|
|
135
|
+
description: {
|
|
136
|
+
story:
|
|
137
|
+
"Hostile copy and results at every `CanvasSwitcher` width; the buttons call the component's internal handlers because a real camera can't be faked here. " +
|
|
138
|
+
"Check that: the long URL and Wi-Fi string wrap inside the canvas, `<script>` renders as text, emoji and right-to-left results read correctly, and a single character still shows as a list item; " +
|
|
139
|
+
"`NotAllowedError` shows the built-in plain-language message, an unknown error shows the long unbroken `errorMessages.default` wrapped, and the long German reset label wraps inside its button; " +
|
|
140
|
+
"the stopped message wraps. Switch tabs and back: the camera restarts.",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|
|
@@ -1,53 +1,144 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { ref, type ComponentPublicInstance } from "vue";
|
|
2
|
+
import type { Meta, StoryObj } from "@nuxtjs/storybook";
|
|
3
|
+
import DecodeQrCode from "../DecodeQrCode.vue";
|
|
4
|
+
import CanvasSwitcher from "../../../01.atoms/canvas-switcher/CanvasSwitcher.vue";
|
|
5
|
+
import type { MediaCanvas } from "~/types/components";
|
|
3
6
|
|
|
4
|
-
const meta: Meta<typeof
|
|
7
|
+
const meta: Meta<typeof DecodeQrCode> = {
|
|
5
8
|
title: "Molecules/QR Code/DecodeQrCode",
|
|
6
|
-
component:
|
|
9
|
+
component: DecodeQrCode,
|
|
7
10
|
argTypes: {
|
|
8
11
|
uploadLabel: {
|
|
9
12
|
control: { type: "text" },
|
|
10
13
|
description: "Visible label for the file input",
|
|
11
|
-
table: { category: "Content" },
|
|
14
|
+
table: { category: "Content", defaultValue: { summary: "Upload a QR code image" } },
|
|
12
15
|
},
|
|
13
16
|
dropLabel: {
|
|
14
17
|
control: { type: "text" },
|
|
15
18
|
description: "Instruction text inside the drop zone",
|
|
16
|
-
table: { category: "Content" },
|
|
19
|
+
table: { category: "Content", defaultValue: { summary: "Or drop a QR code image here" } },
|
|
20
|
+
},
|
|
21
|
+
noResultLabel: {
|
|
22
|
+
control: { type: "text" },
|
|
23
|
+
description: "Shown when an image is decoded but contains no QR code",
|
|
24
|
+
table: { category: "Content", defaultValue: { summary: "No QR code was found in that image." } },
|
|
25
|
+
},
|
|
26
|
+
readErrorLabel: {
|
|
27
|
+
control: { type: "text" },
|
|
28
|
+
description: "Shown when a dropped file or URL can't be read as an image",
|
|
29
|
+
table: { category: "Content", defaultValue: { summary: "That file couldn't be read as an image." } },
|
|
17
30
|
},
|
|
18
31
|
styleClassPassthrough: { table: { disable: true } },
|
|
19
32
|
},
|
|
20
33
|
args: {
|
|
21
34
|
uploadLabel: "Upload a QR code image",
|
|
22
35
|
dropLabel: "Or drop a QR code image here",
|
|
23
|
-
|
|
36
|
+
noResultLabel: "No QR code was found in that image.",
|
|
37
|
+
readErrorLabel: "That file couldn't be read as an image.",
|
|
24
38
|
},
|
|
39
|
+
decorators: [
|
|
40
|
+
(story, context) => ({
|
|
41
|
+
components: { story, CanvasSwitcher },
|
|
42
|
+
setup() {
|
|
43
|
+
const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "fullWidthCanvas");
|
|
44
|
+
return { canvasName };
|
|
45
|
+
},
|
|
46
|
+
template: `
|
|
47
|
+
<div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
|
|
48
|
+
<CanvasSwitcher v-model:canvas-name="canvasName" />
|
|
49
|
+
</div>
|
|
50
|
+
<div :class="canvasName" style="margin-inline: auto; padding: 2rem; outline: 1px dashed currentColor;">
|
|
51
|
+
<story />
|
|
52
|
+
</div>
|
|
53
|
+
`,
|
|
54
|
+
}),
|
|
55
|
+
],
|
|
25
56
|
parameters: {
|
|
57
|
+
layout: "fullscreen",
|
|
26
58
|
docs: {
|
|
27
59
|
description: {
|
|
28
60
|
component:
|
|
29
|
-
"Decodes QR codes from an uploaded or dropped image (PNG, JPEG, WEBP and other common formats). Results are announced through a polite live region."
|
|
61
|
+
"Decodes QR codes from an uploaded or dropped image (PNG, JPEG, WEBP and other common formats). Results, or a no-code/unreadable-file message, are announced through a polite live region. " +
|
|
62
|
+
"Copy props: `uploadLabel`, `dropLabel`, `noResultLabel`, `readErrorLabel`.",
|
|
30
63
|
},
|
|
31
64
|
},
|
|
32
65
|
},
|
|
33
66
|
};
|
|
34
67
|
|
|
35
68
|
export default meta;
|
|
69
|
+
type Story = StoryObj<typeof DecodeQrCode>;
|
|
36
70
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
71
|
+
export const Default: Story = {
|
|
72
|
+
render: (args) => ({
|
|
73
|
+
components: { DecodeQrCode },
|
|
74
|
+
setup() {
|
|
75
|
+
return { args };
|
|
76
|
+
},
|
|
77
|
+
template: `<DecodeQrCode v-bind="args" />`,
|
|
78
|
+
}),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
interface DecodeSetupState {
|
|
82
|
+
onDetect: (codes: { rawValue: string }[]) => void;
|
|
83
|
+
onDropping: (dropping: boolean) => void;
|
|
84
|
+
onDropError: () => void;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const hostileResults = [
|
|
88
|
+
"https://example.com/a/very/long/unbroken/path/that/never/wraps/on/its/own?utm_source=northtown&utm_medium=print&utm_campaign=autumn",
|
|
89
|
+
"<b>Not bold</b>",
|
|
90
|
+
"🙂🙂🙂",
|
|
91
|
+
"مرحبا بالعالم",
|
|
92
|
+
"x",
|
|
93
|
+
...Array.from({ length: 10 }, (_, i) => `Code ${i + 1}`),
|
|
94
|
+
];
|
|
52
95
|
|
|
53
|
-
export const
|
|
96
|
+
export const StressTest: Story = {
|
|
97
|
+
name: "Stress Test (Worst-Case Data)",
|
|
98
|
+
args: {
|
|
99
|
+
uploadLabel: "Laden Sie ein Bild mit einem QR-Code von Ihrem Gerät hoch, um ihn zu entschlüsseln",
|
|
100
|
+
dropLabel: "Supercalifragilisticexpialidociousandthensomemoretextwithoutanyspacesatall",
|
|
101
|
+
noResultLabel: "In diesem Bild wurde kein QR-Code gefunden. Versuchen Sie es mit einem schärferen Foto.",
|
|
102
|
+
readErrorLabel: "",
|
|
103
|
+
},
|
|
104
|
+
render: (args) => ({
|
|
105
|
+
components: { DecodeQrCode },
|
|
106
|
+
setup() {
|
|
107
|
+
// Story-only: drive the component's internal handlers to show states without a real image.
|
|
108
|
+
const decodeRef = ref<ComponentPublicInstance | null>(null);
|
|
109
|
+
const internals = () => (decodeRef.value?.$ as unknown as { setupState: DecodeSetupState } | undefined)?.setupState;
|
|
110
|
+
const dropping = ref(false);
|
|
111
|
+
const simulateResults = () => internals()?.onDetect(hostileResults.map((rawValue) => ({ rawValue })));
|
|
112
|
+
const simulateEmpty = () => internals()?.onDetect([]);
|
|
113
|
+
const simulateError = () => internals()?.onDropError();
|
|
114
|
+
const toggleDropping = () => {
|
|
115
|
+
dropping.value = !dropping.value;
|
|
116
|
+
internals()?.onDropping(dropping.value);
|
|
117
|
+
};
|
|
118
|
+
return { args, decodeRef, simulateResults, simulateEmpty, simulateError, toggleDropping };
|
|
119
|
+
},
|
|
120
|
+
template: `
|
|
121
|
+
<div style="display: flex; flex-wrap: wrap; gap: 0.8rem; margin-block-end: 1.6rem;">
|
|
122
|
+
<button type="button" @click="simulateResults">Simulate hostile results</button>
|
|
123
|
+
<button type="button" @click="simulateEmpty">Simulate image with no code</button>
|
|
124
|
+
<button type="button" @click="simulateError">Simulate unreadable file</button>
|
|
125
|
+
<button type="button" @click="toggleDropping">Toggle dragging state</button>
|
|
126
|
+
</div>
|
|
127
|
+
<DecodeQrCode ref="decodeRef" v-bind="args" />
|
|
128
|
+
`,
|
|
129
|
+
}),
|
|
130
|
+
parameters: {
|
|
131
|
+
initialCanvas: "mobileCanvas",
|
|
132
|
+
docs: {
|
|
133
|
+
story: { inline: false, height: "720px" },
|
|
134
|
+
description: {
|
|
135
|
+
story:
|
|
136
|
+
"Hostile copy and results at every `CanvasSwitcher` width; the buttons call the component's internal handlers so each state shows without a real image. " +
|
|
137
|
+
"Check that: the long German upload label wraps and the file input stays inside the canvas; the unbroken drop label wraps inside the drop zone; " +
|
|
138
|
+
"the long URL wraps, `<b>` renders as text, emoji and right-to-left results read correctly, and 15 results stack without overflow; " +
|
|
139
|
+
"the no-code message wraps; `readErrorLabel` is empty here, so the unreadable-file button clears the results and renders no message element at all; " +
|
|
140
|
+
"the dragging state shows the active border and background.",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|