@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.
- package/eslint.config.js +9 -0
- package/histoire.config.ts +60 -0
- package/package.json +71 -0
- package/public/favicon_rc.png +0 -0
- package/src/app.ts +9 -0
- package/src/assets/controls/mouse.svg +54 -0
- package/src/assets/logo_neon.svg +18 -0
- package/src/assets/logo_rc_color.svg +54 -0
- package/src/assets/logo_rc_square_blue.svg +16 -0
- package/src/assets/logo_square_blue.svg +17 -0
- package/src/assets/logo_square_white.svg +17 -0
- package/src/assets/logo_text_white.svg +32 -0
- package/src/assets/stars.svg +66 -0
- package/src/components/breadcrumb/LBreadLink.vue +40 -0
- package/src/components/breadcrumb/LBreadcrumb.story.vue +29 -0
- package/src/components/breadcrumb/LBreadcrumb.vue +54 -0
- package/src/components/breadcrumb/type.ts +6 -0
- package/src/components/context/LContextMenu.story.vue +73 -0
- package/src/components/context/LContextMenu.vue +24 -0
- package/src/components/context/LContextMenuElement.vue +54 -0
- package/src/components/context/LContextMenuWrapper.vue +55 -0
- package/src/components/context/LContextOption.story.vue +18 -0
- package/src/components/context/LContextOption.vue +160 -0
- package/src/components/context/LContextWrapper.story.vue +11 -0
- package/src/components/context/LContextWrapper.vue +60 -0
- package/src/components/context/store.ts +62 -0
- package/src/components/context/type.ts +27 -0
- package/src/components/dialog/LDialogAlert.vue +38 -0
- package/src/components/dialog/LDialogConfirm.vue +45 -0
- package/src/components/dialog/LDialogInjector.story.vue +41 -0
- package/src/components/dialog/LDialogInjector.vue +40 -0
- package/src/components/dialog/LDialogPrompt.vue +67 -0
- package/src/components/dialog/LDialogWrapper.vue +66 -0
- package/src/components/dialog/lib.ts +50 -0
- package/src/components/dialog/store.ts +32 -0
- package/src/components/floating/LFloating.story.vue +35 -0
- package/src/components/floating/LFloating.vue +362 -0
- package/src/components/form/LAutoComplete.vue +13 -0
- package/src/components/form/LAutoInput.story.vue +43 -0
- package/src/components/form/LAutoInput.vue +101 -0
- package/src/components/form/LButton.story.vue +147 -0
- package/src/components/form/LButton.vue +227 -0
- package/src/components/form/LCheckbox.story.vue +13 -0
- package/src/components/form/LCheckbox.vue +70 -0
- package/src/components/form/LColorInput.story.vue +28 -0
- package/src/components/form/LColorInput.vue +101 -0
- package/src/components/form/LImageInput.story.vue +28 -0
- package/src/components/form/LImageInput.vue +75 -0
- package/src/components/form/LInfo.story.vue +22 -0
- package/src/components/form/LInfo.vue +44 -0
- package/src/components/form/LInput.story.vue +150 -0
- package/src/components/form/LInput.vue +493 -0
- package/src/components/form/LInputDateFloating.vue +61 -0
- package/src/components/form/LInputNumber.story.vue +58 -0
- package/src/components/form/LProgress.story.vue +49 -0
- package/src/components/form/LProgress.vue +77 -0
- package/src/components/form/LSelect.story.vue +67 -0
- package/src/components/form/LSelect.vue +142 -0
- package/src/components/form/LSwitch.story.vue +15 -0
- package/src/components/form/LSwitch.vue +79 -0
- package/src/components/form/LTextarea.story.vue +29 -0
- package/src/components/form/LTextarea.vue +151 -0
- package/src/components/form/color-picker/LColorAlpha.vue +129 -0
- package/src/components/form/color-picker/LColorHue.vue +109 -0
- package/src/components/form/color-picker/LColorModels.vue +223 -0
- package/src/components/form/color-picker/LColorPicker.story.vue +44 -0
- package/src/components/form/color-picker/LColorPicker.vue +105 -0
- package/src/components/form/color-picker/LColorShade.vue +114 -0
- package/src/components/form/color-picker/LImagePicker.vue +477 -0
- package/src/components/form/dropdown/LDropdown.story.vue +123 -0
- package/src/components/form/dropdown/LDropdown.vue +483 -0
- package/src/components/form/dropdown/LDropdownOption.vue +224 -0
- package/src/components/form/dropdown/LDropdownSelection.vue +76 -0
- package/src/components/form/dropdown/types.ts +15 -0
- package/src/components/form/emoji-picker/LEmojiList.vue +54 -0
- package/src/components/form/emoji-picker/LEmojiListCategory.vue +92 -0
- package/src/components/form/emoji-picker/LEmojiPicker.story.vue +32 -0
- package/src/components/form/emoji-picker/LEmojiPicker.vue +55 -0
- package/src/components/form/emoji-picker/LEmojiSelect.story.vue +22 -0
- package/src/components/form/emoji-picker/LEmojiSelect.vue +51 -0
- package/src/components/form/icon-picker/LIconList.vue +100 -0
- package/src/components/form/icon-picker/LIconMaterial.vue +43 -0
- package/src/components/form/icon-picker/LIconPicker.story.vue +39 -0
- package/src/components/form/icon-picker/LIconPicker.vue +92 -0
- package/src/components/form/icon-picker/LIconSelect.story.vue +25 -0
- package/src/components/form/icon-picker/LIconSelect.vue +91 -0
- package/src/components/icons/LControls.story.vue +92 -0
- package/src/components/icons/LKeyIcon.vue +66 -0
- package/src/components/icons/LMouseIcon.vue +85 -0
- package/src/components/icons/LShortcut.story.vue +12 -0
- package/src/components/icons/LShortcut.vue +45 -0
- package/src/components/layout/LResizer.story.vue +89 -0
- package/src/components/layout/LResizer.vue +138 -0
- package/src/components/misc/LIcon.vue +34 -0
- package/src/components/misc/LLineLoader.story.vue +18 -0
- package/src/components/misc/LLineLoader.vue +52 -0
- package/src/components/misc/LLoading.story.vue +14 -0
- package/src/components/misc/LLoading.vue +28 -0
- package/src/components/misc/LStarsBackground.story.vue +16 -0
- package/src/components/misc/LStarsBackground.vue +121 -0
- package/src/components/navigation/LElementsPagination.vue +75 -0
- package/src/components/navigation/LPagination.story.vue +57 -0
- package/src/components/navigation/LPagination.vue +125 -0
- package/src/components/table/LCell.vue +37 -0
- package/src/components/table/LLine.vue +24 -0
- package/src/components/table/LTable.story.vue +35 -0
- package/src/components/table/LTable.vue +21 -0
- package/src/components/toasts/LContainer.story.vue +47 -0
- package/src/components/toasts/LContainer.vue +140 -0
- package/src/components/toasts/LToast.story.vue +47 -0
- package/src/components/toasts/LToast.vue +30 -0
- package/src/components/toasts/LToastInjector.vue +54 -0
- package/src/components/toasts/requests.ts +46 -0
- package/src/components/toasts/store.ts +45 -0
- package/src/components/utils/LVirtualElement.vue +36 -0
- package/src/components/utils/LVirtualScroller.story.vue +62 -0
- package/src/components/utils/LVirtualScroller.vue +105 -0
- package/src/components/utils/virtual.ts +6 -0
- package/src/env.d.ts +9 -0
- package/src/histoire.setup.ts +8 -0
- package/src/icons.ts +8 -0
- package/src/index.ts +58 -0
- package/src/style/colors.scss +152 -0
- package/src/style/fonts.scss +45 -0
- package/src/style/index.scss +64 -0
- package/src/style/layout.scss +3 -0
- package/src/style/lengths.scss +21 -0
- package/src/style/scrollbar.scss +27 -0
- package/tsconfig.json +34 -0
- package/vite.config.ts +68 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ul
|
|
3
|
+
ref="options"
|
|
4
|
+
class="options"
|
|
5
|
+
:class="{'max-height': maxHeight, big}"
|
|
6
|
+
>
|
|
7
|
+
<template
|
|
8
|
+
v-for="option in options"
|
|
9
|
+
:key="option.id"
|
|
10
|
+
>
|
|
11
|
+
<LDropdownOption
|
|
12
|
+
:big="big"
|
|
13
|
+
:container="container"
|
|
14
|
+
:depth="depth"
|
|
15
|
+
:hover="hover"
|
|
16
|
+
:max-height="maxHeight"
|
|
17
|
+
:option="option"
|
|
18
|
+
:selected="selected"
|
|
19
|
+
@select="select"
|
|
20
|
+
/>
|
|
21
|
+
</template>
|
|
22
|
+
<li
|
|
23
|
+
v-if="!options.length"
|
|
24
|
+
class="option disabled"
|
|
25
|
+
>
|
|
26
|
+
No options...
|
|
27
|
+
</li>
|
|
28
|
+
</ul>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script setup lang="ts">
|
|
32
|
+
import { TOption } from "@/components/form/dropdown/types";
|
|
33
|
+
import { onMounted, useTemplateRef } from "vue";
|
|
34
|
+
import LDropdownOption from "@/components/form/dropdown/LDropdownOption.vue";
|
|
35
|
+
|
|
36
|
+
const props = withDefaults(defineProps<{
|
|
37
|
+
big?: boolean;
|
|
38
|
+
container?: string;
|
|
39
|
+
depth?: number;
|
|
40
|
+
hover: Array<string>;
|
|
41
|
+
maxHeight?: boolean;
|
|
42
|
+
options: Array<TOption>;
|
|
43
|
+
selected?: string | number
|
|
44
|
+
}>(), {
|
|
45
|
+
depth: 0
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const emits = defineEmits<(e: "select", value: TOption) => void>();
|
|
49
|
+
const optionsTemplate = useTemplateRef("options");
|
|
50
|
+
|
|
51
|
+
onMounted(() => {
|
|
52
|
+
if (optionsTemplate.value) {
|
|
53
|
+
optionsTemplate.value.querySelector(".selected")?.scrollIntoView({ block: "center" });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
async function select(option: TOption) {
|
|
58
|
+
emits("select", option);
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<style scoped>
|
|
63
|
+
.options {
|
|
64
|
+
overflow: hidden;
|
|
65
|
+
font-size: 0.8rem;
|
|
66
|
+
|
|
67
|
+
&.big {
|
|
68
|
+
font-size: .9rem;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
&.max-height {
|
|
72
|
+
max-height: 240px;
|
|
73
|
+
overflow-y: auto;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
|
2
|
+
|
|
3
|
+
export type TOptions = Array<string | number | TOption> | Record<string, string>;
|
|
4
|
+
|
|
5
|
+
export type TOption = {
|
|
6
|
+
action?: () => Promise<void> | void;
|
|
7
|
+
children?: Array<TOption>;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
icon?: {
|
|
10
|
+
color?: string;
|
|
11
|
+
icon: IconDefinition | string;
|
|
12
|
+
};
|
|
13
|
+
id: string;
|
|
14
|
+
value: string;
|
|
15
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="emoji-list">
|
|
3
|
+
<LEmojiListCategory
|
|
4
|
+
v-if="filter"
|
|
5
|
+
:big="big"
|
|
6
|
+
:category="result"
|
|
7
|
+
@select="$emit('select', $event)"
|
|
8
|
+
/>
|
|
9
|
+
<template v-else>
|
|
10
|
+
<LEmojiListCategory
|
|
11
|
+
v-for="category in emojiCategories"
|
|
12
|
+
:key="category.name"
|
|
13
|
+
:big="big"
|
|
14
|
+
:category="category"
|
|
15
|
+
@select="$emit('select', $event)"
|
|
16
|
+
/>
|
|
17
|
+
</template>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { computed } from "vue";
|
|
23
|
+
import { computedAsync } from "@vueuse/core";
|
|
24
|
+
import LEmojiListCategory from "@/components/form/emoji-picker/LEmojiListCategory.vue";
|
|
25
|
+
|
|
26
|
+
const props = defineProps<{
|
|
27
|
+
big?: boolean;
|
|
28
|
+
filter: string;
|
|
29
|
+
}>();
|
|
30
|
+
|
|
31
|
+
const emits = defineEmits<(e: "select", value: string) => void>();
|
|
32
|
+
|
|
33
|
+
const emojiCategories = computedAsync(async () => {
|
|
34
|
+
const { emojiCategories } = await import("symbol-db/emoji");
|
|
35
|
+
return emojiCategories;
|
|
36
|
+
}, {});
|
|
37
|
+
|
|
38
|
+
const allEmojis = computed(() => Object.values(emojiCategories.value).flatMap((category) => category.emojis));
|
|
39
|
+
|
|
40
|
+
const result = computed(() => ({
|
|
41
|
+
emojis: allEmojis.value.filter((emoji) => {
|
|
42
|
+
const searchWords = props.filter.split(" ");
|
|
43
|
+
return searchWords.every((searchWord) => emoji.search.includes(searchWord));
|
|
44
|
+
}).slice(0, 128)
|
|
45
|
+
}));
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<style scoped>
|
|
49
|
+
.emoji-list {
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
gap: var(--length-m);
|
|
53
|
+
}
|
|
54
|
+
</style>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="emoji-category"
|
|
4
|
+
:class="{big}"
|
|
5
|
+
>
|
|
6
|
+
<h2 v-if="category.name">
|
|
7
|
+
{{ category.name }}
|
|
8
|
+
</h2>
|
|
9
|
+
<div class="list">
|
|
10
|
+
<div
|
|
11
|
+
v-for="emoji in category.emojis"
|
|
12
|
+
:key="emoji.name"
|
|
13
|
+
class="emoji"
|
|
14
|
+
tabindex="0"
|
|
15
|
+
:title="emoji.name"
|
|
16
|
+
@click="$emit('select', emoji.emoji)"
|
|
17
|
+
@keydown.enter="$emit('select', emoji.emoji)"
|
|
18
|
+
>
|
|
19
|
+
{{ emoji.emoji }}
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup lang="ts">
|
|
26
|
+
import { TEmojiCategory } from "symbol-db/emoji";
|
|
27
|
+
|
|
28
|
+
const props = defineProps<{
|
|
29
|
+
big?: boolean;
|
|
30
|
+
category: TEmojiCategory;
|
|
31
|
+
}>();
|
|
32
|
+
|
|
33
|
+
const emits = defineEmits<(e: "select", value: string) => void>();
|
|
34
|
+
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<style scoped>
|
|
38
|
+
.emoji-category {
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
gap: var(--length-xxs);
|
|
42
|
+
|
|
43
|
+
h2 {
|
|
44
|
+
font-weight: 600;
|
|
45
|
+
font-size: 0.8rem;
|
|
46
|
+
padding: 0;
|
|
47
|
+
margin: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&.big h2 {
|
|
51
|
+
font-size: 1.2rem;
|
|
52
|
+
color: var(--color-content-lite);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.list {
|
|
56
|
+
display: grid;
|
|
57
|
+
grid-template-columns: repeat(auto-fill, minmax(24px, 1fr));
|
|
58
|
+
font-size: 1rem;
|
|
59
|
+
grid-gap: var(--length-xxxs);
|
|
60
|
+
|
|
61
|
+
.emoji {
|
|
62
|
+
display: flex;
|
|
63
|
+
justify-content: center;
|
|
64
|
+
align-items: center;
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
border-radius: var(--length-radius-m);
|
|
67
|
+
border: 2px solid transparent;
|
|
68
|
+
width: 24px;
|
|
69
|
+
height: 24px;
|
|
70
|
+
overflow: hidden;
|
|
71
|
+
|
|
72
|
+
&:hover {
|
|
73
|
+
background: var(--color-primary-dark);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
&:focus {
|
|
77
|
+
border: 2px dashed var(--color-primary);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&.big .list {
|
|
83
|
+
grid-template-columns: repeat(auto-fill, minmax(32px, 1fr));
|
|
84
|
+
font-size: 1.5rem;
|
|
85
|
+
|
|
86
|
+
.emoji {
|
|
87
|
+
width: 32px;
|
|
88
|
+
height: 32px;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Story
|
|
3
|
+
:layout="{
|
|
4
|
+
type: 'grid',
|
|
5
|
+
width: '320',
|
|
6
|
+
}"
|
|
7
|
+
>
|
|
8
|
+
<Variant title="big">
|
|
9
|
+
<LEmojiPicker
|
|
10
|
+
v-model="emoji"
|
|
11
|
+
big
|
|
12
|
+
/>
|
|
13
|
+
</Variant>
|
|
14
|
+
<Variant title="Small">
|
|
15
|
+
<LEmojiPicker
|
|
16
|
+
v-model="emoji"
|
|
17
|
+
small
|
|
18
|
+
/>
|
|
19
|
+
</Variant>
|
|
20
|
+
</Story>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import { ref } from "vue";
|
|
25
|
+
import LEmojiPicker from "@/components/form/emoji-picker/LEmojiPicker.vue";
|
|
26
|
+
|
|
27
|
+
const emoji = ref("");
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<style scoped>
|
|
31
|
+
|
|
32
|
+
</style>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<LFloating @show="focusSearch">
|
|
3
|
+
<LButton
|
|
4
|
+
:big="big"
|
|
5
|
+
class="button"
|
|
6
|
+
:class="{big}"
|
|
7
|
+
square
|
|
8
|
+
>
|
|
9
|
+
{{ modelValue }}
|
|
10
|
+
</LButton>
|
|
11
|
+
<template #popper>
|
|
12
|
+
<div class="popper-wrapper">
|
|
13
|
+
<LEmojiSelect
|
|
14
|
+
:big="big"
|
|
15
|
+
@select="$emit('update:modelValue', $event)"
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
</LFloating>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import { nextTick } from "vue";
|
|
24
|
+
import LFloating from "@/components/floating/LFloating.vue";
|
|
25
|
+
import LEmojiSelect from "@/components/form/emoji-picker/LEmojiSelect.vue";
|
|
26
|
+
import LButton from "@/components/form/LButton.vue";
|
|
27
|
+
|
|
28
|
+
const props = defineProps<{
|
|
29
|
+
big?: boolean;
|
|
30
|
+
modelValue: string;
|
|
31
|
+
}>();
|
|
32
|
+
|
|
33
|
+
const emits = defineEmits<(e: "update:modelValue", value: string) => void>();
|
|
34
|
+
|
|
35
|
+
async function focusSearch() {
|
|
36
|
+
await nextTick();
|
|
37
|
+
const input = document.querySelector(".popper-wrapper input") as HTMLInputElement;
|
|
38
|
+
input?.focus();
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<style scoped>
|
|
43
|
+
.button {
|
|
44
|
+
font-size: 1rem;
|
|
45
|
+
|
|
46
|
+
&.big {
|
|
47
|
+
font-size: 1.5rem;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.popper-wrapper {
|
|
52
|
+
padding: var(--length-s);
|
|
53
|
+
color: var(--color-content);
|
|
54
|
+
}
|
|
55
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Story
|
|
3
|
+
:layout="{
|
|
4
|
+
type: 'grid',
|
|
5
|
+
width: '320',
|
|
6
|
+
}"
|
|
7
|
+
>
|
|
8
|
+
<Variant title="big">
|
|
9
|
+
<LEmojiSelect big />
|
|
10
|
+
</Variant>
|
|
11
|
+
<Variant title="Small">
|
|
12
|
+
<LEmojiSelect />
|
|
13
|
+
</Variant>
|
|
14
|
+
</Story>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import LEmojiSelect from "@/components/form/emoji-picker/LEmojiSelect.vue";
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<style scoped>
|
|
22
|
+
</style>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="emoji-select"
|
|
4
|
+
:class="{big}"
|
|
5
|
+
>
|
|
6
|
+
<LInput
|
|
7
|
+
v-model="filter"
|
|
8
|
+
:big="big"
|
|
9
|
+
placeholder="Search emoji..."
|
|
10
|
+
/>
|
|
11
|
+
<LEmojiList
|
|
12
|
+
:big="big"
|
|
13
|
+
class="list"
|
|
14
|
+
:filter="filter"
|
|
15
|
+
@select="$emit('select', $event)"
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script setup lang="ts">
|
|
21
|
+
import { ref } from "vue";
|
|
22
|
+
import LInput from "@/components/form/LInput.vue";
|
|
23
|
+
import LEmojiList from "@/components/form/emoji-picker/LEmojiList.vue";
|
|
24
|
+
|
|
25
|
+
const filter = ref("");
|
|
26
|
+
|
|
27
|
+
const props = defineProps<{
|
|
28
|
+
big?: boolean;
|
|
29
|
+
}>();
|
|
30
|
+
|
|
31
|
+
const emits = defineEmits<(e: "select", value: string) => void>();
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<style scoped>
|
|
35
|
+
.emoji-select {
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
gap: var(--length-xs);
|
|
39
|
+
|
|
40
|
+
.list {
|
|
41
|
+
max-height: 200px;
|
|
42
|
+
overflow: auto;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&.big {
|
|
46
|
+
.list {
|
|
47
|
+
max-height: 280px;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
</style>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="icon-list"
|
|
4
|
+
:class="{big}"
|
|
5
|
+
>
|
|
6
|
+
<div
|
|
7
|
+
v-for="icon in filteredIcons"
|
|
8
|
+
:key="icon.name"
|
|
9
|
+
class="icon-wrapper"
|
|
10
|
+
tabindex="0"
|
|
11
|
+
:title="icon.name"
|
|
12
|
+
@click="$emit('select', icon.name)"
|
|
13
|
+
@keydown.enter="$emit('select', icon.name)"
|
|
14
|
+
>
|
|
15
|
+
<LIconMaterial
|
|
16
|
+
class="icon"
|
|
17
|
+
:icon="icon.name"
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import { computedAsync } from "@vueuse/core";
|
|
25
|
+
import { computed } from "vue";
|
|
26
|
+
|
|
27
|
+
import LIconMaterial from "@/components/form/icon-picker/LIconMaterial.vue";
|
|
28
|
+
|
|
29
|
+
const props = withDefaults(defineProps<{
|
|
30
|
+
big?: boolean;
|
|
31
|
+
filter: string;
|
|
32
|
+
}>(), {
|
|
33
|
+
filter: ""
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const emits = defineEmits<(e: "select", value: string) => void>();
|
|
37
|
+
|
|
38
|
+
const iconDb = computedAsync(async () => {
|
|
39
|
+
const { icons } = await import("symbol-db/material");
|
|
40
|
+
return icons;
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
const filteredIcons = computed(() => {
|
|
44
|
+
return iconDb.value.filter((icon) => {
|
|
45
|
+
const searchWords = props.filter.split(" ");
|
|
46
|
+
return searchWords.every((searchWord) => icon.search.includes(searchWord));
|
|
47
|
+
}).slice(0, 128);
|
|
48
|
+
});
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<style scoped>
|
|
52
|
+
.icon-list {
|
|
53
|
+
display: grid;
|
|
54
|
+
grid-template-columns: repeat(auto-fill, minmax(24px, 1fr));
|
|
55
|
+
grid-gap: var(--length-xxxs);
|
|
56
|
+
|
|
57
|
+
.icon-wrapper {
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
justify-content: center;
|
|
61
|
+
padding: var(--length-xxxs);
|
|
62
|
+
border-radius: var(--length-radius-m);
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
border: 2px solid transparent;
|
|
65
|
+
width: 24px;
|
|
66
|
+
height: 24px;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
|
|
69
|
+
.icon {
|
|
70
|
+
font-size: 16px;
|
|
71
|
+
width: 16px;
|
|
72
|
+
height: 16px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&:focus {
|
|
76
|
+
border: 2px dashed var(--color-primary);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
&:hover {
|
|
80
|
+
background-color: var(--color-primary-dark);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
&.big {
|
|
85
|
+
grid-template-columns: repeat(auto-fill, minmax(32px, 1fr));
|
|
86
|
+
padding: var(--length-xxs);
|
|
87
|
+
|
|
88
|
+
.icon-wrapper{
|
|
89
|
+
width: 32px;
|
|
90
|
+
height: 32px;
|
|
91
|
+
|
|
92
|
+
.icon {
|
|
93
|
+
font-size: 24px;
|
|
94
|
+
width: 24px;
|
|
95
|
+
height: 24px;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
</style>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span
|
|
3
|
+
class="icon"
|
|
4
|
+
:class="{spin}"
|
|
5
|
+
>
|
|
6
|
+
{{ icon }}
|
|
7
|
+
</span>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
icon: string;
|
|
13
|
+
spin?: boolean;
|
|
14
|
+
}>();
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<style scoped>
|
|
18
|
+
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20,400,1,-25');
|
|
19
|
+
|
|
20
|
+
.icon {
|
|
21
|
+
font-family: 'Material Symbols Rounded', sans-serif;
|
|
22
|
+
text-rendering: optimizelegibility;
|
|
23
|
+
font-feature-settings: "liga";
|
|
24
|
+
user-select: none;
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
|
|
29
|
+
&.spin {
|
|
30
|
+
animation: spin 2s linear infinite;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@keyframes spin {
|
|
35
|
+
0% {
|
|
36
|
+
transform: rotate(0deg);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
100% {
|
|
40
|
+
transform: rotate(360deg);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
</style>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Story
|
|
3
|
+
:layout="{
|
|
4
|
+
type: 'grid',
|
|
5
|
+
width: '320',
|
|
6
|
+
}"
|
|
7
|
+
>
|
|
8
|
+
<Variant title="big">
|
|
9
|
+
<LIconPicker
|
|
10
|
+
v-model="icon"
|
|
11
|
+
big
|
|
12
|
+
/>
|
|
13
|
+
</Variant>
|
|
14
|
+
<Variant title="Small">
|
|
15
|
+
<LIconPicker
|
|
16
|
+
v-model="icon"
|
|
17
|
+
small
|
|
18
|
+
/>
|
|
19
|
+
</Variant>
|
|
20
|
+
<Variant title="Small with-emoji">
|
|
21
|
+
<LIconPicker
|
|
22
|
+
v-model="icon"
|
|
23
|
+
small
|
|
24
|
+
with-emoji
|
|
25
|
+
/>
|
|
26
|
+
</Variant>
|
|
27
|
+
</Story>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup lang="ts">
|
|
31
|
+
import { ref } from "vue";
|
|
32
|
+
import LIconPicker from "@/components/form/icon-picker/LIconPicker.vue";
|
|
33
|
+
|
|
34
|
+
const icon = ref("");
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<style scoped>
|
|
38
|
+
|
|
39
|
+
</style>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<LFloating @show="focusSearch">
|
|
3
|
+
<LButton
|
|
4
|
+
:big="big"
|
|
5
|
+
:borderless="borderless"
|
|
6
|
+
class="button"
|
|
7
|
+
:class="{big}"
|
|
8
|
+
:small="small"
|
|
9
|
+
square
|
|
10
|
+
:title="value"
|
|
11
|
+
>
|
|
12
|
+
<div class="icon-wrapper">
|
|
13
|
+
<LIconMaterial
|
|
14
|
+
class="icon"
|
|
15
|
+
:icon="value"
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
</LButton>
|
|
19
|
+
<template #popper>
|
|
20
|
+
<div class="popper-wrapper">
|
|
21
|
+
<LIconSelect
|
|
22
|
+
:big="big"
|
|
23
|
+
:with-emoji="withEmoji"
|
|
24
|
+
@select="value = $event"
|
|
25
|
+
/>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
</LFloating>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script setup lang="ts">
|
|
32
|
+
import { nextTick } from "vue";
|
|
33
|
+
import LFloating from "@/components/floating/LFloating.vue";
|
|
34
|
+
import LButton from "@/components/form/LButton.vue";
|
|
35
|
+
import LIconSelect from "@/components/form/icon-picker/LIconSelect.vue";
|
|
36
|
+
import LIconMaterial from "@/components/form/icon-picker/LIconMaterial.vue";
|
|
37
|
+
|
|
38
|
+
const props = defineProps<{
|
|
39
|
+
big?: boolean;
|
|
40
|
+
borderless?: boolean;
|
|
41
|
+
small?: boolean;
|
|
42
|
+
withEmoji?: boolean;
|
|
43
|
+
}>();
|
|
44
|
+
|
|
45
|
+
const value = defineModel<string>({ default: "" });
|
|
46
|
+
|
|
47
|
+
async function focusSearch() {
|
|
48
|
+
await nextTick();
|
|
49
|
+
const input = document.querySelector(".popper-wrapper input") as HTMLInputElement;
|
|
50
|
+
input?.focus();
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<style scoped>
|
|
55
|
+
.button {
|
|
56
|
+
.icon-wrapper {
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
justify-content: center;
|
|
60
|
+
.icon {
|
|
61
|
+
font-size: 1rem;
|
|
62
|
+
width: 1rem;
|
|
63
|
+
height: 1rem;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
&.big {
|
|
68
|
+
.icon-wrapper {
|
|
69
|
+
.icon {
|
|
70
|
+
font-size: 1.5rem;
|
|
71
|
+
width: 1.5rem;
|
|
72
|
+
height: 1.5rem;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
&.small {
|
|
78
|
+
.icon-wrapper {
|
|
79
|
+
.icon {
|
|
80
|
+
font-size: 0.8rem;
|
|
81
|
+
width: 0.8rem;
|
|
82
|
+
height: 0.8rem;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.popper-wrapper {
|
|
89
|
+
padding: var(--length-s);
|
|
90
|
+
color: var(--color-content);
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Story
|
|
3
|
+
:layout="{
|
|
4
|
+
type: 'grid',
|
|
5
|
+
width: '320',
|
|
6
|
+
}"
|
|
7
|
+
>
|
|
8
|
+
<Variant title="big">
|
|
9
|
+
<LIconSelect big />
|
|
10
|
+
</Variant>
|
|
11
|
+
<Variant title="Small">
|
|
12
|
+
<LIconSelect />
|
|
13
|
+
</Variant>
|
|
14
|
+
<Variant title="Small with emoji">
|
|
15
|
+
<LIconSelect with-emoji />
|
|
16
|
+
</Variant>
|
|
17
|
+
</Story>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script setup lang="ts">
|
|
21
|
+
import LIconSelect from "@/components/form/icon-picker/LIconSelect.vue";
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<style scoped>
|
|
25
|
+
</style>
|