vanilla-vue-ui 0.0.7 → 0.0.9

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 (65) hide show
  1. package/basic/accordion/AccordionContent.ts +4 -0
  2. package/basic/accordion/WAccordion.stories.ts +49 -0
  3. package/basic/accordion/WAccordion.vue +86 -0
  4. package/basic/card/WCard.stories.ts +36 -0
  5. package/basic/card/WCard.vue +22 -0
  6. package/basic/carousel/WCarousel.stories.ts +29 -0
  7. package/basic/carousel/WCarousel.vue +90 -0
  8. package/basic/checkbox/WCheckbox.spec.ts +58 -0
  9. package/basic/checkbox/WCheckbox.stories.ts +51 -0
  10. package/basic/checkbox/WCheckbox.vue +42 -0
  11. package/basic/date-picker/WDatePicker.stories.ts +79 -0
  12. package/basic/date-picker/WDatePicker.vue +271 -0
  13. package/basic/dialog/DialogStore.ts +57 -0
  14. package/basic/dialog/WDialog.stories.ts +38 -0
  15. package/basic/dialog/WDialog.vue +66 -0
  16. package/basic/divider/WDivider.spec.ts +42 -0
  17. package/basic/divider/WDivider.stories.ts +40 -0
  18. package/basic/divider/WDivider.vue +56 -0
  19. package/basic/feed/TimeLine.ts +9 -0
  20. package/basic/feed/WFeed.spec.ts +64 -0
  21. package/basic/feed/WFeed.stories.ts +62 -0
  22. package/basic/feed/WFeed.vue +41 -0
  23. package/basic/floating-button/WFloatingButton.stories.ts +24 -0
  24. package/basic/floating-button/WFloatingButton.vue +25 -0
  25. package/basic/gradient-text/WGradientText.stories.ts +23 -0
  26. package/basic/gradient-text/WGradientText.vue +5 -0
  27. package/basic/horizontal-scroll/WHorizontalScroll.stories.ts +29 -0
  28. package/basic/horizontal-scroll/WHorizontalScroll.vue +5 -0
  29. package/basic/loading/LoadingStore.ts +57 -0
  30. package/basic/loading/WLoading.stories.ts +27 -0
  31. package/basic/loading/WLoading.vue +54 -0
  32. package/basic/menu/Menu.stories.ts +55 -0
  33. package/basic/menu/WMenu.vue +96 -0
  34. package/basic/menu/WMenuOption.ts +5 -0
  35. package/basic/navigation-drawer/NavigationDrawerContent.ts +11 -0
  36. package/basic/navigation-drawer/WNavigationDrawer.stories.ts +59 -0
  37. package/basic/navigation-drawer/WNavigationDrawer.vue +123 -0
  38. package/basic/notification/NotificationStore.ts +48 -0
  39. package/basic/notification/WNotification.stories.ts +27 -0
  40. package/basic/notification/WNotification.vue +44 -0
  41. package/basic/pagination/WPagination.stories.ts +30 -0
  42. package/basic/pagination/WPagination.vue +58 -0
  43. package/basic/popup/PopupStore.ts +40 -0
  44. package/basic/popup/WPopup.stories.ts +27 -0
  45. package/basic/popup/WPopup.vue +67 -0
  46. package/basic/select/SelectOption.ts +4 -0
  47. package/basic/select/WSelect.stories.ts +56 -0
  48. package/basic/select/WSelect.vue +58 -0
  49. package/basic/skeleton-loader/WSkeletonBar.vue +24 -0
  50. package/basic/skeleton-loader/WSkeletonLoader.stories.ts +23 -0
  51. package/basic/skeleton-loader/WSkeletonLoader.vue +17 -0
  52. package/basic/slide-over/WSlideOver.stories.ts +23 -0
  53. package/basic/slide-over/WSlideOver.vue +53 -0
  54. package/basic/step/StepContent.ts +8 -0
  55. package/basic/step/StepStatus.ts +3 -0
  56. package/basic/step/WStep.stories.ts +42 -0
  57. package/basic/step/WStep.vue +48 -0
  58. package/basic/table/WTable.stories.ts +23 -0
  59. package/basic/table/WTable.vue +30 -0
  60. package/basic/text-area/TextAreaSize.ts +2 -0
  61. package/basic/text-area/WTextArea.spec.ts +18 -0
  62. package/basic/text-area/WTextArea.stories.ts +41 -0
  63. package/basic/text-area/WTextArea.vue +158 -0
  64. package/package.json +1 -1
  65. package/page-template/app-template/WAppTemplate.vue +2 -2
@@ -0,0 +1,123 @@
1
+ <template>
2
+ <div>
3
+ <TransitionRoot as="template" :show="sidebarOpen">
4
+ <Dialog as="div" class="relative z-50 lg:hidden" @close="sidebarOpen = false">
5
+ <TransitionChild as="template" enter="transition-opacity ease-linear duration-300" enter-from="opacity-0" enter-to="opacity-100" leave="transition-opacity ease-linear duration-300" leave-from="opacity-100" leave-to="opacity-0">
6
+ <div class="fixed inset-0 bg-gray-900/80" />
7
+ </TransitionChild>
8
+
9
+ <div class="fixed inset-0 flex">
10
+ <TransitionChild as="template" enter="transition ease-in-out duration-300 transform" enter-from="-translate-x-full" enter-to="translate-x-0" leave="transition ease-in-out duration-300 transform" leave-from="translate-x-0" leave-to="-translate-x-full">
11
+ <DialogPanel class="relative mr-16 flex w-full max-w-xs flex-1">
12
+ <TransitionChild as="template" enter="ease-in-out duration-300" enter-from="opacity-0" enter-to="opacity-100" leave="ease-in-out duration-300" leave-from="opacity-100" leave-to="opacity-0">
13
+ <div class="absolute left-full top-0 flex w-16 justify-center pt-5">
14
+ <button type="button" class="-m-2.5 p-2.5" @click="sidebarOpen = false">
15
+ <span class="sr-only">Close sidebar</span>
16
+ <XMarkIcon class="h-6 w-6 text-white" aria-hidden="true" />
17
+ </button>
18
+ </div>
19
+ </TransitionChild>
20
+ <div class="flex grow flex-col gap-y-5 overflow-y-auto bg-white px-6 pb-2">
21
+ <div class="flex h-4 shrink-0 items-center"/>
22
+ <nav class="flex flex-1 flex-col">
23
+ <ul role="list" class="flex flex-1 flex-col gap-y-7">
24
+ <li>
25
+ <ul role="list" class="-mx-2 space-y-1">
26
+ <TreeMenu :navigation-items="navigationTop"/>
27
+ </ul>
28
+ </li>
29
+
30
+ <li class="-mx-6 mt-auto px-6 py-2">
31
+ <ul role="list" class="-mx-2 space-y-1">
32
+ <TreeMenu :navigation-items="navigationBottom"/>
33
+ </ul>
34
+ </li>
35
+
36
+ </ul>
37
+ </nav>
38
+ </div>
39
+ </DialogPanel>
40
+ </TransitionChild>
41
+ </div>
42
+ </Dialog>
43
+ </TransitionRoot>
44
+
45
+ <!-- Static sidebar for desktop -->
46
+ <div class="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col">
47
+ <!-- Sidebar component, swap this element with another sidebar if you like -->
48
+ <div class="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6">
49
+ <div class="flex h-4 shrink-0 items-center">
50
+ <!-- <img class="h-8 w-auto" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600" alt="Your Company" /> -->
51
+ </div>
52
+ <nav class="flex flex-1 flex-col">
53
+ <ul role="list" class="flex flex-1 flex-col gap-y-7">
54
+ <li>
55
+ <ul role="list" class="-mx-2 space-y-1">
56
+ <TreeMenu :navigation-items="navigationTop"/>
57
+ </ul>
58
+ </li>
59
+
60
+
61
+ <li class="-mx-6 mt-auto p-6 px-2">
62
+ <ul role="list" class="mx-2 space-y-1">
63
+ <TreeMenu :navigation-items="navigationBottom"/>
64
+ </ul>
65
+ </li>
66
+ </ul>
67
+ </nav>
68
+ </div>
69
+ </div>
70
+
71
+ <AppBar :title="title" @open="sidebarOpen = true"/>
72
+
73
+ <main class="lg:pl-72">
74
+ <div class="">
75
+ <!-- Your content -->
76
+ <slot />
77
+ </div>
78
+ </main>
79
+ </div>
80
+ </template>
81
+
82
+ <script setup lang="ts">
83
+ import type { NavigationDrawerContent } from './NavigationDrawerContent'
84
+ import { ref, watch, type PropType } from 'vue'
85
+ import { useRoute } from 'vue-router'
86
+ import { Dialog, DialogPanel, TransitionChild, TransitionRoot } from '@headlessui/vue'
87
+ import {
88
+ XMarkIcon,
89
+ } from '@heroicons/vue/24/outline'
90
+ import TreeMenu from '../tree-menu/TreeMenu.vue'
91
+ import AppBar from '../app-bar/AppBar.vue'
92
+
93
+ const props = defineProps({
94
+ title: {
95
+ type: String as PropType<string>,
96
+ default: "Dashboard"
97
+ },
98
+ navigationTop: {
99
+ type: Array as PropType<NavigationDrawerContent[]>,
100
+ required: true
101
+ },
102
+ navigationBottom: {
103
+ type: Array as PropType<NavigationDrawerContent[]>,
104
+ required: true
105
+ }
106
+ })
107
+
108
+ const navigationTop = ref(props.navigationTop)
109
+ const navigationBottom = ref(props.navigationBottom)
110
+
111
+ const sidebarOpen = ref(false)
112
+
113
+ const route = useRoute()
114
+
115
+ watch(route, (newRoute) => {
116
+ // current、isOpen を設定
117
+ navigationTop.value.forEach(item => {
118
+ item.current = item.href === newRoute.path;
119
+ // current だったら sub を表示させる
120
+ item.isOpen = true
121
+ });
122
+ }, { immediate: true });
123
+ </script>
@@ -0,0 +1,48 @@
1
+ import { computed, ref } from 'vue'
2
+ import { defineStore } from 'pinia'
3
+
4
+ export const NotificationStore = defineStore('notification', () => {
5
+
6
+ // リアクティブにするために ref を使用する
7
+ const _isOpen = ref(false)
8
+ let _completion: (() => void) | null = null
9
+
10
+ const _title = ref('')
11
+ const _description = ref('')
12
+
13
+ // computed にすることで直接変更できなくする
14
+ const isOpen = computed(() => _isOpen)
15
+ const title = computed(() => _title)
16
+ const description = computed(() => _description)
17
+
18
+ function open({ title = '', description = '', delay = 3000, completion = null }: { title: string, description: string, delay?: number, completion?: (() => void) | null }) {
19
+ _title.value = title
20
+ _description.value = description
21
+
22
+ _isOpen.value = true
23
+
24
+ _completion = completion
25
+
26
+ // 余韻を残して非表示にs
27
+ setTimeout(() => {
28
+ close()
29
+ }, delay);
30
+ }
31
+
32
+ function close() {
33
+
34
+ _isOpen.value = false
35
+
36
+ // コールバック関数が含まれていれば実行
37
+ if (_completion) {
38
+
39
+ // 実行
40
+ _completion()
41
+
42
+ // リセット
43
+ _completion = null
44
+ }
45
+ }
46
+
47
+ return { isOpen, open, close, title, description }
48
+ })
@@ -0,0 +1,27 @@
1
+ // Replace vue3 with vue if you are using Storybook for Vue 2
2
+ import type { Meta, StoryObj } from '@storybook/vue3';
3
+ import { NotificationStore } from './NotificationStore';
4
+ import Notification from './Notification.vue';
5
+
6
+ const meta: Meta<typeof Notification> = {
7
+ component: Notification,
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof Notification>;
12
+
13
+ /*
14
+ *👇 Render functions are a framework specific feature to allow you control on how the component renders.
15
+ * See https://storybook.js.org/docs/api/csf
16
+ * to learn how to use render functions.
17
+ */
18
+ export const Primary: Story = {
19
+ render: () => ({
20
+ setup() {
21
+ const notificationStore = NotificationStore()
22
+ notificationStore.open({ title: 'タイトル', description: 'ここに説明文が表示さえます。'})
23
+ },
24
+ components: { Notification },
25
+ template: '<Notification></Notification>',
26
+ }),
27
+ };
@@ -0,0 +1,44 @@
1
+ <!-- eslint-disable vue/multi-word-component-names -->
2
+ <template>
3
+ <div aria-live="assertive" class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6">
4
+ <div class="flex w-full flex-col items-center space-y-4 sm:items-end">
5
+ <transition enter-active-class="transform ease-out duration-300 transition" enter-from-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2" enter-to-class="translate-y-0 opacity-100 sm:translate-x-0" leave-active-class="transition ease-in duration-100" leave-from-class="opacity-100" leave-to-class="opacity-0">
6
+ <div v-show="showNotification" class="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5">
7
+ <div class="p-4">
8
+ <div class="flex items-start">
9
+ <div class="flex-shrink-0">
10
+ <CheckCircleIcon class="h-6 w-6 text-green-400" aria-hidden="true" />
11
+ </div>
12
+ <div class="ml-3 w-0 flex-1 pt-0.5">
13
+ <p class="text-sm font-medium text-gray-900">{{ notificationStore.title.value }}</p>
14
+ <p class="mt-1 text-sm text-gray-500">{{ notificationStore.description.value }}</p>
15
+ </div>
16
+ <div class="ml-4 flex flex-shrink-0">
17
+ <button type="button" class="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" @click="close()">
18
+ <span class="sr-only">Close</span>
19
+ <XMarkIcon class="h-5 w-5" aria-hidden="true" />
20
+ </button>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </transition>
26
+ </div>
27
+ </div>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import { CheckCircleIcon } from '@heroicons/vue/24/outline'
32
+ import { XMarkIcon } from '@heroicons/vue/20/solid'
33
+ import { NotificationStore } from './NotificationStore'
34
+
35
+ // 通知用 state を購読
36
+ const notificationStore = NotificationStore()
37
+ const showNotification = notificationStore.isOpen
38
+
39
+ // 通知を閉じる
40
+ function close() {
41
+ notificationStore.close()
42
+ }
43
+
44
+ </script>
@@ -0,0 +1,30 @@
1
+ // Replace vue3 with vue if you are using Storybook for Vue 2
2
+ import type { Meta, StoryObj } from '@storybook/vue3';
3
+
4
+ import Pagination from './Pagination.vue';
5
+
6
+ const meta: Meta<typeof Pagination> = {
7
+ component: Pagination,
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof Pagination>;
12
+
13
+ /*
14
+ *👇 Render functions are a framework specific feature to allow you control on how the component renders.
15
+ * See https://storybook.js.org/docs/api/csf
16
+ * to learn how to use render functions.
17
+ */
18
+ export const Primary: Story = {
19
+ render: () => ({
20
+ components: { Pagination },
21
+ template: '<Pagination nextPath="#" previousPath="#"></Pagination>',
22
+ }),
23
+ };
24
+
25
+ export const Disable: Story = {
26
+ render: () => ({
27
+ components: { Pagination },
28
+ template: '<Pagination></Pagination>',
29
+ }),
30
+ };
@@ -0,0 +1,58 @@
1
+ <!-- eslint-disable vue/multi-word-component-names -->
2
+ <template>
3
+ <nav class="flex items-center justify-between border-t border-gray-200 px-4 sm:px-0">
4
+ <div class="-mt-px flex w-0 flex-1">
5
+ <a
6
+ v-if="props.previousPath"
7
+ :href="props.previousPath"
8
+ class="inline-flex items-center border-t-2 border-transparent pr-1 pt-4 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700"
9
+ >
10
+ <ArrowLongLeftIcon class="mr-3 size-5 text-gray-400" aria-hidden="true" />
11
+ Previous
12
+ </a>
13
+ <span
14
+ v-else
15
+ class="inline-flex items-center border-t-2 border-transparent pr-1 pt-4 text-sm font-medium text-gray-300 cursor-not-allowed"
16
+ >
17
+ <ArrowLongLeftIcon class="mr-3 size-5 text-gray-300" aria-hidden="true" />
18
+ Previous
19
+ </span>
20
+ </div>
21
+
22
+ <div class="-mt-px flex w-0 flex-1 justify-end">
23
+ <a
24
+ v-if="props.nextPath"
25
+ :href="props.nextPath"
26
+ class="inline-flex items-center border-t-2 border-transparent pl-1 pt-4 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700"
27
+ >
28
+ Next
29
+ <ArrowLongRightIcon class="ml-3 size-5 text-gray-400" aria-hidden="true" />
30
+ </a>
31
+ <span
32
+ v-else
33
+ class="inline-flex items-center border-t-2 border-transparent pl-1 pt-4 text-sm font-medium text-gray-300 cursor-not-allowed"
34
+ >
35
+ Next
36
+ <ArrowLongRightIcon class="ml-3 size-5 text-gray-300" aria-hidden="true" />
37
+ </span>
38
+ </div>
39
+ </nav>
40
+ </template>
41
+
42
+ <script setup lang="ts">
43
+ import { ArrowLongLeftIcon, ArrowLongRightIcon } from '@heroicons/vue/20/solid'
44
+ import { defineProps, type PropType } from 'vue';
45
+
46
+ const props = defineProps({
47
+ nextPath: {
48
+ type: String as PropType<string | null>,
49
+ required: false,
50
+ default: null
51
+ },
52
+ previousPath: {
53
+ type: String as PropType<string | null>,
54
+ required: false,
55
+ default: null
56
+ }
57
+ })
58
+ </script>
@@ -0,0 +1,40 @@
1
+ import { ref, computed } from 'vue'
2
+ import { defineStore } from 'pinia'
3
+
4
+ export const PopupStore = defineStore('dialog', () => {
5
+
6
+ // リアクティブにするために ref を使用する
7
+ const _isOpen = ref(false)
8
+ let _completion: ((isConfirmed: boolean | null) => void) | null = null
9
+ // true にするとダイアログ以外の場所をタップしても消えない
10
+ // ボタンでしか閉じれなくなる
11
+ const _persistent = ref(false)
12
+
13
+ // computed にすることで直接変更できなくする
14
+ const isOpen = computed(() => _isOpen)
15
+ const getPersistent = computed(() => _persistent)
16
+
17
+ function open({ persistent = false, completion = null }: { persistent?: boolean; completion?: ((isConfirmed: boolean | null) => void) | null }) {
18
+ _persistent.value = persistent
19
+ _isOpen.value = true
20
+ _completion = completion
21
+ }
22
+
23
+ function close({ isConfirmed = null }: { isConfirmed?: boolean | null }) {
24
+
25
+ _isOpen.value = false
26
+ _persistent.value = false
27
+
28
+ // コールバック関数が含まれていれば実行
29
+ if (_completion) {
30
+
31
+ // 実行
32
+ _completion(isConfirmed)
33
+
34
+ // リセット
35
+ _completion = null
36
+ }
37
+ }
38
+
39
+ return { isOpen, getPersistent, open, close }
40
+ })
@@ -0,0 +1,27 @@
1
+ // Replace vue3 with vue if you are using Storybook for Vue 2
2
+ import type { Meta, StoryObj } from '@storybook/vue3';
3
+ import { PopupStore } from './PopupStore';
4
+ import Popup from './Popup.vue';
5
+
6
+ const meta: Meta<typeof Popup> = {
7
+ component: Popup,
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof Popup>;
12
+
13
+ /*
14
+ *👇 Render functions are a framework specific feature to allow you control on how the component renders.
15
+ * See https://storybook.js.org/docs/api/csf
16
+ * to learn how to use render functions.
17
+ */
18
+ export const Primary: Story = {
19
+ render: () => ({
20
+ setup() {
21
+ const popupStore = PopupStore()
22
+ popupStore.open({ persistent: true })
23
+ },
24
+ components: { Popup },
25
+ template: '<Popup><p>こんにちわ</p></Popup>',
26
+ }),
27
+ };
@@ -0,0 +1,67 @@
1
+ <!-- eslint-disable vue/multi-word-component-names -->
2
+ <template>
3
+ <TransitionRoot as="template" :show="isOpen">
4
+ <Dialog as="div" class="relative z-10" @close="close({ isConfirmed: null })">
5
+ <TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0" enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100" leave-to="opacity-0">
6
+ <div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
7
+ </TransitionChild>
8
+
9
+ <div class="fixed inset-0 z-10 w-screen overflow-y-auto">
10
+ <div class="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
11
+ <TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200" leave-from="opacity-100 translate-y-0 sm:scale-100" leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
12
+ <DialogPanel :class="fullClassObject" class="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 w-full sm:w-full sm:max-w-sm sm:p-6">
13
+ <div :class="{ 'flex-grow': full }" class="">
14
+ <div class="relative">
15
+ <div class="absolute top-0 right-0 z-[41]">
16
+ <XmarkSolidIcon @click="forceClose"/>
17
+ </div>
18
+
19
+ <slot />
20
+ </div>
21
+ </div>
22
+ </DialogPanel>
23
+ </TransitionChild>
24
+ </div>
25
+ </div>
26
+ </Dialog>
27
+ </TransitionRoot>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import type { PropType } from 'vue'
32
+ import { Dialog, DialogPanel, TransitionChild, TransitionRoot } from '@headlessui/vue'
33
+ import { PopupStore } from './PopupStore'
34
+ import XmarkSolidIcon from '../../icon/xmark/XmarkSolidIcon.vue';
35
+
36
+ const props = defineProps({
37
+ full: {
38
+ type: Boolean as PropType<boolean>,
39
+ default: false
40
+ }
41
+ })
42
+
43
+ const fullClassObject = {
44
+ 'flex': props.full,
45
+ 'flex-col': props.full,
46
+ 'min-h-screen': props.full
47
+ }
48
+
49
+ // ダイアログ用 state を購読
50
+ const dialogStore = PopupStore()
51
+ // ストアの状態とアクションをコンポーネントにマッピング
52
+ const isOpen = dialogStore.isOpen;
53
+ const persistent = dialogStore.getPersistent
54
+
55
+ // ダイアログを閉じる
56
+ function close({ isConfirmed }: { isConfirmed: boolean | null }) {
57
+
58
+ // 回答が強制されていて、ボタンを押してない場合はダイアログを閉じさせない
59
+ if (persistent.value == false || isConfirmed != null) {
60
+ dialogStore.close({ isConfirmed: isConfirmed })
61
+ }
62
+ }
63
+
64
+ function forceClose() {
65
+ dialogStore.close({ isConfirmed: null})
66
+ }
67
+ </script>
@@ -0,0 +1,4 @@
1
+ export type SelectOption = {
2
+ key: string;
3
+ name: string;
4
+ }
@@ -0,0 +1,56 @@
1
+ // Replace vue3 with vue if you are using Storybook for Vue 2
2
+ import type { Meta, StoryObj } from '@storybook/vue3';
3
+ import { ref, watchEffect } from "vue"
4
+ import Select from './WSelect.vue';
5
+ import type { SelectOption } from './SelectOption';
6
+
7
+ type SelectProps = InstanceType<typeof Select>['$props']
8
+
9
+ const meta: Meta<typeof Select> = {
10
+ component: Select,
11
+ };
12
+
13
+ export default meta;
14
+ type Story = StoryObj<typeof Select>;
15
+
16
+ const selectOption: SelectOption[] = [
17
+ { key: 'milk', name: 'ミルク' },
18
+ { key: 'orange', name: 'オレンジジュース' },
19
+ { key: 'coffee', name: 'コーヒー' }
20
+ ]
21
+
22
+ const selected: SelectOption = { key: 'coffee', name: 'コーヒー' }
23
+
24
+ /*
25
+ *👇 Render functions are a framework specific feature to allow you control on how the component renders.
26
+ * See https://storybook.js.org/docs/api/csf
27
+ * to learn how to use render functions.
28
+ */
29
+ export const Primary: Story = {
30
+ render: (args: SelectProps) => ({
31
+ setup() {
32
+ // `selected`を直接使うのではなく、`modelValue`を経由して初期値を設定
33
+ const modelValue = ref(args.modelValue);
34
+
35
+ // `update:modelValue`イベントをリッスンして、内部状態を更新
36
+ watchEffect(() => {
37
+ modelValue.value = args.modelValue;
38
+ });
39
+
40
+ return {
41
+ ...args,
42
+ modelValue,
43
+ updateModelValue: (newValue: SelectOption) => {
44
+ modelValue.value = newValue;
45
+ },
46
+ };
47
+ },
48
+ components: { Select },
49
+ template: '<Select :options="options" :title="title" v-model="modelValue"></Select>',
50
+ }),
51
+ args: {
52
+ title: '飲み物',
53
+ options: selectOption,
54
+ modelValue: selected, // `selected`ではなく、`modelValue`として指定
55
+ },
56
+ };
@@ -0,0 +1,58 @@
1
+ <template>
2
+ <Listbox v-model="selected" as="div">
3
+ <ListboxLabel class="block text-sm font-medium leading-6 text-onSurface dark:text-onSurface-dark">{{ title }}</ListboxLabel>
4
+ <div class="relative mt-2">
5
+ <ListboxButton class="relative w-full cursor-default rounded-md bg-surface dark:bg-surface-dark py-1.5 pl-3 pr-10 text-left text-onSurface dark:text-onSurface-dark shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-primary-dark sm:text-sm sm:leading-6">
6
+ <span class="block truncate">{{ selected.name }}</span>
7
+ <span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
8
+ <ChevronUpDownIcon class="h-5 w-5 text-onSurface dark:text-onSurface-dark" aria-hidden="true" />
9
+ </span>
10
+ </ListboxButton>
11
+
12
+ <transition leave-active-class="transition ease-in duration-100" leave-from-class="opacity-100" leave-to-class="opacity-0">
13
+ <ListboxOptions class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-surface dark:bg-surface-dark py-1 text-onSurface dark:text-onSurface-dark shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
14
+ <ListboxOption v-for="option in props.options" :key="option.key" v-slot="{ active, selected }" as="template" :value="option">
15
+ <li :class="[active ? 'bg-primaryContainer dark:bg-primaryContainer-dark text-onPrimaryContainer dark:text-onPrimaryContainer-dark' : 'text-onSurface dark:text-onSurface-dark', 'relative cursor-default select-none py-2 pl-8 pr-4']">
16
+ <span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']">{{ option.name }}</span>
17
+
18
+ <span v-if="selected" :class="[active ? 'text-onSurface dark:text-onSurface-dark' : 'text-onSurface dark:text-onSurface-dark', 'absolute inset-y-0 left-0 flex items-center pl-1.5']">
19
+ <CheckIcon class="h-5 w-5" aria-hidden="true" />
20
+ </span>
21
+ </li>
22
+ </ListboxOption>
23
+ </ListboxOptions>
24
+ </transition>
25
+ </div>
26
+ </Listbox>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import { ref, watch, watchEffect, defineProps, defineEmits, type PropType } from 'vue'
31
+ import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '@headlessui/vue'
32
+ import { CheckIcon, ChevronUpDownIcon } from '@heroicons/vue/20/solid'
33
+ import type { SelectOption } from './SelectOption';
34
+
35
+
36
+ const props = defineProps({
37
+ title: String,
38
+ options: {
39
+ type: Array as PropType<SelectOption[]>,
40
+ required: true
41
+ },
42
+ modelValue: Object as PropType<SelectOption>
43
+ })
44
+
45
+ const emits = defineEmits(['update:modelValue'])
46
+
47
+ const selected = ref<SelectOption>(props.modelValue || props.options[0])
48
+
49
+ watchEffect(() => {
50
+ if (props.modelValue) {
51
+ selected.value = props.modelValue
52
+ }
53
+ })
54
+
55
+ watch(selected, (newValue) => {
56
+ emits('update:modelValue', newValue)
57
+ })
58
+ </script>
@@ -0,0 +1,24 @@
1
+ <template>
2
+ <div
3
+ class="animate-pulse bg-gray-200 rounded"
4
+ :class="widthClass"
5
+ :style="{ height: height + 'px' }"
6
+ />
7
+ </template>
8
+
9
+ <script setup lang="ts">
10
+ import { defineProps, withDefaults } from 'vue'
11
+
12
+ // Props の定義とデフォルト値
13
+ withDefaults(defineProps<{
14
+ /** 幅を指定する Tailwind クラス */
15
+ widthClass?: string
16
+ /** 高さ(px) */
17
+ height?: number
18
+ }>(), {
19
+ // 幅はデフォルトで w-full
20
+ widthClass: 'w-full',
21
+ // 高さはデフォルトで 16px (1rem相当)
22
+ height: 16,
23
+ })
24
+ </script>
@@ -0,0 +1,23 @@
1
+ // Replace vue3 with vue if you are using Storybook for Vue 2
2
+ import type { Meta, StoryObj } from '@storybook/vue3';
3
+
4
+ import SkeletonLoader from './WSkeletonLoader.vue';
5
+
6
+ const meta: Meta<typeof SkeletonLoader> = {
7
+ component: SkeletonLoader,
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof SkeletonLoader>;
12
+
13
+ /*
14
+ *👇 Render functions are a framework specific feature to allow you control on how the component renders.
15
+ * See https://storybook.js.org/docs/api/csf
16
+ * to learn how to use render functions.
17
+ */
18
+ export const Primary: Story = {
19
+ render: () => ({
20
+ components: { SkeletonLoader },
21
+ template: '<SkeletonLoader></SkeletonLoader>',
22
+ }),
23
+ };
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <div class="border rounded-xl border-gray-300 p-4 animate-pulse space-y-4">
3
+ <!-- タイトルっぽいバー -->
4
+ <WSkeletonBar widthClass="w-3/4" :height="24" />
5
+
6
+ <!-- テキストっぽいバーたち -->
7
+ <div class="space-y-2">
8
+ <WSkeletonBar widthClass="w-full" :height="16" />
9
+ <WSkeletonBar widthClass="w-5/6" :height="16" />
10
+ <WSkeletonBar widthClass="w-4/6" :height="16" />
11
+ </div>
12
+ </div>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import WSkeletonBar from './WSkeletonBar.vue'
17
+ </script>