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.
- package/dist/module.d.mts +3 -0
- package/dist/module.json +8 -0
- package/dist/module.mjs +26 -0
- package/dist/runtime/components/Accordion.d.vue.ts +39 -0
- package/{ui-library → dist/runtime}/components/Accordion.vue +44 -70
- package/dist/runtime/components/Accordion.vue.d.ts +39 -0
- package/dist/runtime/components/Avatar.d.vue.ts +141 -0
- package/dist/runtime/components/Avatar.vue +72 -0
- package/dist/runtime/components/Avatar.vue.d.ts +141 -0
- package/dist/runtime/components/Badge.d.vue.ts +133 -0
- package/dist/runtime/components/Badge.vue +99 -0
- package/dist/runtime/components/Badge.vue.d.ts +133 -0
- package/dist/runtime/components/Breadcrumbs.d.vue.ts +83 -0
- package/{ui-library → dist/runtime}/components/Breadcrumbs.vue +37 -59
- package/dist/runtime/components/Breadcrumbs.vue.d.ts +83 -0
- package/dist/runtime/components/Button.d.vue.ts +199 -0
- package/dist/runtime/components/Button.vue +125 -0
- package/dist/runtime/components/Button.vue.d.ts +199 -0
- package/dist/runtime/components/Card.d.vue.ts +83 -0
- package/{ui-library → dist/runtime}/components/Card.vue +26 -39
- package/dist/runtime/components/Card.vue.d.ts +83 -0
- package/dist/runtime/components/Checkbox.d.vue.ts +190 -0
- package/{ui-library → dist/runtime}/components/Checkbox.vue +45 -68
- package/dist/runtime/components/Checkbox.vue.d.ts +190 -0
- package/dist/runtime/components/CodeBlock.d.vue.ts +16 -0
- package/dist/runtime/components/CodeBlock.vue +75 -0
- package/dist/runtime/components/CodeBlock.vue.d.ts +16 -0
- package/dist/runtime/components/ColorPicker.d.vue.ts +91 -0
- package/dist/runtime/components/ColorPicker.vue +244 -0
- package/dist/runtime/components/ColorPicker.vue.d.ts +91 -0
- package/dist/runtime/components/Date.d.vue.ts +137 -0
- package/dist/runtime/components/Date.vue +201 -0
- package/dist/runtime/components/Date.vue.d.ts +137 -0
- package/dist/runtime/components/Dialog.d.vue.ts +103 -0
- package/{ui-library → dist/runtime}/components/Dialog.vue +30 -65
- package/dist/runtime/components/Dialog.vue.d.ts +103 -0
- package/dist/runtime/components/Divider.d.vue.ts +51 -0
- package/dist/runtime/components/Divider.vue +52 -0
- package/dist/runtime/components/Divider.vue.d.ts +51 -0
- package/dist/runtime/components/Drawer.d.vue.ts +30 -0
- package/dist/runtime/components/Drawer.vue +43 -0
- package/dist/runtime/components/Drawer.vue.d.ts +30 -0
- package/dist/runtime/components/Dropdown.d.vue.ts +34 -0
- package/dist/runtime/components/Dropdown.vue +127 -0
- package/dist/runtime/components/Dropdown.vue.d.ts +34 -0
- package/dist/runtime/components/FileUploader.d.vue.ts +58 -0
- package/dist/runtime/components/FileUploader.vue +202 -0
- package/dist/runtime/components/FileUploader.vue.d.ts +58 -0
- package/dist/runtime/components/Icon.d.vue.ts +41 -0
- package/dist/runtime/components/Icon.vue +66 -0
- package/dist/runtime/components/Icon.vue.d.ts +41 -0
- package/dist/runtime/components/Image.d.vue.ts +77 -0
- package/dist/runtime/components/Image.vue +61 -0
- package/dist/runtime/components/Image.vue.d.ts +77 -0
- package/dist/runtime/components/Input.d.vue.ts +66 -0
- package/dist/runtime/components/Input.vue +233 -0
- package/dist/runtime/components/Input.vue.d.ts +66 -0
- package/dist/runtime/components/Radio.d.vue.ts +41 -0
- package/dist/runtime/components/Radio.vue +222 -0
- package/dist/runtime/components/Radio.vue.d.ts +41 -0
- package/dist/runtime/components/ScrollArea.d.vue.ts +79 -0
- package/dist/runtime/components/ScrollArea.vue +36 -0
- package/dist/runtime/components/ScrollArea.vue.d.ts +79 -0
- package/dist/runtime/components/Select.d.vue.ts +43 -0
- package/dist/runtime/components/Select.vue +200 -0
- package/dist/runtime/components/Select.vue.d.ts +43 -0
- package/dist/runtime/components/Stepper.d.vue.ts +46 -0
- package/dist/runtime/components/Stepper.vue +218 -0
- package/dist/runtime/components/Stepper.vue.d.ts +46 -0
- package/dist/runtime/components/TabPanel.d.vue.ts +17 -0
- package/dist/runtime/components/TabPanel.vue +21 -0
- package/dist/runtime/components/TabPanel.vue.d.ts +17 -0
- package/dist/runtime/components/Table.d.vue.ts +40 -0
- package/dist/runtime/components/Table.vue +92 -0
- package/dist/runtime/components/Table.vue.d.ts +40 -0
- package/dist/runtime/components/Tabs.d.vue.ts +20 -0
- package/dist/runtime/components/Tabs.vue +62 -0
- package/dist/runtime/components/Tabs.vue.d.ts +20 -0
- package/dist/runtime/components/Textarea.d.vue.ts +208 -0
- package/dist/runtime/components/Textarea.vue +185 -0
- package/dist/runtime/components/Textarea.vue.d.ts +208 -0
- package/dist/runtime/components/Toggle.d.vue.ts +36 -0
- package/dist/runtime/components/Toggle.vue +173 -0
- package/dist/runtime/components/Toggle.vue.d.ts +36 -0
- package/dist/runtime/components/Tooltip.d.vue.ts +63 -0
- package/dist/runtime/components/Tooltip.vue +144 -0
- package/dist/runtime/components/Tooltip.vue.d.ts +63 -0
- package/dist/runtime/components/uiProps.d.ts +38 -0
- package/{ui-library/components/uiProps.ts → dist/runtime/components/uiProps.js} +2 -2
- package/dist/runtime/components/useUiClasses.d.ts +43 -0
- package/{ui-library/components/useUiClasses.ts → dist/runtime/components/useUiClasses.js} +25 -60
- package/dist/runtime/composables/plugin.d.ts +2 -0
- package/dist/runtime/composables/plugin.js +3 -0
- package/dist/runtime/tailwind-color.d.ts +2 -0
- package/dist/runtime/tailwind-color.js +5859 -0
- package/dist/types.d.mts +7 -0
- package/package.json +14 -16
- package/README.md +0 -208
- package/ui-library/components/Avatar.vue +0 -121
- package/ui-library/components/Badge.vue +0 -116
- package/ui-library/components/Button.vue +0 -154
- package/ui-library/components/CodeBlock.vue +0 -99
- package/ui-library/components/ColorPicker.vue +0 -302
- package/ui-library/components/Date.vue +0 -240
- package/ui-library/components/Divider.vue +0 -78
- package/ui-library/components/Drawer.vue +0 -67
- package/ui-library/components/Dropdown.vue +0 -248
- package/ui-library/components/FileUploader.vue +0 -330
- package/ui-library/components/Icon.vue +0 -82
- package/ui-library/components/Image.vue +0 -78
- package/ui-library/components/Input.vue +0 -531
- package/ui-library/components/Radio.vue +0 -356
- package/ui-library/components/ScrollArea.vue +0 -43
- package/ui-library/components/Select.vue +0 -309
- package/ui-library/components/Stepper.vue +0 -361
- package/ui-library/components/TabPanel.vue +0 -25
- package/ui-library/components/Table.vue +0 -152
- package/ui-library/components/Tabs.vue +0 -71
- package/ui-library/components/Textarea.vue +0 -233
- package/ui-library/components/Toggle.vue +0 -319
- package/ui-library/components/Tooltip.vue +0 -200
- package/ui-library/components/plugin.ts +0 -8
- 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>
|