@liftkit-vue/core 0.2.0 → 0.2.1
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/package.json +3 -2
- package/src/components/LkBadge.vue +64 -0
- package/src/components/LkButton.vue +102 -0
- package/src/components/LkCard.vue +85 -0
- package/src/components/LkColumn.vue +40 -0
- package/src/components/LkContainer.vue +26 -0
- package/src/components/LkDropdown/LkDropdown.vue +14 -0
- package/src/components/LkDropdown/LkDropdownMenu.vue +99 -0
- package/src/components/LkDropdown/LkDropdownTrigger.vue +39 -0
- package/src/components/LkDropdown/index.ts +3 -0
- package/src/components/LkGrid.vue +58 -0
- package/src/components/LkHeading.vue +43 -0
- package/src/components/LkIcon.vue +56 -0
- package/src/components/LkIconButton.vue +78 -0
- package/src/components/LkImage.vue +62 -0
- package/src/components/LkMaterialLayer.vue +147 -0
- package/src/components/LkMenuItem.vue +51 -0
- package/src/components/LkNavbar.vue +113 -0
- package/src/components/LkPlaceholderBlock.vue +9 -0
- package/src/components/LkRow.vue +40 -0
- package/src/components/LkSection.vue +45 -0
- package/src/components/LkSelect.vue +167 -0
- package/src/components/LkSelectMenu.vue +67 -0
- package/src/components/LkSelectOption.vue +56 -0
- package/src/components/LkSelectTrigger.vue +43 -0
- package/src/components/LkSnackbar.vue +127 -0
- package/src/components/LkStateLayer.vue +33 -0
- package/src/components/LkSticker.vue +42 -0
- package/src/components/LkSwitch.vue +90 -0
- package/src/components/LkTabContent.vue +33 -0
- package/src/components/LkTabLink.vue +50 -0
- package/src/components/LkTabMenu.vue +59 -0
- package/src/components/LkTabs.vue +65 -0
- package/src/components/LkText.vue +37 -0
- package/src/components/LkTextInput.vue +105 -0
- package/src/components/LkTheme.vue +49 -0
- package/src/components/LkThemeController.vue +396 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { InjectionKey, Ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
export interface LkSelectOption {
|
|
5
|
+
label: string
|
|
6
|
+
value: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface LkSelectContext {
|
|
10
|
+
open: Ref<boolean>
|
|
11
|
+
setOpen: (value: boolean) => void
|
|
12
|
+
toggle: () => void
|
|
13
|
+
triggerRef: Ref<HTMLElement | null>
|
|
14
|
+
contentRef: Ref<HTMLDivElement | null>
|
|
15
|
+
selectedValue: Ref<string>
|
|
16
|
+
setSelectedValue: (value: string) => void
|
|
17
|
+
options: Ref<LkSelectOption[]>
|
|
18
|
+
name: string | undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const SELECT_INJECTION_KEY: InjectionKey<LkSelectContext> = Symbol('liftkit-select')
|
|
22
|
+
|
|
23
|
+
export interface LkSelectProps {
|
|
24
|
+
modelValue?: string
|
|
25
|
+
options?: LkSelectOption[]
|
|
26
|
+
name?: string
|
|
27
|
+
label?: string
|
|
28
|
+
labelPosition?: 'default' | 'on-input'
|
|
29
|
+
helpText?: string
|
|
30
|
+
placeholderText?: string
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<script setup lang="ts">
|
|
35
|
+
import { ref, computed, watch, provide, onMounted, onBeforeUnmount } from 'vue'
|
|
36
|
+
|
|
37
|
+
const props = withDefaults(defineProps<LkSelectProps>(), {
|
|
38
|
+
modelValue: '',
|
|
39
|
+
options: () => [],
|
|
40
|
+
labelPosition: 'default',
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const emit = defineEmits<{
|
|
44
|
+
'update:modelValue': [value: string]
|
|
45
|
+
}>()
|
|
46
|
+
|
|
47
|
+
const open = ref(false)
|
|
48
|
+
const triggerRef = ref<HTMLElement | null>(null)
|
|
49
|
+
const contentRef = ref<HTMLDivElement | null>(null)
|
|
50
|
+
const hiddenSelectRef = ref<HTMLSelectElement | null>(null)
|
|
51
|
+
const selectedValue = ref(props.modelValue)
|
|
52
|
+
|
|
53
|
+
// Sync with v-model
|
|
54
|
+
watch(
|
|
55
|
+
() => props.modelValue,
|
|
56
|
+
(newVal) => {
|
|
57
|
+
selectedValue.value = newVal ?? ''
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
function setOpen(value: boolean) {
|
|
62
|
+
open.value = value
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function toggle() {
|
|
66
|
+
setOpen(!open.value)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function setSelectedValue(value: string) {
|
|
70
|
+
selectedValue.value = value
|
|
71
|
+
emit('update:modelValue', value)
|
|
72
|
+
setOpen(false)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Singleton registry for ensuring only one select open at a time
|
|
76
|
+
let registryInstance: { close: () => void } | null = null
|
|
77
|
+
|
|
78
|
+
watch(open, (isOpen) => {
|
|
79
|
+
if (isOpen) {
|
|
80
|
+
registryInstance = { close: () => setOpen(false) }
|
|
81
|
+
// Close other selects via a global event
|
|
82
|
+
window.dispatchEvent(new CustomEvent('lk-select-opened', { detail: registryInstance }))
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
function handleOtherSelectOpened(e: Event) {
|
|
87
|
+
const customEvent = e as CustomEvent
|
|
88
|
+
if (registryInstance && customEvent.detail !== registryInstance && open.value) {
|
|
89
|
+
setOpen(false)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Click outside detection
|
|
94
|
+
function handleClickOutside(e: MouseEvent) {
|
|
95
|
+
if (
|
|
96
|
+
contentRef.value &&
|
|
97
|
+
!contentRef.value.contains(e.target as Node) &&
|
|
98
|
+
triggerRef.value &&
|
|
99
|
+
!triggerRef.value.contains(e.target as Node)
|
|
100
|
+
) {
|
|
101
|
+
setOpen(false)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Keyboard navigation
|
|
106
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
107
|
+
if (e.key === 'Escape' && open.value) {
|
|
108
|
+
setOpen(false)
|
|
109
|
+
triggerRef.value?.focus()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
onMounted(() => {
|
|
114
|
+
document.addEventListener('mousedown', handleClickOutside)
|
|
115
|
+
document.addEventListener('keydown', handleKeyDown)
|
|
116
|
+
window.addEventListener('lk-select-opened', handleOtherSelectOpened)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
onBeforeUnmount(() => {
|
|
120
|
+
document.removeEventListener('mousedown', handleClickOutside)
|
|
121
|
+
document.removeEventListener('keydown', handleKeyDown)
|
|
122
|
+
window.removeEventListener('lk-select-opened', handleOtherSelectOpened)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const optionsRef = computed(() => props.options)
|
|
126
|
+
|
|
127
|
+
provide(SELECT_INJECTION_KEY, {
|
|
128
|
+
open,
|
|
129
|
+
setOpen,
|
|
130
|
+
toggle,
|
|
131
|
+
triggerRef,
|
|
132
|
+
contentRef,
|
|
133
|
+
selectedValue,
|
|
134
|
+
setSelectedValue,
|
|
135
|
+
options: optionsRef,
|
|
136
|
+
name: props.name,
|
|
137
|
+
})
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<template>
|
|
141
|
+
<div data-lk-component="select" v-bind="$attrs">
|
|
142
|
+
<!-- Hidden native select for form compatibility -->
|
|
143
|
+
<select
|
|
144
|
+
ref="hiddenSelectRef"
|
|
145
|
+
:name="name"
|
|
146
|
+
:value="selectedValue"
|
|
147
|
+
tabindex="-1"
|
|
148
|
+
aria-hidden="true"
|
|
149
|
+
style="position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; pointer-events: none;"
|
|
150
|
+
>
|
|
151
|
+
<option value="" disabled />
|
|
152
|
+
<option
|
|
153
|
+
v-for="option in options"
|
|
154
|
+
:key="option.value"
|
|
155
|
+
:value="option.value"
|
|
156
|
+
>
|
|
157
|
+
{{ option.label }}
|
|
158
|
+
</option>
|
|
159
|
+
</select>
|
|
160
|
+
<slot
|
|
161
|
+
:is-open="open"
|
|
162
|
+
:toggle="toggle"
|
|
163
|
+
:selected-value="selectedValue"
|
|
164
|
+
:set-selected-value="setSelectedValue"
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
</template>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watch, onMounted, onBeforeUnmount, inject } from 'vue'
|
|
3
|
+
import { SELECT_INJECTION_KEY, type LkSelectContext } from './LkSelect.vue'
|
|
4
|
+
import LkCard from './LkCard.vue'
|
|
5
|
+
|
|
6
|
+
export interface LkSelectMenuProps {
|
|
7
|
+
scaleFactor?: LkFontClass
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const props = withDefaults(defineProps<LkSelectMenuProps>(), {
|
|
11
|
+
scaleFactor: 'body',
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const ctx = inject<LkSelectContext>(SELECT_INJECTION_KEY)
|
|
15
|
+
if (!ctx) {
|
|
16
|
+
throw new Error('LkSelectMenu must be used inside an LkSelect component')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { open, triggerRef, contentRef } = ctx
|
|
20
|
+
const localContentRef = ref<HTMLDivElement | null>(null)
|
|
21
|
+
const positionStyle = ref<Record<string, string>>({})
|
|
22
|
+
|
|
23
|
+
function calculatePosition() {
|
|
24
|
+
if (!triggerRef.value) return
|
|
25
|
+
|
|
26
|
+
const rect = triggerRef.value.getBoundingClientRect()
|
|
27
|
+
positionStyle.value = {
|
|
28
|
+
top: `${rect.bottom + window.scrollY}px`,
|
|
29
|
+
left: `${rect.left + window.scrollX}px`,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
watch(open, (isOpen) => {
|
|
34
|
+
if (isOpen) {
|
|
35
|
+
calculatePosition()
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
onMounted(() => {
|
|
40
|
+
if (localContentRef.value) {
|
|
41
|
+
contentRef.value = localContentRef.value
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
onBeforeUnmount(() => {
|
|
46
|
+
contentRef.value = null
|
|
47
|
+
})
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<template>
|
|
51
|
+
<Teleport to="body">
|
|
52
|
+
<div
|
|
53
|
+
v-if="open && triggerRef"
|
|
54
|
+
ref="localContentRef"
|
|
55
|
+
:style="positionStyle"
|
|
56
|
+
role="menu"
|
|
57
|
+
data-lk-component="select-menu"
|
|
58
|
+
:data-isactive="open"
|
|
59
|
+
>
|
|
60
|
+
<LkCard class="shadow-xl" :scale-factor="scaleFactor">
|
|
61
|
+
<div data-lk-component="column" data-lk-column-gap="none" :class="scaleFactor">
|
|
62
|
+
<slot />
|
|
63
|
+
</div>
|
|
64
|
+
</LkCard>
|
|
65
|
+
</div>
|
|
66
|
+
</Teleport>
|
|
67
|
+
</template>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, inject } from 'vue'
|
|
3
|
+
import { SELECT_INJECTION_KEY, type LkSelectContext } from './LkSelect.vue'
|
|
4
|
+
|
|
5
|
+
export interface LkSelectOptionProps {
|
|
6
|
+
value: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const props = defineProps<LkSelectOptionProps>()
|
|
10
|
+
|
|
11
|
+
const ctx = inject<LkSelectContext>(SELECT_INJECTION_KEY)
|
|
12
|
+
if (!ctx) {
|
|
13
|
+
throw new Error('LkSelectOption must be used inside an LkSelect component')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { selectedValue, setSelectedValue } = ctx
|
|
17
|
+
|
|
18
|
+
const isSelected = computed(() => selectedValue.value === props.value)
|
|
19
|
+
|
|
20
|
+
function handleClick() {
|
|
21
|
+
setSelectedValue(props.value)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
25
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
26
|
+
e.preventDefault()
|
|
27
|
+
handleClick()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div
|
|
34
|
+
role="menuitem"
|
|
35
|
+
data-lk-component="menu-item"
|
|
36
|
+
:tabindex="0"
|
|
37
|
+
:data-selected="isSelected"
|
|
38
|
+
:style="{
|
|
39
|
+
cursor: 'pointer',
|
|
40
|
+
fontWeight: isSelected ? 'bold' : 'normal',
|
|
41
|
+
}"
|
|
42
|
+
class="select-option"
|
|
43
|
+
@click="handleClick"
|
|
44
|
+
@keydown="handleKeyDown"
|
|
45
|
+
>
|
|
46
|
+
<slot name="startIcon" />
|
|
47
|
+
<p data-lk-menu-item-element="content-wrap">
|
|
48
|
+
<slot />
|
|
49
|
+
</p>
|
|
50
|
+
<slot name="endIcon" />
|
|
51
|
+
<div
|
|
52
|
+
data-lk-component="state-layer"
|
|
53
|
+
:data-lk-state-layer-forced-state="isSelected ? 'active' : undefined"
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { inject, ref, onMounted, onBeforeUnmount } from 'vue'
|
|
3
|
+
import { SELECT_INJECTION_KEY, type LkSelectContext } from './LkSelect.vue'
|
|
4
|
+
|
|
5
|
+
const ctx = inject<LkSelectContext>(SELECT_INJECTION_KEY)
|
|
6
|
+
if (!ctx) {
|
|
7
|
+
throw new Error('LkSelectTrigger must be used inside an LkSelect component')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { open, toggle, triggerRef } = ctx
|
|
11
|
+
const localRef = ref<HTMLElement | null>(null)
|
|
12
|
+
|
|
13
|
+
onMounted(() => {
|
|
14
|
+
if (localRef.value) {
|
|
15
|
+
const firstChild = localRef.value.firstElementChild as HTMLElement | null
|
|
16
|
+
if (firstChild) {
|
|
17
|
+
triggerRef.value = firstChild
|
|
18
|
+
} else {
|
|
19
|
+
triggerRef.value = localRef.value
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
onBeforeUnmount(() => {
|
|
25
|
+
triggerRef.value = null
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
function handleClick() {
|
|
29
|
+
toggle()
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div
|
|
35
|
+
ref="localRef"
|
|
36
|
+
data-lk-component="select-trigger"
|
|
37
|
+
:aria-expanded="open"
|
|
38
|
+
aria-haspopup="menu"
|
|
39
|
+
@click="handleClick"
|
|
40
|
+
>
|
|
41
|
+
<slot />
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, watch, onMounted, onUnmounted, useAttrs } from 'vue'
|
|
3
|
+
import { propsToDataAttrs } from '../utils/utilities'
|
|
4
|
+
import { getOnToken } from '../utils/colorUtils'
|
|
5
|
+
import LkCard from './LkCard.vue'
|
|
6
|
+
|
|
7
|
+
export interface LkSnackbarProps {
|
|
8
|
+
globalColor?: LkColorWithOnToken
|
|
9
|
+
message?: string
|
|
10
|
+
modelValue?: boolean
|
|
11
|
+
autoDismiss?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<LkSnackbarProps>(), {
|
|
15
|
+
message: 'Notification text goes here.',
|
|
16
|
+
modelValue: true,
|
|
17
|
+
autoDismiss: 0,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const emit = defineEmits<{
|
|
21
|
+
'update:modelValue': [value: boolean]
|
|
22
|
+
}>()
|
|
23
|
+
|
|
24
|
+
defineOptions({
|
|
25
|
+
inheritAttrs: false,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const attrs = useAttrs()
|
|
29
|
+
|
|
30
|
+
const isVisible = ref(props.modelValue)
|
|
31
|
+
let dismissTimer: ReturnType<typeof setTimeout> | null = null
|
|
32
|
+
|
|
33
|
+
watch(
|
|
34
|
+
() => props.modelValue,
|
|
35
|
+
(newVal) => {
|
|
36
|
+
isVisible.value = newVal
|
|
37
|
+
if (newVal && props.autoDismiss > 0) {
|
|
38
|
+
startDismissTimer()
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
function startDismissTimer() {
|
|
44
|
+
clearDismissTimer()
|
|
45
|
+
if (props.autoDismiss > 0) {
|
|
46
|
+
dismissTimer = setTimeout(() => {
|
|
47
|
+
isVisible.value = false
|
|
48
|
+
emit('update:modelValue', false)
|
|
49
|
+
}, props.autoDismiss)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function clearDismissTimer() {
|
|
54
|
+
if (dismissTimer) {
|
|
55
|
+
clearTimeout(dismissTimer)
|
|
56
|
+
dismissTimer = null
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
onMounted(() => {
|
|
61
|
+
if (isVisible.value && props.autoDismiss > 0) {
|
|
62
|
+
startDismissTimer()
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
onUnmounted(() => {
|
|
67
|
+
clearDismissTimer()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const dataAttrs = computed(() =>
|
|
71
|
+
propsToDataAttrs(
|
|
72
|
+
{
|
|
73
|
+
globalColor: props.globalColor,
|
|
74
|
+
message: props.message,
|
|
75
|
+
},
|
|
76
|
+
'snackbar',
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
const onColorToken = computed(() => {
|
|
81
|
+
if (!props.globalColor) return undefined
|
|
82
|
+
return getOnToken(props.globalColor as LkColor)
|
|
83
|
+
})
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<template>
|
|
87
|
+
<Teleport to="body">
|
|
88
|
+
<div
|
|
89
|
+
v-if="isVisible"
|
|
90
|
+
data-lk-component="snackbar"
|
|
91
|
+
v-bind="{ ...dataAttrs, ...attrs }"
|
|
92
|
+
>
|
|
93
|
+
<LkCard
|
|
94
|
+
:scale-factor="$slots.icon ? 'subheading' : 'body'"
|
|
95
|
+
:bg-color="globalColor"
|
|
96
|
+
:optical-correction="$slots.icon ? 'none' : 'y'"
|
|
97
|
+
class="shadow-sm"
|
|
98
|
+
>
|
|
99
|
+
<div data-lk-component="row" data-lk-row-align-items="center">
|
|
100
|
+
<!-- Icon slot -->
|
|
101
|
+
<div v-if="$slots.icon" data-lk-slot="snackbar-icon">
|
|
102
|
+
<slot name="icon" :on-color="onColorToken" />
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<!-- Message / default content slot -->
|
|
106
|
+
<div data-lk-slot="snackbar-text">
|
|
107
|
+
<slot :on-color="onColorToken" :global-color="globalColor">
|
|
108
|
+
<p>{{ message }}</p>
|
|
109
|
+
</slot>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div data-lk-component="row">
|
|
113
|
+
<!-- Action buttons slot -->
|
|
114
|
+
<div v-if="$slots.action" data-lk-slot="snackbar-actions">
|
|
115
|
+
<slot name="action" :on-color="onColorToken" :global-color="globalColor" />
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<!-- Icon action buttons (dismiss etc.) slot -->
|
|
119
|
+
<div v-if="$slots.iconAction" data-lk-slot="snackbar-icon-actions">
|
|
120
|
+
<slot name="iconAction" :on-color="onColorToken" :global-color="globalColor" />
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</LkCard>
|
|
125
|
+
</div>
|
|
126
|
+
</Teleport>
|
|
127
|
+
</template>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface LkStateLayerProps {
|
|
5
|
+
bgColor?: LkColor | 'currentColor'
|
|
6
|
+
forcedState?: 'hover' | 'active' | 'focus'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const props = withDefaults(defineProps<LkStateLayerProps>(), {
|
|
10
|
+
bgColor: 'currentColor',
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
defineOptions({
|
|
14
|
+
inheritAttrs: false,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const layerClass = computed(() =>
|
|
18
|
+
props.bgColor !== 'currentColor' ? `bg-${props.bgColor}` : '',
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const layerStyle = computed(() =>
|
|
22
|
+
props.bgColor === 'currentColor' ? { backgroundColor: 'currentColor' } : {},
|
|
23
|
+
)
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<div
|
|
28
|
+
data-lk-component="state-layer"
|
|
29
|
+
:class="layerClass"
|
|
30
|
+
:style="layerStyle"
|
|
31
|
+
v-bind="props.forcedState ? { 'data-lk-forced-state': props.forcedState } : {}"
|
|
32
|
+
></div>
|
|
33
|
+
</template>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { getOnToken } from '../utils/colorUtils'
|
|
4
|
+
|
|
5
|
+
interface LkStickerProps {
|
|
6
|
+
fontClass?: LkFontClass
|
|
7
|
+
bgColor?: LkColor
|
|
8
|
+
class?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(defineProps<LkStickerProps>(), {
|
|
12
|
+
fontClass: 'label',
|
|
13
|
+
bgColor: 'primarycontainer',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
defineOptions({
|
|
17
|
+
inheritAttrs: false,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const onColor = computed(() => getOnToken(props.bgColor))
|
|
21
|
+
|
|
22
|
+
const stickerClass = computed(() => {
|
|
23
|
+
const classes: string[] = [
|
|
24
|
+
`bg-${props.bgColor}`,
|
|
25
|
+
`color-${onColor.value}`,
|
|
26
|
+
]
|
|
27
|
+
if (props.class) classes.push(props.class)
|
|
28
|
+
return classes.join(' ')
|
|
29
|
+
})
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div
|
|
34
|
+
data-lk-component="sticker"
|
|
35
|
+
:class="stickerClass"
|
|
36
|
+
v-bind="$attrs"
|
|
37
|
+
>
|
|
38
|
+
<LkText :font-class="props.fontClass">
|
|
39
|
+
<slot>Sticker</slot>
|
|
40
|
+
</LkText>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { getOnToken } from '../utils/colorUtils'
|
|
4
|
+
|
|
5
|
+
export interface LkSwitchProps {
|
|
6
|
+
modelValue?: boolean
|
|
7
|
+
offColor?: LkColorWithOnToken
|
|
8
|
+
onColor?: LkColorWithOnToken
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(defineProps<LkSwitchProps>(), {
|
|
12
|
+
modelValue: false,
|
|
13
|
+
offColor: 'surfacevariant',
|
|
14
|
+
onColor: 'primary',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const emit = defineEmits<{
|
|
18
|
+
'update:modelValue': [value: boolean]
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
defineOptions({
|
|
22
|
+
inheritAttrs: false,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const switchThumbOffColor = computed(() => getOnToken(props.offColor))
|
|
26
|
+
const switchThumbOnColor = computed(() => getOnToken(props.onColor))
|
|
27
|
+
|
|
28
|
+
function handleClick() {
|
|
29
|
+
emit('update:modelValue', !props.modelValue)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const switchState = computed(() => (props.modelValue ? 'on' : 'off'))
|
|
33
|
+
|
|
34
|
+
const switchStyles = computed(() => ({
|
|
35
|
+
'--switch-off-color': `var(--lk-${props.offColor})`,
|
|
36
|
+
'--switch-on-color': `var(--lk-${props.onColor})`,
|
|
37
|
+
'--switch-thumb-off-color': `var(--lk-${switchThumbOffColor.value})`,
|
|
38
|
+
'--switch-thumb-on-color': `var(--lk-${switchThumbOnColor.value})`,
|
|
39
|
+
}))
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<div
|
|
44
|
+
data-lk-component="switch"
|
|
45
|
+
v-bind="{ ...$attrs, style: switchStyles }"
|
|
46
|
+
:data-lk-switch-state="switchState"
|
|
47
|
+
@click="handleClick"
|
|
48
|
+
>
|
|
49
|
+
<div
|
|
50
|
+
data-lk-component="switch-thumb"
|
|
51
|
+
:data-lk-switch-state="switchState"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<style scoped>
|
|
57
|
+
[data-lk-component="switch"] {
|
|
58
|
+
position: relative;
|
|
59
|
+
display: block;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: start;
|
|
62
|
+
width: var(--lk-size-xl);
|
|
63
|
+
height: calc(var(--lk-size-md) + calc(var(--lk-size-2xs) * 2));
|
|
64
|
+
background-color: var(--switch-off-color);
|
|
65
|
+
border-radius: 100em;
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
[data-lk-component="switch"][data-lk-switch-state="on"] {
|
|
70
|
+
background-color: var(--switch-on-color);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
[data-lk-component="switch-thumb"] {
|
|
74
|
+
position: absolute;
|
|
75
|
+
top: 50%;
|
|
76
|
+
left: var(--lk-size-2xs);
|
|
77
|
+
right: auto;
|
|
78
|
+
width: var(--lk-size-md);
|
|
79
|
+
height: var(--lk-size-md);
|
|
80
|
+
border-radius: 50%;
|
|
81
|
+
background-color: var(--switch-thumb-off-color);
|
|
82
|
+
transform: translateY(-50%);
|
|
83
|
+
transition: left 0.1s ease-out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
[data-lk-component="switch-thumb"][data-lk-switch-state="on"] {
|
|
87
|
+
left: calc(100% - calc(var(--lk-size-md) + var(--lk-size-2xs)));
|
|
88
|
+
background-color: var(--switch-thumb-on-color);
|
|
89
|
+
}
|
|
90
|
+
</style>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, inject } from 'vue'
|
|
3
|
+
import type { Ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
export interface LkTabContentProps {
|
|
6
|
+
index: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const props = defineProps<LkTabContentProps>()
|
|
10
|
+
|
|
11
|
+
defineOptions({
|
|
12
|
+
inheritAttrs: false,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const tabsContext = inject<{ activeTab: Ref<number> } | null>('lk-tabs', null)
|
|
16
|
+
|
|
17
|
+
const isVisible = computed(() => {
|
|
18
|
+
if (tabsContext) {
|
|
19
|
+
return tabsContext.activeTab.value === props.index
|
|
20
|
+
}
|
|
21
|
+
return true
|
|
22
|
+
})
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<div
|
|
27
|
+
v-show="isVisible"
|
|
28
|
+
data-lk-component="tab-content"
|
|
29
|
+
v-bind="$attrs"
|
|
30
|
+
>
|
|
31
|
+
<slot />
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|