@personizely/ui 0.0.42

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 (221) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +1 -0
  3. package/dist/personizely-ui.css +6 -0
  4. package/dist/personizely-ui.js +26854 -0
  5. package/dist/personizely-ui.umd.cjs +111 -0
  6. package/package.json +117 -0
  7. package/src/assets/index.css +136 -0
  8. package/src/components/ui/accordion/Accordion.vue +19 -0
  9. package/src/components/ui/accordion/AccordionContent.vue +24 -0
  10. package/src/components/ui/accordion/AccordionItem.vue +37 -0
  11. package/src/components/ui/accordion/AccordionTrigger.vue +39 -0
  12. package/src/components/ui/accordion/index.ts +2 -0
  13. package/src/components/ui/alert/Alert.vue +68 -0
  14. package/src/components/ui/alert/index.ts +22 -0
  15. package/src/components/ui/alert-dialog/AlertDialog.vue +66 -0
  16. package/src/components/ui/alert-dialog/AlertDialogContent.vue +44 -0
  17. package/src/components/ui/alert-dialog/AlertDialogProvider.vue +27 -0
  18. package/src/components/ui/alert-dialog/AlertDialogTrigger.vue +11 -0
  19. package/src/components/ui/alert-dialog/index.ts +3 -0
  20. package/src/components/ui/alert-dialog/useAlertDialog.ts +19 -0
  21. package/src/components/ui/autocomplete/Autocomplete.vue +170 -0
  22. package/src/components/ui/autocomplete/AutocompleteContent.vue +27 -0
  23. package/src/components/ui/autocomplete/AutocompleteEmpty.vue +20 -0
  24. package/src/components/ui/autocomplete/AutocompleteGroup.vue +29 -0
  25. package/src/components/ui/autocomplete/AutocompleteItem.vue +26 -0
  26. package/src/components/ui/autocomplete/AutocompleteRoot.vue +31 -0
  27. package/src/components/ui/autocomplete/AutocompleteSeparator.vue +23 -0
  28. package/src/components/ui/autocomplete/index.ts +1 -0
  29. package/src/components/ui/avatar/Avatar.vue +31 -0
  30. package/src/components/ui/avatar/AvatarFallback.vue +11 -0
  31. package/src/components/ui/avatar/AvatarImage.vue +9 -0
  32. package/src/components/ui/avatar/index.ts +19 -0
  33. package/src/components/ui/badge/Badge.vue +16 -0
  34. package/src/components/ui/badge/index.ts +22 -0
  35. package/src/components/ui/button/Button.vue +123 -0
  36. package/src/components/ui/button/index.ts +78 -0
  37. package/src/components/ui/calendar/Calendar.vue +76 -0
  38. package/src/components/ui/calendar/CalendarCell.vue +24 -0
  39. package/src/components/ui/calendar/CalendarCellTrigger.vue +38 -0
  40. package/src/components/ui/calendar/CalendarGrid.vue +24 -0
  41. package/src/components/ui/calendar/CalendarGridBody.vue +11 -0
  42. package/src/components/ui/calendar/CalendarGridHead.vue +11 -0
  43. package/src/components/ui/calendar/CalendarGridRow.vue +21 -0
  44. package/src/components/ui/calendar/CalendarHeadCell.vue +21 -0
  45. package/src/components/ui/calendar/CalendarHeader.vue +21 -0
  46. package/src/components/ui/calendar/CalendarHeading.vue +27 -0
  47. package/src/components/ui/calendar/CalendarNextButton.vue +32 -0
  48. package/src/components/ui/calendar/CalendarPrevButton.vue +32 -0
  49. package/src/components/ui/calendar/index.ts +1 -0
  50. package/src/components/ui/card/Card.vue +57 -0
  51. package/src/components/ui/card/CardContent.vue +14 -0
  52. package/src/components/ui/card/CardDescription.vue +14 -0
  53. package/src/components/ui/card/CardFooter.vue +14 -0
  54. package/src/components/ui/card/CardHeader.vue +14 -0
  55. package/src/components/ui/card/CardTitle.vue +18 -0
  56. package/src/components/ui/card/CardTray.vue +14 -0
  57. package/src/components/ui/card/index.ts +1 -0
  58. package/src/components/ui/checkbox/Checkbox.vue +63 -0
  59. package/src/components/ui/checkbox/CheckboxBase.vue +39 -0
  60. package/src/components/ui/checkbox/index.ts +1 -0
  61. package/src/components/ui/checkbox-group/CheckboxGroup.vue +65 -0
  62. package/src/components/ui/checkbox-group/index.ts +15 -0
  63. package/src/components/ui/color-picker/Alpha.vue +63 -0
  64. package/src/components/ui/color-picker/Angle.vue +145 -0
  65. package/src/components/ui/color-picker/Checkboard.vue +43 -0
  66. package/src/components/ui/color-picker/Color.vue +255 -0
  67. package/src/components/ui/color-picker/ColorPicker.vue +25 -0
  68. package/src/components/ui/color-picker/Gradient.vue +172 -0
  69. package/src/components/ui/color-picker/Handle.vue +19 -0
  70. package/src/components/ui/color-picker/Hue.vue +80 -0
  71. package/src/components/ui/color-picker/LabelInput.vue +16 -0
  72. package/src/components/ui/color-picker/Rail.vue +100 -0
  73. package/src/components/ui/color-picker/Saturation.vue +142 -0
  74. package/src/components/ui/color-picker/index.ts +4 -0
  75. package/src/components/ui/combobox/Combobox.vue +202 -0
  76. package/src/components/ui/combobox/ComboboxContent.vue +27 -0
  77. package/src/components/ui/combobox/ComboboxEmpty.vue +20 -0
  78. package/src/components/ui/combobox/ComboboxGroup.vue +29 -0
  79. package/src/components/ui/combobox/ComboboxInput.vue +52 -0
  80. package/src/components/ui/combobox/ComboboxItem.vue +26 -0
  81. package/src/components/ui/combobox/ComboboxRoot.vue +31 -0
  82. package/src/components/ui/combobox/ComboboxSeparator.vue +23 -0
  83. package/src/components/ui/combobox/index.ts +1 -0
  84. package/src/components/ui/date-picker/DatePicker.vue +55 -0
  85. package/src/components/ui/date-picker/index.ts +1 -0
  86. package/src/components/ui/date-range-picker/DateRangePicker.vue +137 -0
  87. package/src/components/ui/date-range-picker/index.ts +1 -0
  88. package/src/components/ui/dialog/Dialog.vue +78 -0
  89. package/src/components/ui/dialog/DialogContent.vue +46 -0
  90. package/src/components/ui/dialog/DialogDescription.vue +24 -0
  91. package/src/components/ui/dialog/DialogFooter.vue +19 -0
  92. package/src/components/ui/dialog/DialogHeader.vue +16 -0
  93. package/src/components/ui/dialog/DialogTitle.vue +29 -0
  94. package/src/components/ui/dialog/DialogTrigger.vue +11 -0
  95. package/src/components/ui/dialog/index.ts +1 -0
  96. package/src/components/ui/drawer/Drawer.vue +88 -0
  97. package/src/components/ui/drawer/DrawerContent.vue +57 -0
  98. package/src/components/ui/drawer/DrawerDescription.vue +22 -0
  99. package/src/components/ui/drawer/DrawerFooter.vue +19 -0
  100. package/src/components/ui/drawer/DrawerHeader.vue +16 -0
  101. package/src/components/ui/drawer/DrawerTitle.vue +22 -0
  102. package/src/components/ui/drawer/DrawerTrigger.vue +11 -0
  103. package/src/components/ui/drawer/index.ts +21 -0
  104. package/src/components/ui/dropdown-menu/DropdownCheckboxGroupMenu.vue +87 -0
  105. package/src/components/ui/dropdown-menu/DropdownMenu.vue +72 -0
  106. package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +40 -0
  107. package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +37 -0
  108. package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +11 -0
  109. package/src/components/ui/dropdown-menu/DropdownMenuHelp.vue +14 -0
  110. package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +28 -0
  111. package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +24 -0
  112. package/src/components/ui/dropdown-menu/DropdownMenuPart.vue +64 -0
  113. package/src/components/ui/dropdown-menu/DropdownMenuPartItem.vue +76 -0
  114. package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +19 -0
  115. package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +41 -0
  116. package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +22 -0
  117. package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +14 -0
  118. package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
  119. package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +30 -0
  120. package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +33 -0
  121. package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +13 -0
  122. package/src/components/ui/dropdown-menu/DropdownRadioGroupMenu.vue +75 -0
  123. package/src/components/ui/dropdown-menu/index.ts +36 -0
  124. package/src/components/ui/file-upload-button/FileUploadButton.vue +55 -0
  125. package/src/components/ui/file-upload-button/index.ts +1 -0
  126. package/src/components/ui/form/Form.vue +13 -0
  127. package/src/components/ui/form/FormControl.vue +16 -0
  128. package/src/components/ui/form/FormDescription.vue +20 -0
  129. package/src/components/ui/form/FormField.vue +61 -0
  130. package/src/components/ui/form/FormLabel.vue +23 -0
  131. package/src/components/ui/form/FormMessage.vue +16 -0
  132. package/src/components/ui/form/index.ts +2 -0
  133. package/src/components/ui/form/useFormField.ts +30 -0
  134. package/src/components/ui/icon/Icon.vue +16 -0
  135. package/src/components/ui/icon/index.ts +1 -0
  136. package/src/components/ui/input/Input.vue +51 -0
  137. package/src/components/ui/input/InputBase.vue +18 -0
  138. package/src/components/ui/input/index.ts +1 -0
  139. package/src/components/ui/label/Label.vue +27 -0
  140. package/src/components/ui/label/index.ts +1 -0
  141. package/src/components/ui/pagination/Pagination.vue +50 -0
  142. package/src/components/ui/pagination/PaginationContent.vue +21 -0
  143. package/src/components/ui/pagination/PaginationEllipsis.vue +24 -0
  144. package/src/components/ui/pagination/PaginationFirst.vue +32 -0
  145. package/src/components/ui/pagination/PaginationItem.vue +32 -0
  146. package/src/components/ui/pagination/PaginationLast.vue +32 -0
  147. package/src/components/ui/pagination/PaginationNext.vue +32 -0
  148. package/src/components/ui/pagination/PaginationPrevious.vue +32 -0
  149. package/src/components/ui/pagination/index.ts +1 -0
  150. package/src/components/ui/popover/Popover.vue +57 -0
  151. package/src/components/ui/popover/PopoverTrigger.vue +15 -0
  152. package/src/components/ui/popover/index.ts +1 -0
  153. package/src/components/ui/progress/Progress.vue +35 -0
  154. package/src/components/ui/progress/ProgressIndicator.vue +19 -0
  155. package/src/components/ui/progress/index.ts +2 -0
  156. package/src/components/ui/progress-circular/ProgressCircular.vue +85 -0
  157. package/src/components/ui/progress-circular/index.ts +1 -0
  158. package/src/components/ui/radio-group/RadioGroup.vue +81 -0
  159. package/src/components/ui/radio-group/RadioGroupItem.vue +39 -0
  160. package/src/components/ui/radio-group/index.ts +15 -0
  161. package/src/components/ui/range-calendar/RangeCalendar.vue +71 -0
  162. package/src/components/ui/range-calendar/RangeCalendarCell.vue +28 -0
  163. package/src/components/ui/range-calendar/RangeCalendarCellTrigger.vue +40 -0
  164. package/src/components/ui/range-calendar/RangeCalendarGrid.vue +24 -0
  165. package/src/components/ui/range-calendar/RangeCalendarGridBody.vue +11 -0
  166. package/src/components/ui/range-calendar/RangeCalendarGridHead.vue +11 -0
  167. package/src/components/ui/range-calendar/RangeCalendarGridRow.vue +21 -0
  168. package/src/components/ui/range-calendar/RangeCalendarHeadCell.vue +21 -0
  169. package/src/components/ui/range-calendar/RangeCalendarHeader.vue +21 -0
  170. package/src/components/ui/range-calendar/RangeCalendarHeading.vue +27 -0
  171. package/src/components/ui/range-calendar/RangeCalendarNextButton.vue +32 -0
  172. package/src/components/ui/range-calendar/RangeCalendarPrevButton.vue +32 -0
  173. package/src/components/ui/range-calendar/index.ts +1 -0
  174. package/src/components/ui/select/Select.vue +120 -0
  175. package/src/components/ui/select/SelectContent.vue +45 -0
  176. package/src/components/ui/select/SelectGroup.vue +19 -0
  177. package/src/components/ui/select/SelectItem.vue +43 -0
  178. package/src/components/ui/select/SelectLabel.vue +13 -0
  179. package/src/components/ui/select/SelectSeparator.vue +17 -0
  180. package/src/components/ui/select/SelectTrigger.vue +31 -0
  181. package/src/components/ui/select/SelectValue.vue +11 -0
  182. package/src/components/ui/select/index.ts +1 -0
  183. package/src/components/ui/slider/Slider.vue +100 -0
  184. package/src/components/ui/slider/index.ts +1 -0
  185. package/src/components/ui/switch/Switch.vue +40 -0
  186. package/src/components/ui/switch/SwitchBase.vue +36 -0
  187. package/src/components/ui/switch/index.ts +1 -0
  188. package/src/components/ui/tabs/Tabs.vue +63 -0
  189. package/src/components/ui/tabs/TabsContent.vue +26 -0
  190. package/src/components/ui/tabs/TabsTrigger.vue +27 -0
  191. package/src/components/ui/tabs/index.ts +28 -0
  192. package/src/components/ui/textarea/Textarea.vue +13 -0
  193. package/src/components/ui/textarea/index.ts +1 -0
  194. package/src/components/ui/toast/Toast.vue +28 -0
  195. package/src/components/ui/toast/ToastAction.vue +26 -0
  196. package/src/components/ui/toast/ToastClose.vue +29 -0
  197. package/src/components/ui/toast/ToastDescription.vue +19 -0
  198. package/src/components/ui/toast/ToastProvider.vue +11 -0
  199. package/src/components/ui/toast/ToastTitle.vue +19 -0
  200. package/src/components/ui/toast/ToastViewport.vue +17 -0
  201. package/src/components/ui/toast/Toaster.vue +30 -0
  202. package/src/components/ui/toast/index.ts +34 -0
  203. package/src/components/ui/toast/useToast.ts +163 -0
  204. package/src/components/ui/toggle/Toggle.vue +51 -0
  205. package/src/components/ui/toggle/index.ts +71 -0
  206. package/src/components/ui/toggle-group/ToggleGroup.vue +58 -0
  207. package/src/components/ui/toggle-group/ToggleGroupItem.vue +54 -0
  208. package/src/components/ui/toggle-group/index.ts +1 -0
  209. package/src/components/ui/tooltip/Tooltip.vue +42 -0
  210. package/src/components/ui/tooltip/TooltipProvider.vue +11 -0
  211. package/src/components/ui/tooltip/index.ts +2 -0
  212. package/src/composables/delegated-props.ts +6 -0
  213. package/src/composables/emits-as-props.ts +17 -0
  214. package/src/composables/forward-props-emits.ts +11 -0
  215. package/src/directives/autofocus.ts +7 -0
  216. package/src/index.ts +159 -0
  217. package/src/options-provider.ts +19 -0
  218. package/src/utils/gradient.ts +158 -0
  219. package/src/utils/options.ts +40 -0
  220. package/src/utils/tailwind.ts +14 -0
  221. package/web-types.json +10560 -0
@@ -0,0 +1,172 @@
1
+ <template>
2
+ <div class="flex flex-col gap-4">
3
+ <Rail
4
+ ref="rail"
5
+ :background="compile({ points, angle: 90, type: 'linear' })"
6
+ @drag="movePoint"
7
+ @drag-start="onDragStart"
8
+ >
9
+ <Handle
10
+ v-for="(point, index) in points"
11
+ :key="index"
12
+ :style="{ left: point.left + '%', backgroundColor: point.color }"
13
+ role="presentation"
14
+ class="h-full w-2 -translate-x-1/2"
15
+ :class="point === currentPoint && 'border-primary'"
16
+ @touchstart.passive="selectPoint(point)"
17
+ @mousedown="selectPoint(point)"
18
+ @keydown.enter.prevent="selectPoint(point)"
19
+ @keydown.space.prevent="selectPoint(point)"
20
+ @up="increase(point)"
21
+ @down="decrease(point)"
22
+ />
23
+ </Rail>
24
+ <Color v-model="currentPoint.color" />
25
+ <div class="flex gap-4 justify-between md:justify-normal">
26
+ <Select
27
+ v-model="type"
28
+ :options="typeOptions"
29
+ class="grow"
30
+ />
31
+ <div
32
+ v-if="type === 'linear'"
33
+ class="flex gap-4"
34
+ >
35
+ <Angle
36
+ v-model="angle"
37
+ />
38
+ <Input
39
+ v-model.number="angle"
40
+ :min="1"
41
+ :max="360"
42
+ class="w-14 [&>input]:text-center [&>input]:pe-4"
43
+ trailing
44
+ >
45
+ <template #trailing>
46
+ <span class="text-gray-500 dark:text-gray-400 text-xs">°</span>
47
+ </template>
48
+ </Input>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </template>
53
+
54
+ <script setup lang="ts">
55
+ import Rail from './Rail.vue'
56
+ import Color from './Color.vue'
57
+ import Handle from './Handle.vue'
58
+ import Angle from './Angle.vue'
59
+ import Input from '../input/Input.vue'
60
+ import Select from '../select/Select.vue'
61
+ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
62
+ import { parseGradient, compile } from '@/utils/gradient.js'
63
+ import type { Point } from '@/utils/gradient.js'
64
+
65
+ const emit = defineEmits(['update:modelValue'])
66
+ const props = withDefaults(defineProps<{
67
+ modelValue: string
68
+ min?: number
69
+ max?: number
70
+ }>(), {
71
+ min: 2,
72
+ max: 5
73
+ })
74
+
75
+ const typeOptions = [
76
+ { value: 'linear', label: 'Linear' },
77
+ { value: 'radial', label: 'Radial' }
78
+ ]
79
+
80
+ const initialGradient = parseGradient(props.modelValue)
81
+ const points = ref(initialGradient.points)
82
+ const type = ref(initialGradient.type)
83
+ const angle = ref(initialGradient.angle)
84
+ const shape = ref(initialGradient.shape)
85
+ const xAxis = ref(initialGradient.xAxis)
86
+ const yAxis = ref(initialGradient.yAxis)
87
+
88
+ const gradient = computed(() => {
89
+ return compile({
90
+ points: points.value,
91
+ angle: angle.value,
92
+ type: type.value
93
+ })
94
+ })
95
+
96
+ const currentPoint = ref(points.value[0])
97
+
98
+ watch(gradient, () => {
99
+ emit('update:modelValue', gradient.value)
100
+ })
101
+
102
+ watch(() => props.modelValue, (to) => {
103
+ if (to !== gradient.value) {
104
+ const newGradient = parseGradient(props.modelValue)
105
+ points.value = newGradient.points
106
+ type.value = newGradient.type
107
+ angle.value = newGradient.angle
108
+ shape.value = newGradient.shape
109
+ xAxis.value = newGradient.xAxis
110
+ yAxis.value = newGradient.yAxis
111
+ }
112
+ })
113
+
114
+ const findClosestPoint = (left: number) => {
115
+ const sortedPoints = [...points.value].sort((a, b) => Math.abs(left - a.left) - Math.abs(left - b.left))
116
+ return sortedPoints[0]
117
+ }
118
+
119
+ const addPoint = (left: number) => {
120
+ const point = {
121
+ color: findClosestPoint(left).color,
122
+ left
123
+ }
124
+ points.value.push(point)
125
+ points.value.sort((a, b) => a.left - b.left)
126
+ currentPoint.value = point
127
+ }
128
+
129
+ const removePoint = () => {
130
+ const idx = points.value.indexOf(currentPoint.value)
131
+ points.value.splice(idx, 1)
132
+ currentPoint.value = findClosestPoint(currentPoint.value.left)
133
+ }
134
+
135
+ const selectPoint = (point: Point) => {
136
+ currentPoint.value = point
137
+ }
138
+
139
+ const movePoint = (left: number) => {
140
+ currentPoint.value.left = left
141
+ points.value.sort((a, b) => a.left - b.left)
142
+ }
143
+
144
+ const increase = (point: Point) => {
145
+ const value = point.left + 10
146
+ point.left = value <= 100 ? value : 100
147
+ }
148
+
149
+ const decrease = (point: Point) => {
150
+ const value = point.left - 10
151
+ point.left = value >= 0 ? value : 0
152
+ }
153
+
154
+ const onKeyDown = (e: KeyboardEvent) => {
155
+ if ((e.target as HTMLElement).tagName.toLowerCase() !== 'input' && ['delete', 'backspace'].includes(e.key.toLowerCase()) && points.value.length > props.min) {
156
+ removePoint()
157
+ }
158
+ }
159
+
160
+ const onDragStart = (left: number, isRail: boolean) => {
161
+ if (isRail) {
162
+ if (points.value.length < props.max) {
163
+ addPoint(left)
164
+ } else {
165
+ movePoint(left)
166
+ }
167
+ }
168
+ }
169
+
170
+ onMounted(() => document.addEventListener('keydown', onKeyDown))
171
+ onUnmounted(() => document.removeEventListener('keydown', onKeyDown))
172
+ </script>
@@ -0,0 +1,19 @@
1
+ <template>
2
+ <div
3
+ :class="cn('absolute rounded-md cursor-pointer select-none border-2 border-gray-300 shadow-xs scale-[1.2] ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', $attrs.class)"
4
+ role="presentation"
5
+ tabindex="0"
6
+ @keydown.up.prevent="$emit('up', $event)"
7
+ @keydown.down.prevent="$emit('down', $event)"
8
+ @keydown.right.prevent="$emit('up', $event)"
9
+ @keydown.left.prevent="$emit('down', $event)"
10
+ >
11
+ <slot />
12
+ </div>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import { cn } from '@/utils/tailwind'
17
+
18
+ defineEmits(['up', 'down'])
19
+ </script>
@@ -0,0 +1,80 @@
1
+ <template>
2
+ <Rail
3
+ :orientation="orientation"
4
+ :background="background"
5
+ @drag="onDrag"
6
+ @drag-start="onDragStart"
7
+ @drag-end="onDragEnd"
8
+ >
9
+ <Handle
10
+ :style="{ [props.orientation === 'horizontal' ? 'left' : 'top']: offset + '%' }"
11
+ :class="orientation === 'horizontal' ? 'h-full w-2 -translate-x-1/2' : 'w-full h-2 -translate-y-1/2'"
12
+ @up="increment"
13
+ @down="decrement"
14
+ />
15
+ </Rail>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ import { computed, ref, watch } from 'vue'
20
+ import Rail from './Rail.vue'
21
+ import Handle from './Handle.vue'
22
+
23
+ const calculateHandlePosition = (hue: number) => (100 * hue / 360)
24
+
25
+ const modelValue = defineModel<number>({ required: true })
26
+ const props = withDefaults(defineProps<{
27
+ orientation?: 'horizontal' | 'vertical'
28
+ }>(), {
29
+ orientation: 'horizontal'
30
+ })
31
+
32
+ const isDragging = ref(false)
33
+ const offset = ref(calculateHandlePosition(modelValue.value))
34
+
35
+ watch(modelValue, (to) => {
36
+ if (!isDragging.value) {
37
+ offset.value = calculateHandlePosition(to)
38
+ }
39
+ })
40
+
41
+ const background = computed(() => {
42
+ return props.orientation === 'horizontal'
43
+ ? 'linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%)'
44
+ : 'linear-gradient(to top, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%)'
45
+ })
46
+
47
+ const onDragStart = (value: number) => {
48
+ offset.value = value
49
+ update(value)
50
+ isDragging.value = true
51
+ }
52
+
53
+ const onDragEnd = () => {
54
+ isDragging.value = false
55
+ }
56
+
57
+ const onDrag = (value: number) => {
58
+ offset.value = value
59
+ update(value)
60
+ }
61
+
62
+ const update = (value: number) => {
63
+ const h = (360 * value / 100)
64
+ if (h !== modelValue.value) {
65
+ modelValue.value = Math.round(h)
66
+ }
67
+ }
68
+
69
+ const increment = () => {
70
+ let i = 10
71
+ const value = modelValue.value + i
72
+ modelValue.value = value <= 360 ? value : value - 360
73
+ }
74
+
75
+ const decrement = () => {
76
+ let i = 10
77
+ const value = modelValue.value - i
78
+ modelValue.value = value >= 0 ? value : 360 + value
79
+ }
80
+ </script>
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <div class="flex flex-col gap-1">
3
+ <slot v-bind="{ id }" />
4
+ <label :for="id" class="text-center text-xs">{{ label }}</label>
5
+ </div>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ import { useId } from 'reka-ui'
10
+
11
+ const props = defineProps<{
12
+ label: string
13
+ }>()
14
+
15
+ const id = useId(props.label.toLowerCase())
16
+ </script>
@@ -0,0 +1,100 @@
1
+ <template>
2
+ <div
3
+ class="relative rounded-md select-none"
4
+ :class="orientation === 'horizontal' ? 'h-8' : 'min-w-8'"
5
+ >
6
+ <div class="overflow-hidden">
7
+ <Checkboard
8
+ v-if="checkboard"
9
+ class="absolute inset-0 overflow-hidden rounded-md"
10
+ />
11
+ <div
12
+ class="absolute inset-0 rounded-md"
13
+ :style="{ background }"
14
+ />
15
+ </div>
16
+ <div
17
+ ref="container"
18
+ class="cursor-pointer relative flex items-center justify-center"
19
+ :class="orientation === 'horizontal' ? 'h-full w-full' : 'w-full h-full'"
20
+ role="slider"
21
+ @mousedown="handleMouseDown"
22
+ @touchmove.passive="handleMove"
23
+ @touchstart.passive="handleTouchStart"
24
+ @touchend="$emit('drag-end')"
25
+ >
26
+ <slot />
27
+ </div>
28
+ </div>
29
+ </template>
30
+
31
+ <script setup lang="ts">
32
+ import Checkboard from './Checkboard.vue'
33
+ import { onBeforeUnmount, ref } from 'vue'
34
+
35
+ const emit = defineEmits(['drag', 'drag-start', 'drag-end'])
36
+ const props = withDefaults(defineProps<{
37
+ checkboard?: boolean
38
+ orientation?: 'horizontal' | 'vertical'
39
+ background: string
40
+ }>(), {
41
+ checkboard: true,
42
+ orientation: 'horizontal'
43
+ })
44
+
45
+ const container = ref<HTMLElement>()
46
+
47
+ const handleMove = (e: MouseEvent | TouchEvent, skip: boolean = false) => {
48
+ if (!skip) {
49
+ e.preventDefault()
50
+ }
51
+
52
+ const offset = calculateOffset(e)
53
+
54
+ if (!skip) {
55
+ emit('drag', offset)
56
+ }
57
+ }
58
+
59
+ const handleTouchStart = (e: TouchEvent) => {
60
+ const offset = calculateOffset(e)
61
+ emit('drag-start', offset, e.target === e.currentTarget)
62
+ handleMove(e, true)
63
+ }
64
+
65
+ const handleMouseDown = (e: MouseEvent) => {
66
+ if (!container.value!.contains(e.target as HTMLElement)) return
67
+
68
+ handleMove(e, true)
69
+ const offset = calculateOffset(e)
70
+ emit('drag-start', offset, e.target === e.currentTarget)
71
+ window.addEventListener('mousemove', handleMove)
72
+ window.addEventListener('mouseup', handleMouseUp)
73
+ }
74
+
75
+ const calculateOffset = (e: TouchEvent | MouseEvent) => {
76
+ const offset = props.orientation === 'horizontal'
77
+ ? ('touches' in e ? e.touches[0].pageX : e.pageX) - window.scrollX
78
+ : ('touches' in e ? e.touches[0].pageY : e.pageY) - window.scrollY
79
+ const rect = container.value!.getBoundingClientRect()
80
+ const x = props.orientation === 'horizontal'
81
+ ? offset - rect.left
82
+ : offset - rect.top
83
+
84
+ return Math.min(100, Math.max(0, Math.round(x / (props.orientation === 'horizontal' ? rect.width : rect.height) * 100)))
85
+ }
86
+
87
+ const handleMouseUp = () => {
88
+ emit('drag-end')
89
+ unbindEventListeners()
90
+ }
91
+
92
+ const unbindEventListeners = () => {
93
+ window.removeEventListener('mousemove', handleMove)
94
+ window.removeEventListener('mouseup', handleMouseUp)
95
+ }
96
+
97
+ onBeforeUnmount(() => {
98
+ unbindEventListeners()
99
+ })
100
+ </script>
@@ -0,0 +1,142 @@
1
+ <template>
2
+ <div
3
+ ref="container"
4
+ class="relative cursor-pointer w-full aspect-[4/2.5] rounded-md select-none"
5
+ :style="{ background }"
6
+ @mousedown="handleMouseDown"
7
+ @touchmove.passive="handleMove"
8
+ @touchstart.passive="handleTouchStart"
9
+ @touchend="isDragging = false"
10
+ >
11
+ <div class="overflow-hidden">
12
+ <div class="absolute inset-0 w-full h-full rounded-md bg-linear-to-r from-white to-transparent" />
13
+ <div class="absolute inset-0 w-full h-full rounded-md bg-linear-to-t from-black to-transparent" />
14
+ </div>
15
+ <div
16
+ class="absolute cursor-pointer"
17
+ :style="{
18
+ left: left + '%',
19
+ top: top + '%'
20
+ }"
21
+ >
22
+ <div
23
+ class="w-3 h-3 border-2 border-white box-content shadow-md inset-shadow-xs inset-shadow-md rounded-full transform -translate-x-1/2 -translate-y-1/2 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
24
+ tabindex="0"
25
+ role="presentation"
26
+ @keydown.up.prevent="onUp"
27
+ @keydown.down.prevent="onDown"
28
+ @keydown.right="onRight"
29
+ @keydown.left="onLeft"
30
+ />
31
+ </div>
32
+ </div>
33
+ </template>
34
+
35
+ <script setup lang="ts">
36
+ import { computed, onBeforeUnmount, ref, watch } from 'vue'
37
+
38
+ const calculateHandleLeftPosition = (saturation: number) => saturation * 100
39
+ const calculateHandleTopPosition = (value: number) => -(value * 100) + 100
40
+
41
+ const emit = defineEmits(['update', 'update:saturation', 'update:value'])
42
+ const props = defineProps<{
43
+ saturation: number
44
+ value: number
45
+ hue: number
46
+ }>()
47
+
48
+ const container = ref<HTMLElement>()
49
+ const isDragging = ref(false)
50
+ const left = ref(calculateHandleLeftPosition(props.saturation))
51
+ const top = ref(calculateHandleTopPosition(props.value))
52
+
53
+ const background = computed(() => `hsl(${props.hue}, 100%, 50%)`)
54
+
55
+ watch(() => props.saturation, () => {
56
+ if (!isDragging.value) {
57
+ left.value = calculateHandleLeftPosition(props.saturation)
58
+ }
59
+ })
60
+ watch(() => props.value, () => {
61
+ if (!isDragging.value) {
62
+ top.value = calculateHandleTopPosition(props.value)
63
+ }
64
+ })
65
+
66
+ const clamp = (value: number, min: number, max: number) => {
67
+ return min < max
68
+ ? (value < min ? min : value > max ? max : value)
69
+ : (value < max ? max : value > min ? min : value)
70
+ }
71
+
72
+ const onUp = () => {
73
+ const value = props.value + 0.1
74
+ emit('update:value', value <= 1 ? value : 1)
75
+ }
76
+
77
+ const onDown = () => {
78
+ const value = props.value - 0.1
79
+ emit('update:value', value >= 0 ? value : 0)
80
+ }
81
+
82
+ const onLeft = () => {
83
+ const saturation = props.saturation - 0.1
84
+ emit('update:saturation', saturation >= 0 ? saturation : 0)
85
+ }
86
+
87
+ const onRight = () => {
88
+ const saturation = props.saturation + 0.1
89
+ emit('update:saturation', saturation <= 1 ? saturation : 1)
90
+ }
91
+
92
+ const handleMove = (e: MouseEvent | TouchEvent) => {
93
+ e.preventDefault()
94
+ const containerWidth = container.value!.clientWidth
95
+ const containerHeight = container.value!.clientHeight
96
+
97
+ const xOffset = container.value!.getBoundingClientRect().left + window.scrollX
98
+ const yOffset = container.value!.getBoundingClientRect().top + window.scrollY
99
+ const pageX = 'touches' in e ? e.touches[0].pageX : e.pageX
100
+ const pageY = 'touches' in e ? e.touches[0].pageY : e.pageY
101
+ const l = clamp(pageX - xOffset, 0, containerWidth) / containerWidth * 100
102
+ const t = clamp(pageY - yOffset, 0, containerHeight) / containerHeight * 100
103
+
104
+ left.value = l
105
+ top.value = t
106
+
107
+ const saturation = l
108
+ const value = -(t / 100) + 1
109
+
110
+ emit('update', {
111
+ s: saturation,
112
+ v: value,
113
+ h: props.hue
114
+ })
115
+ }
116
+
117
+ const handleMouseDown = () => {
118
+ isDragging.value = true
119
+ window.addEventListener('mousemove', handleMove)
120
+ window.addEventListener('mouseup', handleMouseUp)
121
+ }
122
+
123
+ const handleMouseUp = (e: MouseEvent) => {
124
+ handleMove(e)
125
+ unbindEventListeners()
126
+ isDragging.value = false
127
+ }
128
+
129
+ const handleTouchStart = (e: TouchEvent) => {
130
+ isDragging.value = true
131
+ handleMove(e)
132
+ }
133
+
134
+ const unbindEventListeners = () => {
135
+ window.removeEventListener('mousemove', handleMove)
136
+ window.removeEventListener('mouseup', handleMouseUp)
137
+ }
138
+
139
+ onBeforeUnmount(() => {
140
+ unbindEventListeners()
141
+ })
142
+ </script>
@@ -0,0 +1,4 @@
1
+ export { default as ColorPicker } from './ColorPicker.vue'
2
+ export { default as Color } from './Color.vue'
3
+ export { default as Gradient } from './Gradient.vue'
4
+ export { default as Checkboard } from './Checkboard.vue'