windly-ui 1.0.1 → 1.0.2

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 (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -33
  3. package/package.json +47 -15
  4. package/ui-library/dist/module.d.mts +3 -0
  5. package/ui-library/dist/module.json +8 -0
  6. package/ui-library/dist/module.mjs +22 -0
  7. package/ui-library/dist/types.d.mts +7 -0
  8. package/ui-library/components/Accordion.vue +0 -118
  9. package/ui-library/components/Avatar.vue +0 -121
  10. package/ui-library/components/Badge.vue +0 -116
  11. package/ui-library/components/Breadcrumbs.vue +0 -138
  12. package/ui-library/components/Button.vue +0 -154
  13. package/ui-library/components/Card.vue +0 -84
  14. package/ui-library/components/Checkbox.vue +0 -148
  15. package/ui-library/components/CodeBlock.vue +0 -99
  16. package/ui-library/components/ColorPicker.vue +0 -302
  17. package/ui-library/components/Date.vue +0 -240
  18. package/ui-library/components/Dialog.vue +0 -187
  19. package/ui-library/components/Divider.vue +0 -78
  20. package/ui-library/components/Drawer.vue +0 -67
  21. package/ui-library/components/Dropdown.vue +0 -248
  22. package/ui-library/components/FileUploader.vue +0 -330
  23. package/ui-library/components/Icon.vue +0 -82
  24. package/ui-library/components/Image.vue +0 -78
  25. package/ui-library/components/Input.vue +0 -531
  26. package/ui-library/components/Radio.vue +0 -356
  27. package/ui-library/components/ScrollArea.vue +0 -43
  28. package/ui-library/components/Select.vue +0 -309
  29. package/ui-library/components/Stepper.vue +0 -361
  30. package/ui-library/components/TabPanel.vue +0 -25
  31. package/ui-library/components/Table.vue +0 -152
  32. package/ui-library/components/Tabs.vue +0 -71
  33. package/ui-library/components/Textarea.vue +0 -233
  34. package/ui-library/components/Toggle.vue +0 -319
  35. package/ui-library/components/Tooltip.vue +0 -200
  36. package/ui-library/components/plugin.ts +0 -8
  37. package/ui-library/components/uiProps.ts +0 -11
  38. package/ui-library/components/useUiClasses.ts +0 -159
  39. package/ui-library/module.ts +0 -20
@@ -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>