@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,77 @@
1
+ <template>
2
+ <div
3
+ class="progress"
4
+ :class="{big, line, borderless}"
5
+ >
6
+ <div class="value" />
7
+ <slot />
8
+ </div>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { computed } from "vue";
13
+
14
+ const props = defineProps<{
15
+ big?: boolean;
16
+ borderless?: boolean;
17
+ line?: boolean;
18
+ max: number;
19
+ value: number;
20
+ }>();
21
+
22
+ const percentage = computed(() => {
23
+ return `${ props.value / props.max * 100 }%`;
24
+ });
25
+ </script>
26
+
27
+ <style scoped>
28
+ .progress {
29
+ box-sizing: border-box;
30
+ height: 24px;
31
+ font-size: 0.8rem;
32
+ background: var(--color-background-1);
33
+ border: 1px solid var(--color-soft);
34
+ border-radius: var(--length-radius-s);
35
+ color: var(--color-content-lite);
36
+ transition: var(--transition-fast);
37
+ display: flex;
38
+ justify-content: center;
39
+ align-items: center;
40
+
41
+ &.line {
42
+ border-radius: var(--length-radius-xs);
43
+ height: 8px;
44
+ }
45
+
46
+ &.big {
47
+ font-size: .9rem;
48
+ border-width: 2px;
49
+ border-radius: var(--length-radius-m);
50
+ height: 36px;
51
+
52
+ &.line {
53
+ border-radius: var(--length-radius-s);
54
+ height: 12px;
55
+ }
56
+ }
57
+
58
+ &.borderless {
59
+ border: none;
60
+ }
61
+
62
+ overflow: hidden;
63
+ position: relative;
64
+ z-index: 1;
65
+
66
+ .value {
67
+ position: absolute;
68
+ left: 0;
69
+ top: 0;
70
+ bottom: 0;
71
+ width: v-bind(percentage);
72
+ background: var(--color-primary-dark);
73
+ z-index: -1;
74
+ transition: width var(--duration-fast);
75
+ }
76
+ }
77
+ </style>
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <Story
3
+ :layout="{
4
+ type: 'grid',
5
+ width: '320px',
6
+ }"
7
+ >
8
+ <Variant title="Default">
9
+ <LSelect
10
+ v-model="value"
11
+ :options="options"
12
+ />
13
+ </Variant>
14
+ <Variant title="Default">
15
+ <LSelect
16
+ v-model="value"
17
+ :options="optionsIcon"
18
+ />
19
+ </Variant>
20
+ <Variant title="Custom slots">
21
+ <LSelect
22
+ v-model="value"
23
+ :options="options"
24
+ >
25
+ <template #hover>
26
+ Hello!
27
+ </template>
28
+ </LSelect>
29
+ </Variant>
30
+ <Variant title="Grid">
31
+ <LSelect
32
+ v-model="value"
33
+ :grid="2"
34
+ :options="options"
35
+ >
36
+ <template #hover>
37
+ Hello!
38
+ </template>
39
+ </LSelect>
40
+ </Variant>
41
+ </Story>
42
+ </template>
43
+
44
+ <script setup lang="ts">
45
+ import { ref } from "vue";
46
+ import LSelect from "@/components/form/LSelect.vue";
47
+ import { faCar, faPlane, faRocket } from "@fortawesome/pro-solid-svg-icons";
48
+
49
+ const value = ref("default");
50
+
51
+ const options = [
52
+ "default",
53
+ "hover",
54
+ "active",
55
+ "focus"
56
+ ];
57
+
58
+ const optionsIcon = [
59
+ { id: "plane", icon: faPlane, value: "Plane" },
60
+ { id: "car", icon: faCar, value: "Car" },
61
+ { id: "rocket", icon: faRocket, value: "Rocket" }
62
+ ];
63
+ </script>
64
+
65
+ <style scoped>
66
+
67
+ </style>
@@ -0,0 +1,142 @@
1
+ <template>
2
+ <div
3
+ class="select"
4
+ :class="{grid, small, borderless, raw}"
5
+ >
6
+ <div
7
+ v-for="option in internalOptions"
8
+ :key="option.id"
9
+ class="option"
10
+ :class="{selected: option.id === props.modelValue}"
11
+ :title="option.title || option.value"
12
+ @click="emits('update:modelValue', option.id)"
13
+ >
14
+ <slot :name="option.id">
15
+ <LIcon
16
+ v-if="option.icon"
17
+ class="icon"
18
+ :icon="option.icon"
19
+ />
20
+ {{ option.value }}
21
+ </slot>
22
+ </div>
23
+ </div>
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
28
+ import { computed } from "vue";
29
+
30
+ import LIcon from "@/components/misc/LIcon.vue";
31
+
32
+ type TFullOption = { icon?: IconDefinition; id: string; title?: string; value?: string; };
33
+ type TOptions = Array<string | TFullOption> | Record<string, string>;
34
+
35
+ const props = defineProps<{
36
+ borderless?: boolean;
37
+ grid?: number;
38
+ modelValue?: string;
39
+ options: TOptions;
40
+ raw?: boolean;
41
+ small?: boolean;
42
+ }>();
43
+
44
+ const emits = defineEmits<(e: "update:modelValue", value: string) => void>();
45
+
46
+ const internalOptions = computed<Array<TFullOption>>(() => {
47
+ if (Array.isArray(props.options)) {
48
+ return props.options.map((value) => {
49
+ if (typeof value === "string") {
50
+ return { id: value, value };
51
+ }
52
+
53
+ return value;
54
+ });
55
+ }
56
+
57
+ return Object.entries(props.options).map(([id, value]) => ({ id, value }));
58
+ });
59
+
60
+ const columnTemplate = computed(() => `repeat(${ props.grid || 1 }, 1fr)`);
61
+ </script>
62
+
63
+ <style scoped>
64
+ .select {
65
+ display: flex;
66
+ font-size: 0.8rem;
67
+ color: var(--color-content-liter);
68
+ background: var(--color-background-1);
69
+ border: 1px solid var(--color-soft);
70
+ box-sizing: border-box;
71
+ align-items: center;
72
+ border-radius: var(--length-radius-s);
73
+ overflow: hidden;
74
+ position: relative;
75
+ background-clip: content-box;
76
+ gap: 1px;
77
+ z-index: 0;
78
+
79
+ &::before {
80
+ content: "";
81
+ position: absolute;
82
+ inset: 0;
83
+ z-index: -1;
84
+ background-color: var(--color-soft);
85
+ }
86
+
87
+ &.grid {
88
+ display: grid;
89
+ grid-template-columns: v-bind(columnTemplate);
90
+ }
91
+
92
+ .option {
93
+ box-sizing: border-box;
94
+ height: 22px;
95
+ display: flex;
96
+ gap: var(--length-xxs);
97
+ align-items: center;
98
+ justify-content: center;
99
+ flex: 1 1 0;
100
+ cursor: pointer;
101
+ transition: var(--transition-fast);
102
+ background: var(--color-background-1);
103
+ padding: 0 var(--length-xs);
104
+
105
+ .icon {
106
+ color: var(--color-content-litest);
107
+ }
108
+
109
+ &:hover {
110
+ background-color: var(--color-background-2);
111
+ }
112
+
113
+ &.selected {
114
+ color: var(--color-content-lite);
115
+ background-color: var(--color-primary-dark);
116
+
117
+ .icon {
118
+ color: var(--color-content-liter);
119
+ }
120
+ }
121
+ }
122
+
123
+ &.raw .option {
124
+ padding: 0;
125
+ }
126
+
127
+ &.small {
128
+ .option {
129
+ height: 18px;
130
+ }
131
+ }
132
+
133
+ &.borderless {
134
+ border-color: var(--color-background-1);
135
+ border-radius: var(--length-radius-xs);
136
+
137
+ &::before {
138
+ background-color: var(--color-background-1);
139
+ }
140
+ }
141
+ }
142
+ </style>
@@ -0,0 +1,15 @@
1
+ <template>
2
+ <Story>
3
+ <LSwitch v-model="value" />
4
+ </Story>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { ref } from "vue";
9
+ import LSwitch from "./LSwitch.vue";
10
+
11
+ const value = ref(false);
12
+ </script>
13
+
14
+ <style scoped>
15
+ </style>
@@ -0,0 +1,79 @@
1
+ <template>
2
+ <div
3
+ class="switch"
4
+ :class="{activated: modelValue, big}"
5
+ :title="title"
6
+ @click="toggle"
7
+ >
8
+ <div class="circle" />
9
+ </div>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { computed } from "vue";
14
+
15
+ const props = defineProps<{
16
+ big?: boolean;
17
+ modelValue: boolean;
18
+ }>();
19
+
20
+ const emits = defineEmits<(e: "update:modelValue", value: boolean) => void>();
21
+
22
+ function toggle() {
23
+ emits("update:modelValue", !props.modelValue);
24
+ }
25
+
26
+ const title = computed(() => props.modelValue ? "On" : "Off");
27
+ </script>
28
+
29
+ <style scoped>
30
+ .switch {
31
+ --switch-size: 20px;
32
+ --switch-background: var(--color-background-0);
33
+ background: var(--switch-background);
34
+ width: calc(2 * var(--switch-size));
35
+ border-radius: var(--switch-size);
36
+ cursor: pointer;
37
+ transition: background var(--duration-fast);
38
+ border: 1px solid var(--color-soft);
39
+
40
+ &.big {
41
+ --switch-size: 32px;
42
+ border-width: 2px;
43
+ }
44
+
45
+ .circle {
46
+ left: 0;
47
+ position: relative;
48
+ background: var(--color-background-1);
49
+ border-radius: var(--switch-size);
50
+ border: 2px solid var(--switch-background);
51
+ box-sizing: border-box;
52
+ height: var(--switch-size);
53
+ width: var(--switch-size);
54
+ transition: background var(--duration-fast), border var(--duration-fast), left var(--duration-fast);
55
+ }
56
+
57
+ &:hover {
58
+ .circle {
59
+ background: var(--color-background-3);
60
+ }
61
+ }
62
+
63
+ &.activated {
64
+ border-color: var(--color-primary);
65
+ --switch-background: var(--color-primary-dark);
66
+
67
+ .circle {
68
+ left: var(--switch-size);
69
+ background: var(--color-primary);
70
+ }
71
+
72
+ &:hover {
73
+ .circle {
74
+ background: var(--color-primary-content-lite);
75
+ }
76
+ }
77
+ }
78
+ }
79
+ </style>
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <Story>
3
+ <Variant title="standard">
4
+ <LTextarea
5
+ v-model="text"
6
+ class="textarea"
7
+ />
8
+ </Variant>
9
+ <Variant title="big">
10
+ <LTextarea
11
+ v-model="text"
12
+ big
13
+ class="textarea"
14
+ />
15
+ </Variant>
16
+ </Story>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ import { ref } from "vue";
21
+ import LTextarea from "./LTextarea.vue";
22
+
23
+ const text = ref("Lorem ipsum");
24
+ </script>
25
+
26
+ <style>
27
+ .textarea {
28
+ }
29
+ </style>
@@ -0,0 +1,151 @@
1
+ <template>
2
+ <label
3
+ class="input"
4
+ :class="{big, transparent, borderless, primary, 'with-label':!!label, 'with-value': hasValue, small }"
5
+ >
6
+ <span
7
+ v-if="label"
8
+ class="label"
9
+ >
10
+ {{ label }}
11
+ </span>
12
+ <textarea
13
+ class="text"
14
+ :placeholder="placeholder"
15
+ :title="modelValue"
16
+ :value="modelValue"
17
+ @input="emits('update:modelValue', $event.target.value)"
18
+ @keydown.enter="emits('enter')"
19
+ />
20
+ </label>
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ import { computed } from "vue";
25
+
26
+ const props = defineProps<{
27
+ big?: boolean;
28
+ borderless?: boolean;
29
+ label?: string;
30
+ mask?: string; // TODO: mask (like XX/XX/XXXX)
31
+ modelValue: string;
32
+ placeholder?: string;
33
+ primary?: boolean;
34
+ small?: boolean;
35
+ transparent?: boolean;
36
+ }>();
37
+
38
+ const emits = defineEmits<{
39
+ (e: "update:modelValue", id: string): void;
40
+ (e: "enter"): void;
41
+ }>();
42
+
43
+ const hasValue = computed(() => {
44
+ return !!props.modelValue?.trim() || props.placeholder;
45
+ });
46
+ </script>
47
+
48
+ <style scoped>
49
+ .input {
50
+ font-size: 0.8rem;
51
+ color: var(--color-content-lite);
52
+ background: var(--color-background-1);
53
+ border: 1px solid var(--color-soft);
54
+ border-radius: var(--length-radius-s);
55
+ padding: 0 var(--length-xxs);
56
+ transition: var(--transition-fast);
57
+ cursor: text;
58
+ display: flex;
59
+ box-sizing: border-box;
60
+ position: relative;
61
+
62
+ .label {
63
+ position: absolute;
64
+ top: 2px;
65
+ left: 0;
66
+ transform: translate(6px, 0px);
67
+ transition: var(--transition-fast),transform var(--duration-fast);
68
+ color: var(--color-content-liter);
69
+ opacity: 0.5;
70
+ }
71
+
72
+ &.big {
73
+ font-size: .9rem;
74
+ border-width: 2px;
75
+ border-radius: var(--length-radius-m);
76
+ padding: 0 var(--length-xs);
77
+
78
+ .label {
79
+ transform: translate(10px, 0px);
80
+ top: 6px;
81
+ }
82
+ }
83
+
84
+ &.small {
85
+ font-size: .7rem;
86
+ border-radius: var(--length-radius-xs);
87
+ padding: 0 var(--length-xxxs);
88
+ }
89
+
90
+ &.with-label:focus-within, &.with-label.with-value {
91
+ margin-top: 18px;
92
+
93
+ .label {
94
+ transform: translate(0px, -24px);
95
+ color: var(--color-content-liter);
96
+ opacity: 1;
97
+ }
98
+
99
+ &.big {
100
+ margin-top: 24px;
101
+
102
+ .label {
103
+ transform: translate(0px, -36px);
104
+ }
105
+ }
106
+ }
107
+
108
+ &.with-label:focus-within {
109
+ .label {
110
+ color: var(--color-content);
111
+ }
112
+ }
113
+
114
+ &.borderless {
115
+ border: none;
116
+ }
117
+
118
+ &.primary {
119
+ background: var(--color-primary);
120
+ }
121
+
122
+ &.transparent {
123
+ background: none;
124
+ }
125
+
126
+ .text {
127
+ cursor: inherit;
128
+ width: 100%;
129
+
130
+ &::placeholder {
131
+ color: var(--color-content-liter);
132
+ opacity: 0.5;
133
+ }
134
+ }
135
+
136
+ &:hover, &:focus-within {
137
+ background: var(--color-background-2);
138
+ color: var(--color-content);
139
+ border-color: var(--color-primary);
140
+
141
+ &.primary {
142
+ background: var(--color-primary-dark);
143
+ }
144
+
145
+ &.transparent {
146
+ background: none;
147
+ border-color: var(--color-content-litest);
148
+ }
149
+ }
150
+ }
151
+ </style>
@@ -0,0 +1,129 @@
1
+ <template>
2
+ <div
3
+ class="color-alpha"
4
+ @mousedown.left="isUpdating = true"
5
+ >
6
+ <div
7
+ ref="gradient"
8
+ class="checkerboard"
9
+ :style="gradientStyle"
10
+ >
11
+ <div class="gradient" />
12
+ </div>
13
+ <div
14
+ class="cursor"
15
+ :style="cursorStyle"
16
+ >
17
+ <div
18
+ class="color"
19
+ :style="colorStyle"
20
+ />
21
+ </div>
22
+ </div>
23
+ </template>
24
+
25
+
26
+ <script setup lang="ts">
27
+ import { computed, ref, watch } from "vue";
28
+ import { usePointer } from "@vueuse/core";
29
+ import { clamp } from "@luna-park/utils";
30
+
31
+ const props = withDefaults(defineProps<{
32
+ alpha: number;
33
+ height?: number;
34
+ hex: string;
35
+ }>(), {
36
+ height: 120
37
+ });
38
+
39
+ const emits = defineEmits<(e: "update:alpha", value: number) => void>();
40
+
41
+ const cursorStyle = computed(() => {
42
+ return {
43
+ top: `${ props.height * (1 - props.alpha) - 6 }px`
44
+ };
45
+ });
46
+
47
+ const colorStyle = computed(() => {
48
+ return {
49
+ background: props.hex + Math.round(props.alpha * 255).toString(16).padStart(2, "0")
50
+ };
51
+ });
52
+
53
+ const gradient = ref<HTMLCanvasElement>();
54
+ const isUpdating = ref(false);
55
+ const { pressure: mouseClick, x: mouseX, y: mouseY } = usePointer();
56
+
57
+ const gradientStyle = computed(() => {
58
+ return {
59
+ height: `${ props.height }px`
60
+ };
61
+ });
62
+
63
+ watch([isUpdating, mouseClick, mouseX, mouseY], () => {
64
+ if (!isUpdating.value) {
65
+ return;
66
+ }
67
+
68
+ if (!mouseClick.value) {
69
+ isUpdating.value = false;
70
+ return;
71
+ }
72
+
73
+ if (!gradient.value) {
74
+ return;
75
+ }
76
+
77
+ const rect = gradient.value.getBoundingClientRect();
78
+ const alpha = clamp(1 - (mouseY.value - rect.top) / props.height, 0, 1);
79
+
80
+ emits("update:alpha", alpha);
81
+ });
82
+ </script>
83
+
84
+ <style scoped>
85
+ .color-alpha {
86
+ position: relative;
87
+
88
+ canvas {
89
+ width: 16px;
90
+ display: block;
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
+ background: repeating-conic-gradient(var(--color-background-lite) 0% 25%, var(--color-background-3) 0% 50%) 50% / 16px 16px;
103
+ overflow: hidden;
104
+ cursor: ns-resize;
105
+
106
+ &:hover {
107
+ border-color: var(--color-content-lite);
108
+ }
109
+
110
+ .color {
111
+ width: 100%;
112
+ height: 100%;
113
+ }
114
+ }
115
+
116
+ .checkerboard {
117
+ background: repeating-conic-gradient(var(--color-background-lite) 0% 25%, transparent 0% 50%) 50% / 16px 16px;
118
+ border-radius: var(--length-radius-s);
119
+ overflow: hidden;
120
+ width: 16px;
121
+ height: 100%;
122
+
123
+ .gradient {
124
+ height: 100%;
125
+ background: linear-gradient(to bottom, v-bind('props.hex'), transparent);
126
+ }
127
+ }
128
+ }
129
+ </style>