srcdev-nuxt-components 9.1.36 → 9.1.38

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 (32) hide show
  1. package/.claude/settings.json +2 -1
  2. package/.claude/settings.local.json +2 -2
  3. package/.claude/skills/components/capture-qr-code.md +84 -0
  4. package/.claude/skills/components/data-grid.md +167 -0
  5. package/.claude/skills/components/decode-qr-code.md +84 -0
  6. package/.claude/skills/components/display-qr-code.md +64 -0
  7. package/.claude/skills/index.md +5 -2
  8. package/app/components/01.atoms/grids/data-grid/DataGrid.vue +39 -0
  9. package/app/components/01.atoms/grids/data-grid/stories/DataGrid.stories.ts +234 -0
  10. package/app/components/01.atoms/grids/data-grid/tests/DataGrid.spec.ts +140 -0
  11. package/app/components/01.atoms/grids/data-grid/tests/__snapshots__/DataGrid.spec.ts.snap +11 -0
  12. package/app/components/01.atoms/qr-code/DisplayQrCode.vue +50 -0
  13. package/app/components/01.atoms/qr-code/stories/DisplayQrCode.stories.ts +206 -0
  14. package/app/components/01.atoms/qr-code/tests/DisplayQrCode.spec.ts +139 -0
  15. package/app/components/02.molecules/qr-code/CaptureQrCode.vue +142 -0
  16. package/app/components/{qr-code → 02.molecules/qr-code}/DecodeQrCode.vue +11 -35
  17. package/app/components/02.molecules/qr-code/stories/QrCode.stories.ts +101 -0
  18. package/app/components/02.molecules/qr-code/tests/CaptureQrCode.spec.ts +212 -0
  19. package/app/components/02.molecules/qr-code/tests/DecodeQrCode.spec.ts +145 -0
  20. package/app/layouts/default.vue +0 -1
  21. package/app/pages/ui/qr-code/[componentName].vue +3 -3
  22. package/app/pages/ui/simple-grid.vue +2 -2
  23. package/package.json +1 -1
  24. package/.claude/skills/components/scroll-parallax-section.md +0 -148
  25. package/app/components/01.atoms/scroll-parallax-section/ScrollParallaxSection.vue +0 -108
  26. package/app/components/01.atoms/scroll-parallax-section/stories/ScrollParallaxSection.stories.ts +0 -151
  27. package/app/components/01.atoms/scroll-parallax-section/tests/ScrollParallaxSection.spec.ts +0 -91
  28. package/app/components/display-grid/DisplayGridCore.vue +0 -22
  29. package/app/components/qr-code/CaptureQrCode.vue +0 -183
  30. package/app/components/qr-code/DisplayQrCode.vue +0 -53
  31. package/app/components/qr-code/stories/QrCode.stories.ts +0 -933
  32. package/app/pages/ui/scroll-parallax-section.vue +0 -65
@@ -1,91 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import { nextTick } from "vue";
3
- import { mountSuspended } from "@nuxt/test-utils/runtime";
4
- import ScrollParallaxSection from "../ScrollParallaxSection.vue";
5
-
6
- describe("ScrollParallaxSection", () => {
7
- beforeEach(() => {
8
- vi.stubGlobal("requestAnimationFrame", (cb: FrameRequestCallback) => {
9
- cb(0);
10
- return 0;
11
- });
12
- vi.stubGlobal("cancelAnimationFrame", vi.fn());
13
- });
14
-
15
- it("mounts without error", async () => {
16
- const wrapper = await mountSuspended(ScrollParallaxSection, {
17
- props: { backgroundImage: "/img/bg.jpg" },
18
- });
19
- expect(wrapper.vm).toBeTruthy();
20
- });
21
-
22
- it("renders the bg and content divs", async () => {
23
- const wrapper = await mountSuspended(ScrollParallaxSection, {
24
- props: { backgroundImage: "/img/bg.jpg" },
25
- });
26
- expect(wrapper.find(".scroll-parallax-section__bg").exists()).toBe(true);
27
- expect(wrapper.find(".scroll-parallax-section__content").exists()).toBe(true);
28
- });
29
-
30
- it("renders slot content inside __content", async () => {
31
- const wrapper = await mountSuspended(ScrollParallaxSection, {
32
- props: { backgroundImage: "/img/bg.jpg" },
33
- slots: { default: "<p>Hello</p>" },
34
- });
35
- expect(wrapper.find(".scroll-parallax-section__content p").text()).toBe("Hello");
36
- });
37
-
38
- it("renders as the given tag", async () => {
39
- const wrapper = await mountSuspended(ScrollParallaxSection, {
40
- props: { backgroundImage: "/img/bg.jpg", tag: "section" },
41
- });
42
- expect(wrapper.element.tagName.toLowerCase()).toBe("section");
43
- });
44
-
45
- it("defaults to div tag", async () => {
46
- const wrapper = await mountSuspended(ScrollParallaxSection, {
47
- props: { backgroundImage: "/img/bg.jpg" },
48
- });
49
- expect(wrapper.element.tagName.toLowerCase()).toBe("div");
50
- });
51
-
52
- it("applies styleClassPassthrough classes", async () => {
53
- const wrapper = await mountSuspended(ScrollParallaxSection, {
54
- props: { backgroundImage: "/img/bg.jpg", styleClassPassthrough: "my-custom-class" },
55
- });
56
- expect(wrapper.classes()).toContain("my-custom-class");
57
- });
58
-
59
- it("adds and removes scroll and resize listeners on mount/unmount", async () => {
60
- const addSpy = vi.spyOn(window, "addEventListener");
61
- const removeSpy = vi.spyOn(window, "removeEventListener");
62
-
63
- const wrapper = await mountSuspended(ScrollParallaxSection, {
64
- props: { backgroundImage: "/img/bg.jpg" },
65
- });
66
-
67
- expect(addSpy).toHaveBeenCalledWith("scroll", expect.any(Function), { passive: true });
68
- expect(addSpy).toHaveBeenCalledWith("resize", expect.any(Function), { passive: true });
69
-
70
- wrapper.unmount();
71
-
72
- expect(removeSpy).toHaveBeenCalledWith("scroll", expect.any(Function));
73
- expect(removeSpy).toHaveBeenCalledWith("resize", expect.any(Function));
74
- });
75
-
76
- it("updates bg transform on scroll", async () => {
77
- const wrapper = await mountSuspended(ScrollParallaxSection, {
78
- props: { backgroundImage: "/img/bg.jpg", parallaxStrength: 0.3 },
79
- });
80
-
81
- const bg = wrapper.find(".scroll-parallax-section__bg").element as HTMLElement;
82
-
83
- Object.defineProperty(wrapper.element, "offsetTop", { value: 400, configurable: true });
84
- Object.defineProperty(wrapper.element, "offsetHeight", { value: 600, configurable: true });
85
-
86
- window.dispatchEvent(new Event("scroll"));
87
- await nextTick();
88
-
89
- expect(bg.style.transform).toMatch(/translate3d\(/);
90
- });
91
- });
@@ -1,22 +0,0 @@
1
- <template>
2
- <div :class="[elementClasses]">
3
- <slot v-for="item in gridData" :key="item.id" :name="item.id"></slot>
4
- </div>
5
- </template>
6
-
7
- <script setup lang="ts">
8
- const props = defineProps({
9
- gridData: {
10
- type: Object,
11
- default: () => ({}),
12
- },
13
- styleClassPassthrough: {
14
- type: [String, Array] as PropType<string | string[]>,
15
- default: () => [],
16
- },
17
- });
18
-
19
- const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
20
-
21
- const gridData = toRef(() => props.gridData);
22
- </script>
@@ -1,183 +0,0 @@
1
- <template>
2
- <div class="capture-qr-stream" :class="[elementClasses]">
3
- <h2>Capture QR Code</h2>
4
- <div v-if="!state.error">
5
- <QrcodeStream v-if="state.cameraOn" ref="qrcodeStreamRef" @error="onError" @detect="onDetect" />
6
- <div v-else class="camera-stopped">
7
- <p>Camera stopped</p>
8
- </div>
9
- <div class="pt-4">
10
- <h5>Scanned QRCodes:</h5>
11
- <ul v-if="result" class="list-disc pl-4">
12
- <li v-for="(r, i) in result" :key="i">
13
- <span class="text-wrap wrap-anywhere">
14
- {{ r }}
15
- </span>
16
- </li>
17
- </ul>
18
- </div>
19
- </div>
20
- <div v-else>
21
- <h3>
22
- {{ state.errorMsg }}
23
- </h3>
24
- <button @click="resetCamera">reset</button>
25
- </div>
26
- </div>
27
- </template>
28
-
29
- <script setup lang="ts">
30
- import type { DetectedBarcode } from "nuxt-qrcode"
31
-
32
- const props = defineProps({
33
- styleClassPassthrough: {
34
- type: [String, Array] as PropType<string | string[]>,
35
- default: () => [],
36
- },
37
- })
38
-
39
- const qrcodeStreamRef = ref()
40
- const result = ref<string[]>()
41
- const state = reactive({
42
- errorMsg: "",
43
- error: false,
44
- cameraOn: true,
45
- })
46
-
47
- // Reset camera state when component mounts
48
- onMounted(() => {
49
- // Reset to default state on mount
50
- state.cameraOn = true
51
- state.error = false
52
- state.errorMsg = ""
53
- result.value = []
54
-
55
- const handleVisibilityChange = () => {
56
- if (document.hidden) {
57
- state.cameraOn = false
58
- stopAllMediaStreams()
59
- }
60
- }
61
-
62
- document.addEventListener("visibilitychange", handleVisibilityChange)
63
-
64
- // Cleanup listener on unmount
65
- onBeforeUnmount(() => {
66
- document.removeEventListener("visibilitychange", handleVisibilityChange)
67
- })
68
- })
69
-
70
- function onDetect(detectedCodes: DetectedBarcode[]) {
71
- result.value = detectedCodes.map((code) => {
72
- // toast.add({
73
- // title: 'Detected',
74
- // description: `Value: ${code.rawValue}`,
75
- // actions: [
76
- // {
77
- // label: 'Copy',
78
- // onClick: () => {
79
- // navigator.clipboard.writeText(code.rawValue)
80
- // },
81
- // },
82
- // ],
83
- // })
84
- return code.rawValue
85
- })
86
- }
87
-
88
- function onError(err: Error) {
89
- state.error = true
90
- state.errorMsg = `[${err.name}]: ${err.message}`
91
- }
92
-
93
- function resetCamera() {
94
- state.error = false
95
- state.cameraOn = true
96
- }
97
-
98
- // Function to stop all media streams
99
- function stopAllMediaStreams() {
100
- // Stop streams via the QrcodeStream component ref
101
- if (qrcodeStreamRef.value) {
102
- try {
103
- // Try to access the video element and stop its tracks
104
- const videoElement = qrcodeStreamRef.value.$el?.querySelector("video")
105
- if (videoElement && videoElement.srcObject) {
106
- const stream = videoElement.srcObject as MediaStream
107
- const tracks = stream.getTracks()
108
- tracks.forEach((track) => {
109
- track.stop()
110
- })
111
- videoElement.srcObject = null
112
- }
113
- } catch (error) {
114
- console.warn("Error stopping camera stream:", error)
115
- }
116
- }
117
-
118
- // Global cleanup: Find all video elements and stop their streams
119
- try {
120
- const allVideoElements = document.querySelectorAll("video")
121
- allVideoElements.forEach((video) => {
122
- if (video.srcObject) {
123
- const stream = video.srcObject as MediaStream
124
- const tracks = stream.getTracks()
125
- tracks.forEach((track) => {
126
- track.stop()
127
- })
128
- video.srcObject = null
129
- }
130
- })
131
- } catch (error) {
132
- console.warn("Error in global video cleanup:", error)
133
- }
134
- }
135
-
136
- // Watch for camera state changes
137
- watch(
138
- () => state.cameraOn,
139
- (newValue) => {
140
- if (!newValue) {
141
- // Wait a tick for the component to unmount, then clean up
142
- nextTick(() => {
143
- stopAllMediaStreams()
144
- })
145
- }
146
- }
147
- )
148
-
149
- // Stop camera when component is unmounted (e.g., route change)
150
- onBeforeUnmount(() => {
151
- state.cameraOn = false
152
- stopAllMediaStreams()
153
- })
154
-
155
- // Also handle dynamic component switching (like in [componentName].vue)
156
- onDeactivated(() => {
157
- state.cameraOn = false
158
- stopAllMediaStreams()
159
- })
160
-
161
- onActivated(() => {
162
- // Reset state when component becomes active again
163
- state.cameraOn = true
164
- state.error = false
165
- state.errorMsg = ""
166
- })
167
-
168
- // Use Nuxt's navigation guard to stop camera before route changes
169
- onBeforeRouteLeave(() => {
170
- state.cameraOn = false
171
- stopAllMediaStreams()
172
- })
173
-
174
- const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
175
- </script>
176
-
177
- <style lang="css">
178
- @layer components {
179
- .capture-qr-stream {
180
- aspect-ratio: 1 / 1;
181
- }
182
- }
183
- </style>
@@ -1,53 +0,0 @@
1
- <template>
2
- <Qrcode :value="qrValue" class="display-qr-code" :variant :radius :blackColor :whiteColor :class="[elementClasses]" />
3
- </template>
4
-
5
- <script setup lang="ts">
6
- import type { QrCodeVariant } from "../../types/components";
7
-
8
- const props = defineProps({
9
- qrValue: {
10
- type: String,
11
- required: true,
12
- },
13
- variant: {
14
- type: Object as PropType<QrCodeVariant>,
15
- default: () => ({
16
- inner: "default",
17
- marker: "default",
18
- pixel: "default",
19
- }),
20
- },
21
- radius: {
22
- type: Number,
23
- default: 0,
24
- },
25
- blackColor: {
26
- type: String,
27
- default: "currentColor",
28
- },
29
- whiteColor: {
30
- type: String,
31
- default: "transparent",
32
- },
33
- size: {
34
- type: String,
35
- default: "256px",
36
- },
37
- styleClassPassthrough: {
38
- type: [String, Array] as PropType<string | string[]>,
39
- default: () => [],
40
- },
41
- });
42
-
43
- const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
44
- </script>
45
-
46
- <style lang="css">
47
- @layer components {
48
- .display-qr-code {
49
- aspect-ratio: 1 / 1;
50
- width: v-bind(size);
51
- }
52
- }
53
- </style>