vanilla-vue-ui 0.0.1 → 0.0.3

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 (30) hide show
  1. package/basic/app-bar/WAppBar.stories.ts +41 -0
  2. package/basic/app-bar/WAppBar.vue +48 -0
  3. package/basic/banner/BannerStore.ts +54 -0
  4. package/basic/banner/WBanner.stories.ts +28 -0
  5. package/basic/banner/WBanner.vue +41 -0
  6. package/basic/breadcrumb/WBreadcrumb.stories.ts +25 -0
  7. package/basic/breadcrumb/WBreadcrumb.vue +97 -0
  8. package/basic/button/WButton.stories.ts +10 -3
  9. package/basic/button/WButton.vue +3 -3
  10. package/basic/icon/WIcon.vue +5 -0
  11. package/basic/range/WRange.vue +113 -17
  12. package/basic/text-field/TextFieldSize.ts +2 -0
  13. package/basic/text-field/WTextField.stories.ts +88 -0
  14. package/basic/text-field/WTextField.vue +188 -0
  15. package/basic/tooltip/WTooltip.vue +1 -1
  16. package/package.json +1 -1
  17. package/template/footer-simple/WFooterSimple.stories.ts +23 -0
  18. package/template/footer-simple/WFooterSimple.vue +20 -0
  19. package/template/navigation-drawer/NavigationDrawer.stories.ts +59 -0
  20. package/template/navigation-drawer/NavigationDrawer.vue +121 -0
  21. package/template/navigation-drawer/NavigationDrawerContent.ts +11 -0
  22. package/template/primary-button/WPrimaryButton.spec.ts +17 -0
  23. package/template/primary-button/WPrimaryButton.stories.ts +30 -0
  24. package/template/primary-button/WPrimaryButton.vue +9 -0
  25. package/template/secondary-button/WSecondaryButton.spec.ts +17 -0
  26. package/template/secondary-button/WSecondaryButton.stories.ts +30 -0
  27. package/template/secondary-button/WSecondaryButton.vue +9 -0
  28. package/template/tree-menu/TreeMenuContent.ts +11 -0
  29. package/template/tree-menu/WTreeMenu.stories.ts +55 -0
  30. package/template/tree-menu/WTreeMenu.vue +152 -0
@@ -0,0 +1,41 @@
1
+ // Replace vue3 with vue if you are using Storybook for Vue 2
2
+ import type { Meta, StoryObj } from '@storybook/vue3';
3
+ import AppBar from './WAppBar.vue';
4
+
5
+ type AppBarProps = InstanceType<typeof AppBar>['$props']
6
+
7
+ const meta: Meta<typeof AppBar> = {
8
+ component: AppBar,
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof AppBar>;
13
+
14
+ /*
15
+ *👇 Render functions are a framework specific feature to allow you control on how the component renders.
16
+ * See https://storybook.js.org/docs/api/csf
17
+ * to learn how to use render functions.
18
+ */
19
+ export const Primary: Story = {
20
+ render: (args: AppBarProps) => ({
21
+ setup() {
22
+ return {
23
+ ...args
24
+ }
25
+ },
26
+ components: { AppBar },
27
+ template: '<AppBar :title="title" :classes="classes"></AppBar>',
28
+ }),
29
+ args: {
30
+ title: 'ダッシュボード',
31
+ classes: {
32
+ text: {
33
+ color: 'text-onSurface dark:text-onSurface-dark'
34
+ },
35
+ icon: {
36
+ color: 'text-onSurface dark:text-onSurface-dark'
37
+ },
38
+ border: 'border-b-2 border-outline-100 dark:border-outline-dark'
39
+ }
40
+ }
41
+ };
@@ -0,0 +1,48 @@
1
+ <template>
2
+ <div :class="['sticky top-0 z-40 flex items-center gap-x-6 px-4 py-4 sm:px-6 lg:pl-80', mergedClasses.border]">
3
+ <button type="button" :class="['-m-2.5 p-2.5 lg:hidden', mergedClasses.icon?.color]" @click="open()">
4
+ <span class="sr-only">Open sidebar</span>
5
+ <Bars3Icon class="h-6 w-6" aria-hidden="true" />
6
+ </button>
7
+ <div :class="['flex-1 text-sm font-semibold leading-6', mergedClasses.text?.color]">{{ title }}</div>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import type { PropType } from 'vue'
13
+ import {
14
+ Bars3Icon,
15
+ } from '@heroicons/vue/24/outline'
16
+ import type { ClassObject } from '../../types/ClassObject';
17
+ import { deepMergeClassObject } from '../../util';
18
+
19
+ const props = defineProps({
20
+ title: {
21
+ type: String as PropType<string>,
22
+ default: "Dashboard"
23
+ },
24
+ classes: {
25
+ type: Object as PropType<ClassObject>,
26
+ }
27
+ })
28
+
29
+ const defaultClasses: ClassObject = {
30
+ text: {
31
+ color: 'text-onSurface dark:text-onSurface-dark'
32
+ },
33
+ icon: {
34
+ color: 'text-onSurface dark:text-onSurface-dark'
35
+ },
36
+ border: 'border-b border-outline dark:border-outline-dark'
37
+ }
38
+
39
+ // props.classesが渡されていない場合、defaultClassesを使用する
40
+ const mergedClasses = props.classes ? deepMergeClassObject(defaultClasses, props.classes) : defaultClasses;
41
+
42
+ const emits = defineEmits(['open'])
43
+
44
+ function open() {
45
+ emits('open')
46
+ }
47
+
48
+ </script>
@@ -0,0 +1,54 @@
1
+ import { ref, computed } from 'vue'
2
+ import { defineStore } from 'pinia'
3
+
4
+ export const BannerStore = defineStore('banner', () => {
5
+
6
+ // リアクティブにするために ref を使用する
7
+ const _isOpen = ref(false)
8
+ const _title = ref('')
9
+ const _contentText = ref('')
10
+ const _okButtonText = ref('')
11
+
12
+ let _completion: ((isConfirmed: boolean | null) => void) | null = null
13
+ // true にするとダイアログ以外の場所をタップしても消えない
14
+ // ボタンでしか閉じれなくなる
15
+ const _persistent = ref(false)
16
+
17
+ // computed にすることで直接変更できなくする
18
+ const isOpen = computed(() => _isOpen)
19
+ const getTitle = computed(() => _title)
20
+ const getContentText = computed(() => _contentText)
21
+ const getOkButtonText = computed(() => _okButtonText)
22
+ const getPersistent = computed(() => _persistent)
23
+
24
+ function open({ title, contentText, okButtonText = 'OK', persistent = false, completion = null }: { title: string; contentText: string; okButtonText?: string; persistent?: boolean; completion?: ((isConfirmed: boolean | null) => void) | null }) {
25
+
26
+ _title.value = title
27
+ _contentText.value = contentText
28
+ _okButtonText.value = okButtonText
29
+ _persistent.value = persistent
30
+ _isOpen.value = true
31
+ _completion = completion
32
+ }
33
+
34
+ function close({ isConfirmed = null }: { isConfirmed?: boolean | null }) {
35
+
36
+ _isOpen.value = false
37
+ _title.value = ''
38
+ _contentText.value = ''
39
+ _okButtonText.value = ''
40
+ _persistent.value = false
41
+
42
+ // コールバック関数が含まれていれば実行
43
+ if (_completion) {
44
+
45
+ // 実行
46
+ _completion(isConfirmed)
47
+
48
+ // リセット
49
+ _completion = null
50
+ }
51
+ }
52
+
53
+ return { isOpen, getPersistent, getTitle, getContentText, getOkButtonText, open, close }
54
+ })
@@ -0,0 +1,28 @@
1
+ // Replace vue3 with vue if you are using Storybook for Vue 2
2
+ import type { Meta, StoryObj } from '@storybook/vue3';
3
+ import Banner from './WBanner.vue';
4
+ import { BannerStore } from './BannerStore';
5
+
6
+
7
+ const meta: Meta<typeof Banner> = {
8
+ component: Banner,
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof Banner>;
13
+
14
+ /*
15
+ *👇 Render functions are a framework specific feature to allow you control on how the component renders.
16
+ * See https://storybook.js.org/docs/api/csf
17
+ * to learn how to use render functions.
18
+ */
19
+ export const Primary: Story = {
20
+ render: () => ({
21
+ setup() {
22
+ const bannerStore = BannerStore()
23
+ bannerStore.open({ title: 'セール', contentText: '初回50パーセントオフキャンペーン実施中!!'})
24
+ },
25
+ components: { Banner },
26
+ template: '<Banner></Banner>',
27
+ }),
28
+ };
@@ -0,0 +1,41 @@
1
+ <!-- eslint-disable vue/multi-word-component-names -->
2
+ <template>
3
+ <div v-if="isOpen">
4
+ <div class="relative isolate flex items-center gap-x-6 overflow-hidden bg-gray-50 px-6 py-2.5 sm:px-3.5 sm:before:flex-1">
5
+ <div class="absolute left-[max(-7rem,calc(50%-52rem))] top-1/2 -z-10 -translate-y-1/2 transform-gpu blur-2xl" aria-hidden="true">
6
+ <div class="aspect-[577/310] w-[36.0625rem] bg-gradient-to-r from-[#ff80b5] to-[#9089fc] opacity-30" style="clip-path: polygon(74.8% 41.9%, 97.2% 73.2%, 100% 34.9%, 92.5% 0.4%, 87.5% 0%, 75% 28.6%, 58.5% 54.6%, 50.1% 56.8%, 46.9% 44%, 48.3% 17.4%, 24.7% 53.9%, 0% 27.9%, 11.9% 74.2%, 24.9% 54.1%, 68.6% 100%, 74.8% 41.9%)" />
7
+ </div>
8
+ <div class="absolute left-[max(45rem,calc(50%+8rem))] top-1/2 -z-10 -translate-y-1/2 transform-gpu blur-2xl" aria-hidden="true">
9
+ <div class="aspect-[577/310] w-[36.0625rem] bg-gradient-to-r from-[#ff80b5] to-[#9089fc] opacity-30" style="clip-path: polygon(74.8% 41.9%, 97.2% 73.2%, 100% 34.9%, 92.5% 0.4%, 87.5% 0%, 75% 28.6%, 58.5% 54.6%, 50.1% 56.8%, 46.9% 44%, 48.3% 17.4%, 24.7% 53.9%, 0% 27.9%, 11.9% 74.2%, 24.9% 54.1%, 68.6% 100%, 74.8% 41.9%)" />
10
+ </div>
11
+ <div class="flex flex-wrap items-center gap-x-4 gap-y-2">
12
+ <p class="text-sm leading-6 text-gray-900">
13
+ <strong class="font-semibold">{{ title }}</strong><svg viewBox="0 0 2 2" class="mx-2 inline h-0.5 w-0.5 fill-current" aria-hidden="true"><circle cx="1" cy="1" r="1" /></svg>{{ contentText }}
14
+ </p>
15
+ <slot />
16
+ </div>
17
+ <div class="flex flex-1 justify-end">
18
+ <button type="button" class="-m-3 p-3 focus-visible:outline-offset-[-4px]" @click="close">
19
+ <span class="sr-only">Dismiss</span>
20
+ <XMarkIcon class="h-5 w-5 text-gray-900" aria-hidden="true" />
21
+ </button>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </template>
26
+
27
+ <script setup lang="ts">
28
+ import { XMarkIcon } from '@heroicons/vue/20/solid'
29
+ import { BannerStore } from './BannerStore'
30
+
31
+ const bannerStore = BannerStore()
32
+ // ストアの状態とアクションをコンポーネントにマッピング
33
+ const isOpen = bannerStore.isOpen;
34
+ const title = bannerStore.getTitle;
35
+ const contentText = bannerStore.getContentText;
36
+
37
+ function close() {
38
+ bannerStore.close({ isConfirmed: false })
39
+ }
40
+
41
+ </script>
@@ -0,0 +1,25 @@
1
+ // Replace vue3 with vue if you are using Storybook for Vue 2
2
+ import type { Meta, StoryObj } from '@storybook/vue3';
3
+ import Breadcrumb from './WBreadcrumb.vue';
4
+
5
+
6
+ const meta: Meta<typeof Breadcrumb> = {
7
+ component: Breadcrumb,
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof Breadcrumb>;
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
+ },
22
+ components: { Breadcrumb },
23
+ template: '<Breadcrumb></Breadcrumb>',
24
+ }),
25
+ };
@@ -0,0 +1,97 @@
1
+ <!-- eslint-disable vue/multi-word-component-names -->
2
+ <template>
3
+ <nav class="flex" aria-label="Breadcrumb">
4
+ <ol role="list" class="flex items-center space-x-4">
5
+ <li>
6
+ <div>
7
+ <a href="/" :class="mergedClasses.base">
8
+ <HomeIcon class="h-5 w-5 flex-shrink-0" aria-hidden="true" />
9
+ <span class="sr-only">Home</span>
10
+ </a>
11
+ </div>
12
+ </li>
13
+ <li v-for="page in pages" :key="page.name">
14
+ <div class="flex items-center">
15
+ <ChevronRightIcon :class="mergedClasses.icon?.base" aria-hidden="true" />
16
+ <a
17
+ :href="`${page.href}`"
18
+ :class="mergedClasses.text?.base"
19
+ :aria-current="page.current ? 'page' : undefined"
20
+ >
21
+ {{ page.name }}
22
+ </a>
23
+ </div>
24
+ </li>
25
+ </ol>
26
+ </nav>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import { ChevronRightIcon, HomeIcon } from '@heroicons/vue/20/solid'
31
+ import { useRouter, useRoute } from 'vue-router'
32
+ import type { RouteMeta } from 'vue-router';
33
+ import { ref, onMounted, watch, type PropType } from 'vue';
34
+ import type { ClassObject } from '../../types/ClassObject';
35
+ import { deepMergeClassObject } from '../../util';
36
+
37
+ const props = defineProps({
38
+ classes: {
39
+ type: Object as PropType<ClassObject>,
40
+ }
41
+ })
42
+
43
+ const defaultClasses: ClassObject = {
44
+ base: 'text-gray-400 hover:text-gray-500',
45
+ text: {
46
+ base: 'ml-4 text-sm font-medium text-gray-500 hover:text-gray-700'
47
+ },
48
+ icon: {
49
+ base: 'h-5 w-5 flex-shrink-0 text-gray-400'
50
+ }
51
+ }
52
+
53
+ // props.classesが渡されていない場合、defaultClassesを使用する
54
+ const mergedClasses = props.classes ? deepMergeClassObject(defaultClasses, props.classes) : defaultClasses;
55
+
56
+ type Page = {
57
+ name: string;
58
+ href: string;
59
+ current: boolean;
60
+ };
61
+
62
+ const pages = ref<Page[]>([]);
63
+
64
+ const router = useRouter();
65
+ const route = useRoute();
66
+
67
+ onMounted(set);
68
+ watch(() => route.path, set);
69
+
70
+ function set() {
71
+ const pathSegments = splitPath(route.path);
72
+ let fullPath = '';
73
+
74
+ pages.value = pathSegments.map((segment, index) => {
75
+ fullPath += '/' + segment;
76
+
77
+ const metaData = getRouteMeta(fullPath);
78
+ // タイトルが undefined の場合は segment をそのまま使用
79
+ const title = metaData?.title as string || segment;
80
+
81
+ return {
82
+ name: title, // ここで string 型を強制的に指定
83
+ href: fullPath,
84
+ current: (index === pathSegments.length - 1) // 最後のセグメントだけを現在とマーク
85
+ };
86
+ });
87
+ }
88
+
89
+ function splitPath(path: string): string[] {
90
+ return path.replace(/^\//, '').split('/');
91
+ }
92
+
93
+ function getRouteMeta(path: string): RouteMeta | undefined {
94
+ const matchedRoute = router.getRoutes().find(route => route.path === path);
95
+ return matchedRoute?.meta;
96
+ }
97
+ </script>
@@ -25,15 +25,15 @@ export const Primary: Story = {
25
25
  },
26
26
  components: { WButton },
27
27
  template: `
28
- <div><w-button :classes="classes">OK</w-button></div>
29
- <div><w-button :classes="classes" href="/">OK</w-button></div>
28
+ <div><w-button>OK</w-button></div>
29
+ <div><w-button href="/">OK</w-button></div>
30
30
  `,
31
31
  }),
32
32
  args: {
33
33
  classes: {
34
34
  color: 'text-onSurface dark:text-onSurface-dark',
35
35
  backgroundColor: '',
36
- border: 'border border-outline-100 dark:border-outline-dark'
36
+ border: 'border border-outline dark:border-outline-dark'
37
37
  }
38
38
  }
39
39
  };
@@ -45,6 +45,13 @@ export const Block: Story = {
45
45
  }),
46
46
  };
47
47
 
48
+ export const Disabled: Story = {
49
+ render: () => ({
50
+ components: { WButton },
51
+ template: '<w-button :disabled="true">OK</w-button>',
52
+ }),
53
+ };
54
+
48
55
  export const Size: Story = {
49
56
  render: () => ({
50
57
  components: { WButton },
@@ -67,10 +67,10 @@ const props = defineProps({
67
67
  const defaultClasses: ClassObject = {
68
68
  base: 'font-bold',
69
69
  spacing: 'py-2 px-4',
70
- backgroundColor: 'bg-white',
70
+ backgroundColor: '',
71
71
  rounded: 'rounded-full',
72
- color: 'hover:bg-gray-50',
73
- border: 'border border-gray-200',
72
+ color: 'text-onSurface dark:text-onSurface-dark hover:bg-surfaceHover dark:hover:bg-surfaceHover-dark',
73
+ border: 'border border-outline dark:border-outline-dark',
74
74
  size: '',
75
75
  }
76
76
 
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="w-6 fill-current">
3
+ <slot />
4
+ </div>
5
+ </template>
@@ -1,23 +1,44 @@
1
1
  <template>
2
- <input
3
- type="range"
4
- class="w-full"
5
- :class="[
6
- mergedClasses.content?.base,
7
- mergedClasses.content?.backgroundColor,
8
- mergedClasses.content?.rounded,
9
- mergedClasses.content?.size,
10
- ]"
11
- :min="min"
12
- :max="max"
13
- :step="step"
14
- :value="modelValue"
15
- @input="inputValue"
16
- >
2
+ <div class="flex">
3
+ <MinusIcon
4
+ class="flex-none"
5
+ :class="[
6
+ mergedClasses.icon?.color
7
+ ]"
8
+ @click="decrement()"
9
+ />
10
+ <div
11
+ class="flex-1 px-2 flex items-center"
12
+ >
13
+ <input
14
+ id="input-range"
15
+ type="range"
16
+ class="w-full"
17
+ :class="[
18
+ mergedClasses.content?.base,
19
+ mergedClasses.content?.backgroundColor,
20
+ mergedClasses.content?.rounded,
21
+ mergedClasses.content?.size,
22
+ ]"
23
+ :min="min"
24
+ :max="max"
25
+ :step="step"
26
+ :value="modelValue"
27
+ @input="inputValue"
28
+ >
29
+ </div>
30
+ <PlusIcon
31
+ :class="[
32
+ mergedClasses.icon?.color,
33
+ ]"
34
+ @click="increment()"
35
+ />
36
+ </div>
17
37
  </template>
18
38
 
19
39
  <script setup lang="ts">
20
- import { defineProps, type PropType, ref } from 'vue'
40
+ import { defineProps, onMounted, type PropType, ref } from 'vue'
41
+ import { MinusIcon, PlusIcon } from '@heroicons/vue/24/outline'
21
42
  import type { ClassObject } from '../../types/ClassObject';
22
43
  import { deepMergeClassObject } from '../../util';
23
44
 
@@ -57,12 +78,45 @@ const defaultClasses: ClassObject = {
57
78
  },
58
79
  content: {
59
80
  base: 'appearance-none [-webkit-appearance: none]',
60
- backgroundColor: 'bg-gray-400',
81
+ backgroundColor: 'bg-gray-300',
61
82
  rounded: 'rounded-full',
62
83
  size: 'h-2'
63
84
  }
64
85
  }
65
86
 
87
+ const inputRangeActiveColor = '#f59e0b'
88
+ const inputRangeInactiveColor = '#d1d5db'
89
+
90
+ type SliderBackgroundOptions = {
91
+ value: number
92
+ max: number
93
+ min: number
94
+ }
95
+
96
+ function updateSliderBg(sliderBackgroundOptions: SliderBackgroundOptions) {
97
+ const inputRange = document.getElementById('input-range') as HTMLInputElement
98
+ if (inputRange) {
99
+ const value = sliderBackgroundOptions.value
100
+ const min = sliderBackgroundOptions.min
101
+ const max = sliderBackgroundOptions.max
102
+ const ratio = ((value - min) / (max - min)) * 100
103
+
104
+ inputRange.style.background = `linear-gradient(90deg, ${inputRangeActiveColor} ${ratio}%, ${inputRangeInactiveColor} ${ratio}%)`
105
+ }
106
+ }
107
+
108
+ onMounted(() => {
109
+ // つまみの前後で色を塗り分ける
110
+ const inputRange = document.getElementById('input-range') as HTMLInputElement
111
+
112
+ if (inputRange) {
113
+ updateSliderBg({ value: Number(modelValue.value), max: props.max, min: props.min }) // 初期値を反映
114
+ inputRange.addEventListener('input', function (e) {
115
+ updateSliderBg({ value: Number(modelValue.value), max: props.max, min: props.min })
116
+ })
117
+ }
118
+ })
119
+
66
120
  // props.classesが渡されていない場合、defaultClassesを使用する
67
121
  const mergedClasses = props.classes ? deepMergeClassObject(defaultClasses, props.classes) : defaultClasses;
68
122
 
@@ -73,6 +127,28 @@ const emit = defineEmits<{
73
127
  'update:modelValue': [value: number]
74
128
  }>()
75
129
 
130
+ function increment() {
131
+ if (Number(modelValue.value) < props.max) {
132
+ const number = Number(modelValue.value)
133
+ modelValue.value = (number + props.step).toString()
134
+ emitCustomEvent(modelValue.value)
135
+ emit('update:modelValue', Number(modelValue.value))
136
+
137
+ updateSliderBg({ value: Number(modelValue.value), max: props.max, min: props.min})
138
+ }
139
+ }
140
+
141
+ function decrement() {
142
+ if (Number(modelValue.value) > props.min) {
143
+ const number = Number(modelValue.value)
144
+ modelValue.value = (number - props.step).toString()
145
+ emitCustomEvent(modelValue.value)
146
+ emit('update:modelValue', Number(modelValue.value))
147
+
148
+ updateSliderBg({ value: Number(modelValue.value), max: props.max, min: props.min})
149
+ }
150
+ }
151
+
76
152
  function inputValue(e: Event) {
77
153
  const target = e.target as HTMLInputElement | null
78
154
 
@@ -91,3 +167,23 @@ function emitCustomEvent(value: string) {
91
167
  emit('customInput', event)
92
168
  }
93
169
  </script>
170
+
171
+ <style>
172
+ input[type="range"]::-webkit-slider-thumb {
173
+ background-color: white;
174
+ appearance: none;
175
+ width: 20px;
176
+ height: 20px;
177
+ border-radius: 50%;
178
+ border: 2px solid rgb(251, 191, 36);
179
+ }
180
+
181
+ input[type="range"]::-moz-range-thumb {
182
+ background-color: white;
183
+ appearance: none;
184
+ width: 20px;
185
+ height: 20px;
186
+ border-radius: 50%;
187
+ border: 2px solid rgb(251, 191, 36);
188
+ }
189
+ </style>
@@ -0,0 +1,2 @@
1
+ export const textFieldSizes = ['xs', 'sm', 'base', 'lg', '2xl', '3xl', '6xl'] as const;
2
+ export type TextFieldSize = typeof textFieldSizes[number];
@@ -0,0 +1,88 @@
1
+ // Replace vue3 with vue if you are using Storybook for Vue 2
2
+ import type { Meta, StoryObj } from '@storybook/vue3';
3
+ import TextField from './WTextField.vue';
4
+
5
+ type TextFieldProps = InstanceType<typeof TextField>['$props']
6
+
7
+ const meta: Meta<typeof TextField> = {
8
+ component: TextField,
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof TextField>;
13
+
14
+ /*
15
+ *👇 Render functions are a framework specific feature to allow you control on how the component renders.
16
+ * See https://storybook.js.org/docs/api/csf
17
+ * to learn how to use render functions.
18
+ */
19
+ export const Primary: Story = {
20
+ render: (args: TextFieldProps) => ({
21
+ setup() {
22
+ return {
23
+ ...args
24
+ }
25
+ },
26
+ components: { TextField },
27
+ template: `
28
+ <div class="p-4">
29
+ <div class="mb-2"><TextField size="xs" value="John" :classes="classes"></TextField></div>
30
+ <div class="mb-2"><TextField size="sm" value="John" :classes="classes"></TextField></div>
31
+ <div class="mb-2"><TextField size="base" value="John" :classes="classes"></TextField></div>
32
+ <div class="mb-2"><TextField size="lg" value="John" :classes="classes"></TextField></div>
33
+ <div class="mb-2"><TextField size="2xl" value="John" :classes="classes"></TextField></div>
34
+ <div class="mb-2"><TextField size="3xl" value="John" :classes="classes"></TextField></div>
35
+ <div class="mb-2"><TextField size="6xl" value="John" :classes="classes"></TextField></div>
36
+ </div>
37
+ `,
38
+ }),
39
+ args: {
40
+ classes: {
41
+ content: {
42
+ input: {
43
+ color: 'text-onSurface dark:text-onSurface-dark',
44
+ backgroundColor: 'bg-white dark:bg-surface-dark'
45
+ }
46
+ }
47
+ }
48
+ }
49
+ };
50
+
51
+ export const WithLabel: Story = {
52
+ render: () => ({
53
+ components: { TextField },
54
+ template: `
55
+ <div class="mb-2"><TextField size="xs" label="Name"></TextField></div>
56
+ <div class="mb-2"><TextField size="sm" label="Name"></TextField></div>
57
+ <div class="mb-2"><TextField size="base" label="Name"></TextField></div>
58
+ <div class="mb-2"><TextField size="lg" label="Name"></TextField></div>
59
+ <div class="mb-2"><TextField size="2xl" label="Name"></TextField></div>
60
+ <div class="mb-2"><TextField size="3xl" label="Name"></TextField></div>
61
+ <div class="mb-2"><TextField size="6xl" label="Name"></TextField></div>
62
+ `,
63
+ }),
64
+ };
65
+
66
+ export const WithPlaceholder: Story = {
67
+ render: () => ({
68
+ components: { TextField },
69
+ template: '<TextField placeholder="入力してください"></TextField>',
70
+ }),
71
+ };
72
+
73
+ export const WithError: Story = {
74
+ render: (args: TextFieldProps) => ({
75
+ setup() {
76
+ return {
77
+ ...args,
78
+ };
79
+ },
80
+ components: { TextField },
81
+ template: '<TextField value="John Smith" :rules="rules"></TextField>',
82
+ }),
83
+ args: {
84
+ rules: [
85
+ (value: string) => value.length <= 5 || 'Max 5 characters',
86
+ ],
87
+ },
88
+ };