@luna-park/design 1.0.0

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 (130) hide show
  1. package/eslint.config.js +9 -0
  2. package/histoire.config.ts +60 -0
  3. package/package.json +71 -0
  4. package/public/favicon_rc.png +0 -0
  5. package/src/app.ts +9 -0
  6. package/src/assets/controls/mouse.svg +54 -0
  7. package/src/assets/logo_neon.svg +18 -0
  8. package/src/assets/logo_rc_color.svg +54 -0
  9. package/src/assets/logo_rc_square_blue.svg +16 -0
  10. package/src/assets/logo_square_blue.svg +17 -0
  11. package/src/assets/logo_square_white.svg +17 -0
  12. package/src/assets/logo_text_white.svg +32 -0
  13. package/src/assets/stars.svg +66 -0
  14. package/src/components/breadcrumb/LBreadLink.vue +40 -0
  15. package/src/components/breadcrumb/LBreadcrumb.story.vue +29 -0
  16. package/src/components/breadcrumb/LBreadcrumb.vue +54 -0
  17. package/src/components/breadcrumb/type.ts +6 -0
  18. package/src/components/context/LContextMenu.story.vue +73 -0
  19. package/src/components/context/LContextMenu.vue +24 -0
  20. package/src/components/context/LContextMenuElement.vue +54 -0
  21. package/src/components/context/LContextMenuWrapper.vue +55 -0
  22. package/src/components/context/LContextOption.story.vue +18 -0
  23. package/src/components/context/LContextOption.vue +160 -0
  24. package/src/components/context/LContextWrapper.story.vue +11 -0
  25. package/src/components/context/LContextWrapper.vue +60 -0
  26. package/src/components/context/store.ts +62 -0
  27. package/src/components/context/type.ts +27 -0
  28. package/src/components/dialog/LDialogAlert.vue +38 -0
  29. package/src/components/dialog/LDialogConfirm.vue +45 -0
  30. package/src/components/dialog/LDialogInjector.story.vue +41 -0
  31. package/src/components/dialog/LDialogInjector.vue +40 -0
  32. package/src/components/dialog/LDialogPrompt.vue +67 -0
  33. package/src/components/dialog/LDialogWrapper.vue +66 -0
  34. package/src/components/dialog/lib.ts +50 -0
  35. package/src/components/dialog/store.ts +32 -0
  36. package/src/components/floating/LFloating.story.vue +35 -0
  37. package/src/components/floating/LFloating.vue +362 -0
  38. package/src/components/form/LAutoComplete.vue +13 -0
  39. package/src/components/form/LAutoInput.story.vue +43 -0
  40. package/src/components/form/LAutoInput.vue +101 -0
  41. package/src/components/form/LButton.story.vue +147 -0
  42. package/src/components/form/LButton.vue +227 -0
  43. package/src/components/form/LCheckbox.story.vue +13 -0
  44. package/src/components/form/LCheckbox.vue +70 -0
  45. package/src/components/form/LColorInput.story.vue +28 -0
  46. package/src/components/form/LColorInput.vue +101 -0
  47. package/src/components/form/LImageInput.story.vue +28 -0
  48. package/src/components/form/LImageInput.vue +75 -0
  49. package/src/components/form/LInfo.story.vue +22 -0
  50. package/src/components/form/LInfo.vue +44 -0
  51. package/src/components/form/LInput.story.vue +150 -0
  52. package/src/components/form/LInput.vue +493 -0
  53. package/src/components/form/LInputDateFloating.vue +61 -0
  54. package/src/components/form/LInputNumber.story.vue +58 -0
  55. package/src/components/form/LProgress.story.vue +49 -0
  56. package/src/components/form/LProgress.vue +77 -0
  57. package/src/components/form/LSelect.story.vue +67 -0
  58. package/src/components/form/LSelect.vue +142 -0
  59. package/src/components/form/LSwitch.story.vue +15 -0
  60. package/src/components/form/LSwitch.vue +79 -0
  61. package/src/components/form/LTextarea.story.vue +29 -0
  62. package/src/components/form/LTextarea.vue +151 -0
  63. package/src/components/form/color-picker/LColorAlpha.vue +129 -0
  64. package/src/components/form/color-picker/LColorHue.vue +109 -0
  65. package/src/components/form/color-picker/LColorModels.vue +223 -0
  66. package/src/components/form/color-picker/LColorPicker.story.vue +44 -0
  67. package/src/components/form/color-picker/LColorPicker.vue +105 -0
  68. package/src/components/form/color-picker/LColorShade.vue +114 -0
  69. package/src/components/form/color-picker/LImagePicker.vue +477 -0
  70. package/src/components/form/dropdown/LDropdown.story.vue +123 -0
  71. package/src/components/form/dropdown/LDropdown.vue +483 -0
  72. package/src/components/form/dropdown/LDropdownOption.vue +224 -0
  73. package/src/components/form/dropdown/LDropdownSelection.vue +76 -0
  74. package/src/components/form/dropdown/types.ts +15 -0
  75. package/src/components/form/emoji-picker/LEmojiList.vue +54 -0
  76. package/src/components/form/emoji-picker/LEmojiListCategory.vue +92 -0
  77. package/src/components/form/emoji-picker/LEmojiPicker.story.vue +32 -0
  78. package/src/components/form/emoji-picker/LEmojiPicker.vue +55 -0
  79. package/src/components/form/emoji-picker/LEmojiSelect.story.vue +22 -0
  80. package/src/components/form/emoji-picker/LEmojiSelect.vue +51 -0
  81. package/src/components/form/icon-picker/LIconList.vue +100 -0
  82. package/src/components/form/icon-picker/LIconMaterial.vue +43 -0
  83. package/src/components/form/icon-picker/LIconPicker.story.vue +39 -0
  84. package/src/components/form/icon-picker/LIconPicker.vue +92 -0
  85. package/src/components/form/icon-picker/LIconSelect.story.vue +25 -0
  86. package/src/components/form/icon-picker/LIconSelect.vue +91 -0
  87. package/src/components/icons/LControls.story.vue +92 -0
  88. package/src/components/icons/LKeyIcon.vue +66 -0
  89. package/src/components/icons/LMouseIcon.vue +85 -0
  90. package/src/components/icons/LShortcut.story.vue +12 -0
  91. package/src/components/icons/LShortcut.vue +45 -0
  92. package/src/components/layout/LResizer.story.vue +89 -0
  93. package/src/components/layout/LResizer.vue +138 -0
  94. package/src/components/misc/LIcon.vue +34 -0
  95. package/src/components/misc/LLineLoader.story.vue +18 -0
  96. package/src/components/misc/LLineLoader.vue +52 -0
  97. package/src/components/misc/LLoading.story.vue +14 -0
  98. package/src/components/misc/LLoading.vue +28 -0
  99. package/src/components/misc/LStarsBackground.story.vue +16 -0
  100. package/src/components/misc/LStarsBackground.vue +121 -0
  101. package/src/components/navigation/LElementsPagination.vue +75 -0
  102. package/src/components/navigation/LPagination.story.vue +57 -0
  103. package/src/components/navigation/LPagination.vue +125 -0
  104. package/src/components/table/LCell.vue +37 -0
  105. package/src/components/table/LLine.vue +24 -0
  106. package/src/components/table/LTable.story.vue +35 -0
  107. package/src/components/table/LTable.vue +21 -0
  108. package/src/components/toasts/LContainer.story.vue +47 -0
  109. package/src/components/toasts/LContainer.vue +140 -0
  110. package/src/components/toasts/LToast.story.vue +47 -0
  111. package/src/components/toasts/LToast.vue +30 -0
  112. package/src/components/toasts/LToastInjector.vue +54 -0
  113. package/src/components/toasts/requests.ts +46 -0
  114. package/src/components/toasts/store.ts +45 -0
  115. package/src/components/utils/LVirtualElement.vue +36 -0
  116. package/src/components/utils/LVirtualScroller.story.vue +62 -0
  117. package/src/components/utils/LVirtualScroller.vue +105 -0
  118. package/src/components/utils/virtual.ts +6 -0
  119. package/src/env.d.ts +9 -0
  120. package/src/histoire.setup.ts +8 -0
  121. package/src/icons.ts +8 -0
  122. package/src/index.ts +58 -0
  123. package/src/style/colors.scss +152 -0
  124. package/src/style/fonts.scss +45 -0
  125. package/src/style/index.scss +64 -0
  126. package/src/style/layout.scss +3 -0
  127. package/src/style/lengths.scss +21 -0
  128. package/src/style/scrollbar.scss +27 -0
  129. package/tsconfig.json +34 -0
  130. package/vite.config.ts +68 -0
@@ -0,0 +1,109 @@
1
+ <template>
2
+ <div
3
+ class="color-hue"
4
+ @mousedown.left="isUpdating = true"
5
+ >
6
+ <div
7
+ ref="gradient"
8
+ class="gradient"
9
+ :style="gradientStyle"
10
+ />
11
+ <div
12
+ class="cursor"
13
+ :style="cursorStyle"
14
+ />
15
+ </div>
16
+ </template>
17
+
18
+
19
+ <script setup lang="ts">
20
+ import { computed, onMounted, ref, watch } from "vue";
21
+ import { usePointer, watchTriggerable } from "@vueuse/core";
22
+ import { clamp } from "@luna-park/utils";
23
+
24
+ const props = withDefaults(defineProps<{
25
+ height?: number;
26
+ hex: string;
27
+ hue: number;
28
+ luminosity: number;
29
+ saturation: number;
30
+ }>(), {
31
+ height: 120
32
+ });
33
+
34
+ const emits = defineEmits<(e: "update:hue", value: number) => void>();
35
+
36
+ const cursorStyle = computed(() => {
37
+ return {
38
+ top: `${ props.height * (1 - props.hue / 360) - 6 }px`,
39
+ background: props.hex
40
+ };
41
+ });
42
+
43
+ const gradient = ref<HTMLCanvasElement>();
44
+ const isUpdating = ref(false);
45
+ const { pressure: mouseClick, x: mouseX, y: mouseY } = usePointer();
46
+
47
+ const gradientStyle = computed(() => {
48
+ return {
49
+ height: `${ props.height }px`
50
+ };
51
+ });
52
+
53
+ watch([isUpdating, mouseClick, mouseX, mouseY], () => {
54
+ if (!isUpdating.value) {
55
+ return;
56
+ }
57
+
58
+ if (!mouseClick.value) {
59
+ isUpdating.value = false;
60
+ return;
61
+ }
62
+
63
+ if (!gradient.value) {
64
+ return;
65
+ }
66
+
67
+ const rect = gradient.value.getBoundingClientRect();
68
+ const hue = clamp(1 - (mouseY.value - rect.top) / props.height, 0, 1) * 360;
69
+
70
+ emits("update:hue", hue);
71
+ });
72
+ </script>
73
+
74
+ <style scoped>
75
+ .color-hue {
76
+ position: relative;
77
+
78
+ .gradient {
79
+ --gradient-saturation: calc(v-bind('props.saturation') * 100%);
80
+ --gradient-luminosity: calc(v-bind('props.luminosity') * 100%);
81
+
82
+ border-radius: var(--length-radius-s);
83
+ width: 16px;
84
+ background: linear-gradient(to bottom,
85
+ hsl(360, var(--gradient-saturation), var(--gradient-luminosity)),
86
+ hsl(270, var(--gradient-saturation), var(--gradient-luminosity)),
87
+ hsl(180, var(--gradient-saturation), var(--gradient-luminosity)),
88
+ hsl(90, var(--gradient-saturation), var(--gradient-luminosity)),
89
+ hsl(0, var(--gradient-saturation), var(--gradient-luminosity))
90
+ );
91
+ }
92
+
93
+ .cursor {
94
+ left: -4px;
95
+ box-sizing: border-box;
96
+ width: 24px;
97
+ height: 12px;
98
+ border: 2px solid var(--color-content-liter);
99
+ border-radius: 8px;
100
+ position: absolute;
101
+ box-shadow: 0 0 4px var(--color-background-lite);
102
+ cursor: ns-resize;
103
+
104
+ &:hover {
105
+ border-color: var(--color-content-lite);
106
+ }
107
+ }
108
+ }
109
+ </style>
@@ -0,0 +1,223 @@
1
+ <template>
2
+ <div class="color-models">
3
+ <LInput
4
+ v-model="hex"
5
+ @update:model-value="updateValue('hex')"
6
+ >
7
+ <template #prefix>
8
+ <span class="prefix">Hex:</span>
9
+ </template>
10
+ </LInput>
11
+ <div class="rgb">
12
+ <LInput
13
+ v-model="rgb.r"
14
+ :filter="maxFilter(255)"
15
+ type="number"
16
+ @update:model-value="updateValue('rgb')"
17
+ >
18
+ <template #prefix>
19
+ <span class="prefix">R:</span>
20
+ </template>
21
+ </LInput>
22
+ <LInput
23
+ v-model="rgb.g"
24
+ :filter="maxFilter(255)"
25
+ type="number"
26
+ @update:model-value="updateValue('rgb')"
27
+ >
28
+ <template #prefix>
29
+ <span class="prefix">G:</span>
30
+ </template>
31
+ </LInput>
32
+ <LInput
33
+ v-model="rgb.b"
34
+ :filter="maxFilter(255)"
35
+ type="number"
36
+ @update:model-value="updateValue('rgb')"
37
+ >
38
+ <template #prefix>
39
+ <span class="prefix">B:</span>
40
+ </template>
41
+ </LInput>
42
+ </div>
43
+ <div class="hsl">
44
+ <LInput
45
+ v-model="hsl.h"
46
+ :filter="maxFilter(360)"
47
+ type="number"
48
+ @update:model-value="updateValue('hsl')"
49
+ >
50
+ <template #prefix>
51
+ <span class="prefix">H:</span>
52
+ </template>
53
+ <template #suffix>
54
+ <span class="suffix">°</span>
55
+ </template>
56
+ </LInput>
57
+ <LInput
58
+ v-model="hsl.s"
59
+ :filter="maxFilter(100)"
60
+ type="number"
61
+ @update:model-value="updateValue('hsl')"
62
+ >
63
+ <template #prefix>
64
+ <span class="prefix">S:</span>
65
+ </template>
66
+ <template #suffix>
67
+ <span class="suffix">%</span>
68
+ </template>
69
+ </LInput>
70
+ <LInput
71
+ v-model="hsl.l"
72
+ :filter="maxFilter(100)"
73
+ type="number"
74
+ @update:model-value="updateValue('hsl')"
75
+ >
76
+ <template #prefix>
77
+ <span class="prefix">L:</span>
78
+ </template>
79
+ <template #suffix>
80
+ <span class="suffix">%</span>
81
+ </template>
82
+ </LInput>
83
+ </div>
84
+ <LInput
85
+ v-model="alpha"
86
+ :filter="maxFilter(100)"
87
+ type="number"
88
+ @update:model-value="updateValue('alpha')"
89
+ >
90
+ <template #prefix>
91
+ <span class="prefix">Alpha:</span>
92
+ </template>
93
+ <template #suffix>
94
+ <span class="suffix">%</span>
95
+ </template>
96
+ </LInput>
97
+ </div>
98
+ </template>
99
+
100
+
101
+ <script setup lang="ts">
102
+ import { SColor } from "synth-color";
103
+ import { ref, watch } from "vue";
104
+ import { clamp } from "@luna-park/utils";
105
+ import LInput from "@/components/form/LInput.vue";
106
+
107
+ const props = defineProps<{
108
+ color: string;
109
+ }>();
110
+
111
+ const emits = defineEmits<(e: "update:color", value: string) => void>();
112
+
113
+ type TModel = "hex" | "rgb" | "hsl" | "alpha";
114
+
115
+ const sColor = new SColor();
116
+
117
+ const hex = ref("");
118
+ const alpha = ref(0);
119
+ const rgb = ref({ b: 0, g: 0, r: 0 });
120
+ const hsl = ref({ h: 0, l: 0, s: 0 });
121
+
122
+ watch(() => props.color, updateInternals, { immediate: true });
123
+
124
+ function updateInternals() {
125
+ if (props.color === sColor.hex) {
126
+ return;
127
+ }
128
+
129
+ sColor.hex = props.color;
130
+ updateHex();
131
+ updateRgb();
132
+ updateHsl();
133
+ updateAlpha();
134
+ }
135
+
136
+ function updateHex() {
137
+ hex.value = sColor.hex;
138
+ }
139
+
140
+ function updateRgb() {
141
+ rgb.value = {
142
+ b: Math.round(sColor.rgb.b * 255),
143
+ g: Math.round(sColor.rgb.g * 255),
144
+ r: Math.round(sColor.rgb.r * 255)
145
+ };
146
+ }
147
+
148
+ function updateHsl() {
149
+ hsl.value = {
150
+ h: Math.round(sColor.hsl.h),
151
+ l: Math.round(sColor.hsl.l * 100),
152
+ s: Math.round(sColor.hsl.s * 100)
153
+ };
154
+ }
155
+
156
+ function updateAlpha() {
157
+ alpha.value = Math.round(sColor.alpha * 100);
158
+ }
159
+
160
+ function maxFilter(max: number) {
161
+ return (value: string) => clamp(parseInt(value), 0, max).toString();
162
+ }
163
+
164
+ function updateValue(model: TModel) {
165
+ switch (model) {
166
+ case "hex":
167
+ sColor.hex = hex.value;
168
+ emits("update:color", sColor.hex);
169
+ break;
170
+ case "rgb":
171
+ sColor.rgb = { b: rgb.value.b / 255, g: rgb.value.g / 255, r: rgb.value.r / 255 };
172
+ emits("update:color", sColor.hex);
173
+ break;
174
+ case "hsl":
175
+ sColor.hsl = { h: hsl.value.h, l: hsl.value.l / 100, s: hsl.value.s / 100 };
176
+ emits("update:color", sColor.hex);
177
+ break;
178
+ case "alpha":
179
+ sColor.alpha = alpha.value;
180
+ emits("update:color", sColor.hex);
181
+ break;
182
+ }
183
+
184
+ if (model !== "hex") {
185
+ updateHex();
186
+ }
187
+
188
+ if (model !== "rgb") {
189
+ updateRgb();
190
+ }
191
+
192
+ if (model !== "hsl") {
193
+ updateHsl();
194
+ }
195
+
196
+ if (model !== "alpha") {
197
+ updateAlpha();
198
+ }
199
+ }
200
+
201
+ </script>
202
+
203
+ <style scoped>
204
+ .color-models {
205
+ width: 192px;
206
+ display: flex;
207
+ gap: var(--length-xs);
208
+ flex-direction: column;
209
+
210
+ .rgb, .hsl {
211
+ display: flex;
212
+ gap: var(--length-xs);
213
+
214
+ > * {
215
+ flex: 1 1 0;
216
+ }
217
+ }
218
+
219
+ .prefix, .suffix {
220
+ color: var(--color-content-litest);
221
+ }
222
+ }
223
+ </style>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <Story
3
+ :layout="{
4
+ type: 'grid',
5
+ width: '480px'
6
+ }"
7
+ >
8
+ <Variant title="Default">
9
+ <LColorPicker
10
+ v-model="state.color"
11
+ class="picker"
12
+ />
13
+ </Variant>
14
+ <Variant title="Gradient">
15
+ <LColorPickerGradient
16
+ v-model="state.color"
17
+ class="picker"
18
+ />
19
+ </Variant>
20
+ <template #controls>
21
+ <HstText
22
+ v-model="state.color"
23
+ title="Color"
24
+ />
25
+ </template>
26
+ </Story>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import { reactive } from "vue";
31
+ import LColorPicker from "./LColorPicker.vue";
32
+ import LColorPickerGradient from "@/components/form/color-picker/LImagePicker.vue";
33
+
34
+ const state = reactive({
35
+ color: "#ff6600"
36
+ });
37
+ </script>
38
+
39
+ <style scoped>
40
+ .picker {
41
+ padding: var(--length-m);
42
+ height: 220px;
43
+ }
44
+ </style>
@@ -0,0 +1,105 @@
1
+ <template>
2
+ <div class="color-picker">
3
+ <LColorShade
4
+ v-model:saturation="saturation"
5
+ v-model:value="value"
6
+ :hex="internalColorNoAlpha"
7
+ :hue="hue"
8
+ />
9
+ <LColorHue
10
+ v-model:hue="hue"
11
+ :hex="internalColorNoAlpha"
12
+ :luminosity="luminosity"
13
+ :saturation="saturation"
14
+ />
15
+ <LColorAlpha
16
+ v-model:alpha="alpha"
17
+ :hex="internalColorNoAlpha"
18
+ />
19
+ <LColorModels
20
+ :color="internalColor"
21
+ @update:color="updateColor"
22
+ />
23
+ </div>
24
+ </template>
25
+
26
+
27
+ <script setup lang="ts">
28
+ import { computed, ref, watch } from "vue";
29
+ import { SColor } from "synth-color";
30
+ import LColorShade from "@/components/form/color-picker/LColorShade.vue";
31
+ import LColorHue from "@/components/form/color-picker/LColorHue.vue";
32
+ import LColorAlpha from "@/components/form/color-picker/LColorAlpha.vue";
33
+ import LColorModels from "@/components/form/color-picker/LColorModels.vue";
34
+
35
+ const props = defineProps<{
36
+ gradient?: boolean;
37
+ modelValue: string;
38
+ }>();
39
+
40
+ const emits = defineEmits<(e: "update:modelValue", value: string) => void>();
41
+
42
+ const internalColor = ref("");
43
+ const internalColorNoAlpha = computed(() => internalColor.value.slice(0, 7));
44
+ const inputColor = ref("");
45
+
46
+ const hue = ref(0);
47
+ const saturation = ref(0);
48
+ const value = ref(0);
49
+ const luminosity = ref(0);
50
+ const alpha = ref(1);
51
+
52
+ let color = new SColor("");
53
+
54
+ watch(() => props.modelValue, () => {
55
+ color = new SColor(props.modelValue);
56
+ inputColor.value = color.hex;
57
+
58
+ if (inputColor.value === internalColor.value) {
59
+ return;
60
+ }
61
+
62
+ updateInternals();
63
+ }, {
64
+ immediate: true
65
+ });
66
+
67
+ function updateColor(newColor: string) {
68
+ color.hex = newColor;
69
+ updateInternals();
70
+ }
71
+
72
+ function updateInternals() {
73
+ internalColor.value = color.hex;
74
+ hue.value = color.hsv.h;
75
+ saturation.value = color.hsv.s;
76
+ value.value = color.hsv.v;
77
+ alpha.value = color.alpha;
78
+ luminosity.value = color.hsl.l;
79
+ }
80
+
81
+ watch([hue, saturation, value, alpha], () => {
82
+ color.hsva = {
83
+ alpha: alpha.value,
84
+ h: hue.value,
85
+ s: saturation.value,
86
+ v: value.value
87
+ };
88
+ luminosity.value = color.hsl.l;
89
+ internalColor.value = color.hex;
90
+
91
+ if (internalColor.value === inputColor.value) {
92
+ return;
93
+ }
94
+
95
+ emits("update:modelValue", internalColor.value);
96
+ });
97
+ </script>
98
+
99
+ <style scoped>
100
+ .color-picker {
101
+ height: 120px;
102
+ display: flex;
103
+ gap: var(--length-m);
104
+ }
105
+ </style>
@@ -0,0 +1,114 @@
1
+ <template>
2
+ <div
3
+ class="color-shade"
4
+ @mousedown.left="isUpdating = true"
5
+ >
6
+ <div
7
+ ref="gradient"
8
+ class="gradient"
9
+ :style="gradientStyle"
10
+ />
11
+ <div
12
+ class="cursor"
13
+ :style="cursorStyle"
14
+ />
15
+ </div>
16
+ </template>
17
+
18
+
19
+ <script setup lang="ts">
20
+ import { computed, ref, watch } from "vue";
21
+ import { usePointer, watchThrottled } from "@vueuse/core";
22
+ import { clamp } from "@luna-park/utils";
23
+
24
+ const props = withDefaults(defineProps<{
25
+ height?: number;
26
+ hex: string;
27
+ hue: number;
28
+ saturation: number;
29
+ value: number;
30
+ width?: number;
31
+ }>(), {
32
+ height: 120,
33
+ width: 120
34
+ });
35
+
36
+ const emits = defineEmits<{
37
+ (e: "update:value", value: number): void;
38
+ (e: "update:saturation", value: number): void;
39
+ }>();
40
+
41
+ const gradient = ref<HTMLCanvasElement>();
42
+ const isUpdating = ref(false);
43
+ const { pressure: mouseClick, x: mouseX, y: mouseY } = usePointer();
44
+
45
+ const cursorStyle = computed(() => {
46
+ return {
47
+ top: `${ props.height * (1 - props.value) - 6 }px`,
48
+ left: `${ props.height * props.saturation - 6 }px`,
49
+ background: props.hex
50
+ };
51
+ });
52
+
53
+ const gradientStyle = computed(() => {
54
+ return {
55
+ height: `${ props.height }px`,
56
+ width: `${ props.width }px`
57
+ };
58
+ });
59
+
60
+ watchThrottled([isUpdating, mouseClick, mouseX, mouseY], () => {
61
+ if (!isUpdating.value) {
62
+ return;
63
+ }
64
+
65
+ if (!mouseClick.value) {
66
+ isUpdating.value = false;
67
+ return;
68
+ }
69
+
70
+ if (!gradient.value) {
71
+ return;
72
+ }
73
+
74
+ const rect = gradient.value.getBoundingClientRect();
75
+ const saturation = clamp((mouseX.value - rect.left) / props.width, 0, 1);
76
+ const value = clamp(1 - (mouseY.value - rect.top) / props.height, 0, 1);
77
+
78
+ emits("update:saturation", saturation);
79
+ emits("update:value", value);
80
+ }, {
81
+ throttle: 1000 / 60
82
+ });
83
+ </script>
84
+
85
+ <style scoped>
86
+ .color-shade {
87
+ position: relative;
88
+
89
+ .gradient {
90
+ border-radius: var(--length-radius-s);
91
+ background: linear-gradient(to bottom, hsla(0, 0%, 0%, 0), hsla(0, 0%, 0%, 100%)),
92
+ linear-gradient(to right, hsl(v-bind('props.hue'), 100%, 100%), hsl(v-bind('props.hue'), 100%, 50%));
93
+ }
94
+
95
+ canvas {
96
+ border-radius: var(--length-radius-s);
97
+ }
98
+
99
+ .cursor {
100
+ box-sizing: border-box;
101
+ width: 12px;
102
+ height: 12px;
103
+ border: 2px solid var(--color-content-liter);
104
+ border-radius: 8px;
105
+ position: absolute;
106
+ box-shadow: 0 0 4px var(--color-background-lite);
107
+ cursor: move;
108
+
109
+ &:hover {
110
+ border-color: var(--color-content-lite);
111
+ }
112
+ }
113
+ }
114
+ </style>