windly-ui 1.0.1 → 1.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 (123) hide show
  1. package/dist/module.d.mts +3 -0
  2. package/dist/module.json +8 -0
  3. package/dist/module.mjs +26 -0
  4. package/dist/runtime/components/Accordion.d.vue.ts +39 -0
  5. package/{ui-library → dist/runtime}/components/Accordion.vue +44 -70
  6. package/dist/runtime/components/Accordion.vue.d.ts +39 -0
  7. package/dist/runtime/components/Avatar.d.vue.ts +141 -0
  8. package/dist/runtime/components/Avatar.vue +72 -0
  9. package/dist/runtime/components/Avatar.vue.d.ts +141 -0
  10. package/dist/runtime/components/Badge.d.vue.ts +133 -0
  11. package/dist/runtime/components/Badge.vue +99 -0
  12. package/dist/runtime/components/Badge.vue.d.ts +133 -0
  13. package/dist/runtime/components/Breadcrumbs.d.vue.ts +83 -0
  14. package/{ui-library → dist/runtime}/components/Breadcrumbs.vue +37 -59
  15. package/dist/runtime/components/Breadcrumbs.vue.d.ts +83 -0
  16. package/dist/runtime/components/Button.d.vue.ts +199 -0
  17. package/dist/runtime/components/Button.vue +125 -0
  18. package/dist/runtime/components/Button.vue.d.ts +199 -0
  19. package/dist/runtime/components/Card.d.vue.ts +83 -0
  20. package/{ui-library → dist/runtime}/components/Card.vue +26 -39
  21. package/dist/runtime/components/Card.vue.d.ts +83 -0
  22. package/dist/runtime/components/Checkbox.d.vue.ts +190 -0
  23. package/{ui-library → dist/runtime}/components/Checkbox.vue +45 -68
  24. package/dist/runtime/components/Checkbox.vue.d.ts +190 -0
  25. package/dist/runtime/components/CodeBlock.d.vue.ts +16 -0
  26. package/dist/runtime/components/CodeBlock.vue +75 -0
  27. package/dist/runtime/components/CodeBlock.vue.d.ts +16 -0
  28. package/dist/runtime/components/ColorPicker.d.vue.ts +91 -0
  29. package/dist/runtime/components/ColorPicker.vue +244 -0
  30. package/dist/runtime/components/ColorPicker.vue.d.ts +91 -0
  31. package/dist/runtime/components/Date.d.vue.ts +137 -0
  32. package/dist/runtime/components/Date.vue +201 -0
  33. package/dist/runtime/components/Date.vue.d.ts +137 -0
  34. package/dist/runtime/components/Dialog.d.vue.ts +103 -0
  35. package/{ui-library → dist/runtime}/components/Dialog.vue +30 -65
  36. package/dist/runtime/components/Dialog.vue.d.ts +103 -0
  37. package/dist/runtime/components/Divider.d.vue.ts +51 -0
  38. package/dist/runtime/components/Divider.vue +52 -0
  39. package/dist/runtime/components/Divider.vue.d.ts +51 -0
  40. package/dist/runtime/components/Drawer.d.vue.ts +30 -0
  41. package/dist/runtime/components/Drawer.vue +43 -0
  42. package/dist/runtime/components/Drawer.vue.d.ts +30 -0
  43. package/dist/runtime/components/Dropdown.d.vue.ts +34 -0
  44. package/dist/runtime/components/Dropdown.vue +127 -0
  45. package/dist/runtime/components/Dropdown.vue.d.ts +34 -0
  46. package/dist/runtime/components/FileUploader.d.vue.ts +58 -0
  47. package/dist/runtime/components/FileUploader.vue +202 -0
  48. package/dist/runtime/components/FileUploader.vue.d.ts +58 -0
  49. package/dist/runtime/components/Icon.d.vue.ts +41 -0
  50. package/dist/runtime/components/Icon.vue +66 -0
  51. package/dist/runtime/components/Icon.vue.d.ts +41 -0
  52. package/dist/runtime/components/Image.d.vue.ts +77 -0
  53. package/dist/runtime/components/Image.vue +61 -0
  54. package/dist/runtime/components/Image.vue.d.ts +77 -0
  55. package/dist/runtime/components/Input.d.vue.ts +66 -0
  56. package/dist/runtime/components/Input.vue +233 -0
  57. package/dist/runtime/components/Input.vue.d.ts +66 -0
  58. package/dist/runtime/components/Radio.d.vue.ts +41 -0
  59. package/dist/runtime/components/Radio.vue +222 -0
  60. package/dist/runtime/components/Radio.vue.d.ts +41 -0
  61. package/dist/runtime/components/ScrollArea.d.vue.ts +79 -0
  62. package/dist/runtime/components/ScrollArea.vue +36 -0
  63. package/dist/runtime/components/ScrollArea.vue.d.ts +79 -0
  64. package/dist/runtime/components/Select.d.vue.ts +43 -0
  65. package/dist/runtime/components/Select.vue +200 -0
  66. package/dist/runtime/components/Select.vue.d.ts +43 -0
  67. package/dist/runtime/components/Stepper.d.vue.ts +46 -0
  68. package/dist/runtime/components/Stepper.vue +218 -0
  69. package/dist/runtime/components/Stepper.vue.d.ts +46 -0
  70. package/dist/runtime/components/TabPanel.d.vue.ts +17 -0
  71. package/dist/runtime/components/TabPanel.vue +21 -0
  72. package/dist/runtime/components/TabPanel.vue.d.ts +17 -0
  73. package/dist/runtime/components/Table.d.vue.ts +40 -0
  74. package/dist/runtime/components/Table.vue +92 -0
  75. package/dist/runtime/components/Table.vue.d.ts +40 -0
  76. package/dist/runtime/components/Tabs.d.vue.ts +20 -0
  77. package/dist/runtime/components/Tabs.vue +62 -0
  78. package/dist/runtime/components/Tabs.vue.d.ts +20 -0
  79. package/dist/runtime/components/Textarea.d.vue.ts +208 -0
  80. package/dist/runtime/components/Textarea.vue +185 -0
  81. package/dist/runtime/components/Textarea.vue.d.ts +208 -0
  82. package/dist/runtime/components/Toggle.d.vue.ts +36 -0
  83. package/dist/runtime/components/Toggle.vue +173 -0
  84. package/dist/runtime/components/Toggle.vue.d.ts +36 -0
  85. package/dist/runtime/components/Tooltip.d.vue.ts +63 -0
  86. package/dist/runtime/components/Tooltip.vue +144 -0
  87. package/dist/runtime/components/Tooltip.vue.d.ts +63 -0
  88. package/dist/runtime/components/uiProps.d.ts +38 -0
  89. package/{ui-library/components/uiProps.ts → dist/runtime/components/uiProps.js} +2 -2
  90. package/dist/runtime/components/useUiClasses.d.ts +43 -0
  91. package/{ui-library/components/useUiClasses.ts → dist/runtime/components/useUiClasses.js} +25 -60
  92. package/dist/runtime/composables/plugin.d.ts +2 -0
  93. package/dist/runtime/composables/plugin.js +3 -0
  94. package/dist/runtime/tailwind-color.d.ts +2 -0
  95. package/dist/runtime/tailwind-color.js +5859 -0
  96. package/dist/types.d.mts +7 -0
  97. package/package.json +14 -16
  98. package/README.md +0 -208
  99. package/ui-library/components/Avatar.vue +0 -121
  100. package/ui-library/components/Badge.vue +0 -116
  101. package/ui-library/components/Button.vue +0 -154
  102. package/ui-library/components/CodeBlock.vue +0 -99
  103. package/ui-library/components/ColorPicker.vue +0 -302
  104. package/ui-library/components/Date.vue +0 -240
  105. package/ui-library/components/Divider.vue +0 -78
  106. package/ui-library/components/Drawer.vue +0 -67
  107. package/ui-library/components/Dropdown.vue +0 -248
  108. package/ui-library/components/FileUploader.vue +0 -330
  109. package/ui-library/components/Icon.vue +0 -82
  110. package/ui-library/components/Image.vue +0 -78
  111. package/ui-library/components/Input.vue +0 -531
  112. package/ui-library/components/Radio.vue +0 -356
  113. package/ui-library/components/ScrollArea.vue +0 -43
  114. package/ui-library/components/Select.vue +0 -309
  115. package/ui-library/components/Stepper.vue +0 -361
  116. package/ui-library/components/TabPanel.vue +0 -25
  117. package/ui-library/components/Table.vue +0 -152
  118. package/ui-library/components/Tabs.vue +0 -71
  119. package/ui-library/components/Textarea.vue +0 -233
  120. package/ui-library/components/Toggle.vue +0 -319
  121. package/ui-library/components/Tooltip.vue +0 -200
  122. package/ui-library/components/plugin.ts +0 -8
  123. package/ui-library/module.ts +0 -20
@@ -1,99 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref, useSlots, computed, isVNode } from 'vue'
3
-
4
- defineProps<{
5
- title?: string
6
- }>()
7
-
8
- const activeTab = ref('example')
9
- const slots = useSlots()
10
-
11
- import type { VNode } from 'vue'
12
-
13
- function stringifyProps(props: unknown): string {
14
- if (!props || typeof props !== 'object') {
15
- return ''
16
- }
17
-
18
- return Object.entries(props as Record<string, any>)
19
- .filter(([key]) => !key.startsWith('on')) // 🔥 KEY FIX
20
- .map(([key, value]) => {
21
- if (value === true) return key
22
- if (value === false || value == null) return ''
23
- return `${key}="${String(value)}"`
24
- })
25
- .filter(Boolean)
26
- .join(' ')
27
- }
28
-
29
- function renderVNode(vnode: VNode): string {
30
- if (typeof vnode.children === 'string') {
31
- return vnode.children
32
- }
33
-
34
- if (vnode.type === Symbol.for('v-fgt')) {
35
- return (vnode.children as VNode[])
36
- .map(renderVNode)
37
- .join('')
38
- }
39
-
40
- if (typeof vnode.type === 'object') {
41
- const typeAny = vnode.type as any
42
- const name = typeAny.__name || typeAny.name || typeAny.displayName || 'Component'
43
- const props = stringifyProps(vnode.props as any)
44
- return `<${name}${props ? ' ' + props : ''} />`
45
- }
46
-
47
- if (typeof vnode.type === 'string') {
48
- const children = Array.isArray(vnode.children)
49
- ? vnode.children
50
- .map(child => (isVNode(child) ? renderVNode(child) : (typeof child === 'string' ? child : '')))
51
- .join('')
52
- : (typeof vnode.children === 'string' ? vnode.children : '')
53
- return `<${vnode.type}>${children}</${vnode.type}>`
54
- }
55
-
56
- return ''
57
- }
58
-
59
- const slotCode = computed(() => {
60
- const vnodes = slots.default?.() || []
61
- return vnodes.map(renderVNode).join('')
62
- })
63
-
64
- const buttonIcon = ref('content_copy')
65
-
66
- const copyCode = async () => {
67
- await navigator.clipboard.writeText(slotCode.value)
68
-
69
- buttonIcon.value = 'check'
70
- setTimeout(() => {
71
- buttonIcon.value = 'content_copy'
72
- }, 2000)
73
- }
74
- </script>
75
- <template>
76
- <div class="border rounded-lg bg-white overflow-hidden">
77
- <!-- Header -->
78
- <div class="flex items-center justify-between px-4 py-2 border-b bg-gray-50">
79
- <h3 v-if="title" class="font-medium text-gray-700">
80
- {{ title }}
81
- </h3>
82
-
83
- <UIButton flat :icon="buttonIcon" size="sm" class="text-gray-600" @click="copyCode" />
84
- </div>
85
-
86
- <!-- Tabs -->
87
- <UITabs v-model="activeTab">
88
- <UITabPanel name="example" label="Example">
89
- <div class="p-4">
90
- <slot />
91
- </div>
92
- </UITabPanel>
93
-
94
- <UITabPanel name="code" label="Code">
95
- <pre class="p-4 bg-gray-900 text-gray-100 text-sm overflow-x-auto"><code>{{ slotCode }}</code></pre>
96
- </UITabPanel>
97
- </UITabs>
98
- </div>
99
- </template>
@@ -1,302 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref, computed, onMounted, watch } from "vue"
3
-
4
- const props = defineProps({
5
- modelValue: { type: String, default: "" },
6
- defaultValue: { type: String, default: "#ff0000" },
7
- showOpacity: { type: Boolean, default: false },
8
- disabled: { type: Boolean, default: false },
9
- readonly: { type: Boolean, default: false },
10
- flat: { type: Boolean, default: false },
11
- square: { type: Boolean, default: false },
12
- bordered: { type: Boolean, default: false },
13
- borderRadius: { type: [String, Number], default: "12px" }
14
- })
15
-
16
- const emit = defineEmits(["update:modelValue"])
17
-
18
- const hue = ref(0)
19
- const opacity = ref(1)
20
- const pickedColor = ref({ r: 255, g: 0, b: 0 })
21
-
22
- const saturation = ref(100)
23
- const lightness = ref(50)
24
-
25
- const canvasRef = ref<HTMLCanvasElement | null>(null)
26
-
27
- const clamp = (v: number, min: number, max: number) =>
28
- Math.min(max, Math.max(min, v))
29
-
30
- function rgbToHsl(r: number, g: number, b: number) {
31
- r /= 255
32
- g /= 255
33
- b /= 255
34
-
35
- const max = Math.max(r, g, b)
36
- const min = Math.min(r, g, b)
37
- let h = 0, s = 0
38
- const l = (max + min) / 2
39
-
40
- if (max !== min) {
41
- const d = max - min
42
- s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
43
-
44
- switch (max) {
45
- case r: h = (g - b) / d + (g < b ? 6 : 0); break
46
- case g: h = (b - r) / d + 2; break
47
- case b: h = (r - g) / d + 4; break
48
- }
49
-
50
- h /= 6
51
- }
52
-
53
- return {
54
- h: Math.round(h * 360),
55
- s: Math.round(s * 100),
56
- l: Math.round(l * 100)
57
- }
58
- }
59
-
60
- function applyColor(color: string) {
61
- if (color.startsWith("#")) {
62
- const hex = color.replace("#", "")
63
- const r = parseInt(hex.substring(0, 2), 16)
64
- const g = parseInt(hex.substring(2, 4), 16)
65
- const b = parseInt(hex.substring(4, 6), 16)
66
-
67
- pickedColor.value = { r, g, b }
68
-
69
- const hsl = rgbToHsl(r, g, b)
70
- hue.value = hsl.h
71
- saturation.value = hsl.s
72
- lightness.value = hsl.l
73
- }
74
- }
75
-
76
- function drawCanvas() {
77
- const canvas = canvasRef.value
78
- if (!canvas) return
79
-
80
- const ctx = canvas.getContext("2d")!
81
- const w = canvas.width
82
- const h = canvas.height
83
-
84
- ctx.clearRect(0, 0, w, h)
85
-
86
- ctx.fillStyle = `hsl(${hue.value}, 100%, 50%)`
87
- ctx.fillRect(0, 0, w, h)
88
-
89
- const whiteGrad = ctx.createLinearGradient(0, 0, w, 0)
90
- whiteGrad.addColorStop(0, "#fff")
91
- whiteGrad.addColorStop(1, "transparent")
92
- ctx.fillStyle = whiteGrad
93
- ctx.fillRect(0, 0, w, h)
94
-
95
- const blackGrad = ctx.createLinearGradient(0, 0, 0, h)
96
- blackGrad.addColorStop(0, "transparent")
97
- blackGrad.addColorStop(1, "#000")
98
- ctx.fillStyle = blackGrad
99
- ctx.fillRect(0, 0, w, h)
100
- }
101
-
102
- function sampleCanvasAt(sat: number, light: number) {
103
- const canvas = canvasRef.value
104
- if (!canvas) return
105
-
106
- const ctx = canvas.getContext("2d")
107
- if (!ctx) return
108
-
109
- const w = canvas.width
110
- const h = canvas.height
111
-
112
- const x = clamp(Math.floor((sat / 100) * w), 0, w - 1)
113
- const y = clamp(Math.floor(((100 - light) / 100) * h), 0, h - 1)
114
-
115
- const pixel = ctx.getImageData(x, y, 1, 1).data
116
-
117
- pickedColor.value = {
118
- r: pixel[0]!,
119
- g: pixel[1]!,
120
- b: pixel[2]!
121
- }
122
- }
123
-
124
- function emitColor() {
125
- const { r, g, b } = pickedColor.value
126
-
127
- if (props.showOpacity) {
128
- emit("update:modelValue", `rgba(${r}, ${g}, ${b}, ${opacity.value})`)
129
- } else {
130
- emit("update:modelValue", `rgb(${r}, ${g}, ${b})`)
131
- }
132
- }
133
-
134
- function pickSquare(e: MouseEvent) {
135
- if (props.disabled || props.readonly) return
136
-
137
- const canvas = canvasRef.value
138
- if (!canvas) return
139
-
140
- const rect = canvas.getBoundingClientRect()
141
- const x = clamp(e.clientX - rect.left, 0, rect.width)
142
- const y = clamp(e.clientY - rect.top, 0, rect.height)
143
-
144
- saturation.value = Math.round((x / rect.width) * 100)
145
- lightness.value = Math.round(100 - (y / rect.height) * 100)
146
-
147
- sampleCanvasAt(saturation.value, lightness.value)
148
- emitColor()
149
- }
150
-
151
- function pickHue(e: MouseEvent) {
152
- if (props.disabled || props.readonly) return
153
-
154
- const rect = (e.target as HTMLElement).getBoundingClientRect()
155
- const y = clamp(e.clientY - rect.top, 0, rect.height)
156
-
157
- hue.value = clamp(Math.round((y / rect.height) * 360), 0, 359)
158
-
159
- drawCanvas()
160
- sampleCanvasAt(saturation.value, lightness.value)
161
- emitColor()
162
- }
163
-
164
- const squareCursor = computed(() => ({
165
- left: `${saturation.value}%`,
166
- top: `${100 - lightness.value}%`
167
- }))
168
-
169
- const hueCursor = computed(() => ({
170
- top: `${(hue.value / 360) * 100}%`
171
- }))
172
-
173
- function toHex(v: number) {
174
- return v.toString(16).padStart(2, "0")
175
- }
176
-
177
- const currentHex = computed(() => {
178
- const { r, g, b } = pickedColor.value
179
- return `#${toHex(r)}${toHex(g)}${toHex(b)}`
180
- })
181
-
182
- const displayHex = computed(() => {
183
- if (!props.showOpacity) return currentHex.value
184
-
185
- const alpha = Math.round(opacity.value * 255)
186
- .toString(16)
187
- .padStart(2, "0")
188
-
189
- return currentHex.value + alpha
190
- })
191
-
192
- const displayColor = computed(() => {
193
- const { r, g, b } = pickedColor.value
194
-
195
- return props.showOpacity
196
- ? `rgba(${r}, ${g}, ${b}, ${opacity.value})`
197
- : `rgb(${r}, ${g}, ${b})`
198
- })
199
-
200
- function copy(text: string) {
201
- navigator.clipboard.writeText(text).catch(() => {})
202
- }
203
-
204
- const containerClasses = computed(() => [
205
- "p-4 w-fit space-y-4",
206
- props.flat ? "" : "shadow-md bg-white",
207
- props.bordered ? "border border-gray-300" : ""
208
- ])
209
-
210
- const containerStyle = computed(() => ({
211
- borderRadius: props.square ? "0px" :
212
- typeof props.borderRadius === "number"
213
- ? `${props.borderRadius}px`
214
- : props.borderRadius
215
- }))
216
-
217
- watch(
218
- () => props.modelValue,
219
- (val) => {
220
- if (val) applyColor(val)
221
- },
222
- { immediate: true }
223
- )
224
-
225
- onMounted(() => {
226
- const initial = props.modelValue || props.defaultValue
227
- if (initial) applyColor(initial)
228
- drawCanvas()
229
- sampleCanvasAt(saturation.value, lightness.value)
230
- })
231
- </script>
232
-
233
- <template>
234
- <div :class="containerClasses" :style="containerStyle" class="p-4 w-fit bg-white rounded-lg space-y-4">
235
- <div class="flex gap-3">
236
- <div class="relative">
237
- <canvas
238
- ref="canvasRef"
239
- width="192"
240
- height="192"
241
- class="w-48 h-48 rounded cursor-crosshair border"
242
- @click="pickSquare"
243
- />
244
- <div
245
- class="absolute w-3 h-3 border-2 border-white rounded-full shadow -translate-x-1/2 -translate-y-1/2 pointer-events-none"
246
- :style="squareCursor"
247
- />
248
- </div>
249
-
250
- <div
251
- class="relative w-6 h-48 rounded cursor-pointer"
252
- style="background: linear-gradient(to bottom, red, yellow, lime, cyan, blue, magenta, red)"
253
- @click="pickHue"
254
- >
255
- <div
256
- class="absolute left-0 w-full h-1 bg-white shadow -translate-y-1/2 pointer-events-none"
257
- :style="hueCursor"
258
- />
259
- </div>
260
- </div>
261
-
262
- <input
263
- v-if="showOpacity"
264
- type="range"
265
- min="0"
266
- max="1"
267
- step="0.01"
268
- v-model="opacity"
269
- @input="emitColor"
270
- class="w-full"
271
- />
272
-
273
- <div
274
- class="h-12 rounded border"
275
- :style="{ background: displayColor }"
276
- />
277
-
278
- <div class="text-sm space-y-2">
279
-
280
- <div class="flex justify-between items-center">
281
- <span>{{ displayHex }}</span>
282
- <button @click="copy(displayHex)">Copy</button>
283
- </div>
284
-
285
- <div class="flex justify-between items-center">
286
- <span>{{ displayColor }}</span>
287
- <button @click="copy(displayColor)">Copy</button>
288
- </div>
289
-
290
- </div>
291
-
292
- </div>
293
- </template>
294
-
295
- <style scoped>
296
- button {
297
- font-size: 12px;
298
- padding: 2px 6px;
299
- border: 1px solid #ccc;
300
- border-radius: 4px;
301
- }
302
- </style>
@@ -1,240 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref, computed, watch } from "vue"
3
- import { useUiClasses } from './useUiClasses'
4
- import { uiProps } from "./uiProps";
5
-
6
- const props = defineProps({
7
- modelValue: { type: [String, Array], default: "" },
8
- multiDate: Boolean,
9
- rangeDate: Boolean,
10
- disabledDates: { type: Array as () => string[], default: () => [] },
11
- format: { type: String, default: "DD-MM-YYYY" },
12
- showHeader: { type: Boolean, default: true },
13
- backgroundColor: { type: String, default: "white" },
14
- bordered: { type: Boolean, default: false},
15
- borderRadius: { type: String, default: "rounded-md", acceptedValues: ["none", "sm", "md", "lg", "xl"] },
16
- flat: { type: Boolean, default: false },
17
- color: { type: String, default: "indigo" },
18
- textColor: uiProps.textColor,
19
- contentClass: uiProps.contentClass,
20
- contentStyle: uiProps.contentStyle,
21
- disable: uiProps.disable,
22
- })
23
-
24
- const {
25
- isHex,
26
- } = useUiClasses(props)
27
-
28
- const emit = defineEmits(["update:modelValue", "change"])
29
-
30
- const today = new Date()
31
-
32
- const currentMonth = ref(today.getMonth())
33
- const currentYear = ref(today.getFullYear())
34
-
35
- const selectedDates = ref<string[]>([])
36
- const rangeStart = ref<number | null>(null)
37
- const rangeEnd = ref<number | null>(null)
38
-
39
- const headerLabel = computed(() => {
40
- const first = selectedDates.value[0]
41
- if (!first) return "No date selected"
42
-
43
- const date = new Date(first)
44
-
45
- const dayName = date.toLocaleDateString("default", {
46
- weekday: "long"
47
- })
48
-
49
- return dayName
50
- })
51
-
52
- const daysInMonth = computed(() => {
53
- return new Date(currentYear.value, currentMonth.value + 1, 0).getDate()
54
- })
55
-
56
- const blankDays = computed(() => {
57
- return new Date(currentYear.value, currentMonth.value, 1).getDay()
58
- })
59
-
60
- const days = computed(() =>
61
- Array.from({ length: daysInMonth.value }, (_, i) => i + 1)
62
- )
63
-
64
- function formatDate(day: number): string {
65
- const d = new Date(currentYear.value, currentMonth.value, day)
66
- const year = d.getFullYear()
67
- const month = String(d.getMonth() + 1).padStart(2, '0')
68
- const date = String(d.getDate()).padStart(2, '0')
69
- return `${year}-${month}-${date}`
70
- }
71
-
72
- function formatDateString(date: Date, format: string): string {
73
- const map = {
74
- YYYY: String(date.getFullYear()),
75
- MM: String(date.getMonth() + 1).padStart(2, '0'),
76
- DD: String(date.getDate()).padStart(2, '0'),
77
- HH: String(date.getHours()).padStart(2, '0'),
78
- mm: String(date.getMinutes()).padStart(2, '0'),
79
- ss: String(date.getSeconds()).padStart(2, '0'),
80
- }
81
-
82
- return format.replace(/YYYY|MM|DD|HH|mm|ss/g, (match) => {
83
- return map[match as keyof typeof map]
84
- })
85
- }
86
-
87
- function isDisabled(day: number) {
88
- return props.disabledDates.includes(formatDate(day))
89
- }
90
-
91
- function isSelected(day: number) {
92
- return selectedDates.value.includes(formatDate(day))
93
- }
94
-
95
- function isToday(day: number) {
96
- const d = new Date()
97
- return (
98
- day === d.getDate() &&
99
- currentMonth.value === d.getMonth() &&
100
- currentYear.value === d.getFullYear()
101
- )
102
- }
103
-
104
- function isInRange(day: number) {
105
- if (rangeStart.value === null || rangeEnd.value === null) return false
106
- const start = Math.min(rangeStart.value, rangeEnd.value)
107
- const end = Math.max(rangeStart.value, rangeEnd.value)
108
- return day >= start && day <= end
109
- }
110
-
111
- function dayClasses(day: number) {
112
- if (isDisabled(day)) return "text-gray-300 cursor-not-allowed"
113
-
114
- if (isSelected(day))
115
- return `bg-${props.color}-600 text-white font-medium`
116
-
117
- if (props.rangeDate && isInRange(day))
118
- return `bg-${props.color}-100 text-${props.color}-700`
119
-
120
- if (isToday(day))
121
- return `border border-${props.color}-500`
122
-
123
- return "hover:bg-gray-100"
124
- }
125
-
126
- function selectDate(day: number) {
127
- if (isDisabled(day)) return
128
-
129
- const value = formatDate(day)
130
-
131
- if (props.multiDate) {
132
- if (selectedDates.value.includes(value)) {
133
- selectedDates.value = selectedDates.value.filter(d => d !== value)
134
- } else {
135
- selectedDates.value.push(value)
136
- }
137
- } else if (props.rangeDate) {
138
- if (rangeStart.value === null) {
139
- rangeStart.value = day
140
- rangeEnd.value = null
141
- } else {
142
- rangeEnd.value = day
143
- selectedDates.value = buildRange()
144
- }
145
- } else {
146
- selectedDates.value = [value]
147
- }
148
-
149
- emit("update:modelValue", selectedDates.value)
150
- emit("change", selectedDates.value)
151
- }
152
-
153
- function buildRange() {
154
- if (rangeStart.value === null || rangeEnd.value === null) return []
155
-
156
- const start = Math.min(rangeStart.value, rangeEnd.value)
157
- const end = Math.max(rangeStart.value, rangeEnd.value)
158
-
159
- const range: string[] = []
160
- for (let i = start; i <= end; i++) {
161
- range.push(formatDate(i))
162
- }
163
- return range
164
- }
165
-
166
- function nextMonth() {
167
- if (currentMonth.value === 11) {
168
- currentMonth.value = 0
169
- currentYear.value++
170
- } else currentMonth.value++
171
- }
172
-
173
- function prevMonth() {
174
- if (currentMonth.value === 0) {
175
- currentMonth.value = 11
176
- currentYear.value--
177
- } else currentMonth.value--
178
- }
179
-
180
- watch(
181
- () => props.modelValue,
182
- (val) => {
183
- if (!val) return
184
- if (Array.isArray(val)) {
185
- selectedDates.value = val as string[]
186
- } else {
187
- selectedDates.value = [val]
188
- }
189
- },
190
- { immediate: true }
191
- )
192
- </script>
193
-
194
- <template>
195
- <div class="w-80 min-w-80" :class="[
196
- borderRadius ? `rounded-${borderRadius}` : '',
197
- bordered ? 'border border-gray-200' : '',
198
- flat ? '' : 'shadow-md',
199
- textColor && !isHex(textColor) ? `text-${textColor}` : 'text-gray-800',
200
- backgroundColor && !isHex(backgroundColor) ? `bg-${backgroundColor}` : ''
201
- ]" :style="{
202
- backgroundColor: isHex(backgroundColor) ? backgroundColor : '',
203
- color: isHex(textColor) ? textColor : ''
204
- }">
205
- <div v-if="showHeader" class="px-4 py-3 flex justify-between items-center"
206
- :class="bordered ? 'border-b border-gray-200' : ''">
207
- <div class="text-sm font-semibold text-gray-700">
208
- <div v-if="multiDate">{{ selectedDates.length }} selected</div>
209
- <div v-else>
210
- <div class="text-lg" :class="`text-${color}-600`">{{ headerLabel }}</div>
211
- <div class="text-gray-700">
212
- {{ selectedDates[0] ? formatDateString(new Date(selectedDates[0]), format) : '' }}
213
- </div>
214
- </div>
215
- </div>
216
- </div>
217
- <div class="flex items-center justify-between px-4 py-2">
218
- <button @click="prevMonth" class="p-1 rounded hover:bg-gray-100">‹</button>
219
- <div class="text-sm font-medium text-gray-800">
220
- {{ new Date(currentYear, currentMonth).toLocaleString('default', { month: 'long' }) }}
221
- {{ currentYear }}
222
- </div>
223
- <button @click="nextMonth" class="p-1 rounded hover:bg-gray-100">›</button>
224
- </div>
225
- <div class="grid grid-cols-7 text-xs font-medium text-center text-gray-500 px-3 mb-1">
226
- <div v-for="d in ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']" :key="d">
227
- {{ d }}
228
- </div>
229
- </div>
230
- <div class="grid grid-cols-7 gap-1 px-3 pb-3">
231
- <div v-for="n in blankDays" :key="'b' + n"></div>
232
- <div v-for="day in days" :key="day" @click="selectDate(day)"
233
- class="h-10 flex items-center justify-center text-sm rounded-lg cursor-pointer transition-all duration-150"
234
- :class="dayClasses(day)">
235
- {{ day }}
236
- </div>
237
- </div>
238
-
239
- </div>
240
- </template>
@@ -1,78 +0,0 @@
1
- <script setup lang="ts">
2
- defineOptions({
3
- inheritAttrs: false,
4
- })
5
-
6
- import { computed } from 'vue'
7
-
8
- const props = defineProps({
9
- color: {
10
- type: String,
11
- default: 'gray-300',
12
- },
13
-
14
- type: {
15
- type: String,
16
- default: 'solid',
17
- },
18
-
19
- vertical: {
20
- type: Boolean,
21
- default: false,
22
- },
23
-
24
- inset: {
25
- type: Boolean,
26
- default: false,
27
- },
28
-
29
- thickness: {
30
- type: String,
31
- default: '1px',
32
- },
33
- })
34
-
35
- const isHex = (val: string) => val.startsWith('#')
36
-
37
- const dividerStyle = computed(() => {
38
- const style: Record<string, string> = {}
39
-
40
- if (isHex(props.color)) {
41
- style.borderColor = props.color
42
- }
43
-
44
- style.borderWidth = props.thickness
45
-
46
- return style
47
- })
48
-
49
- const dividerClass = computed(() => [
50
- props.vertical
51
- ? 'h-full border-l'
52
- : 'w-full border-t',
53
-
54
- props.type === 'dashed'
55
- ? 'border-dashed'
56
- : '',
57
-
58
- props.type === 'dotted'
59
- ? 'border-dotted'
60
- : '',
61
-
62
- props.inset
63
- ? 'mx-4'
64
- : '',
65
-
66
- !isHex(props.color)
67
- ? `border-${props.color}`
68
- : '',
69
- ])
70
- </script>
71
-
72
- <template>
73
- <div
74
- :class="dividerClass"
75
- :style="dividerStyle"
76
- v-bind="$attrs"
77
- />
78
- </template>