@liftkit-vue/core 0.2.0 → 0.2.2
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
- package/src/css/liftkit-tokens.css +10 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liftkit-vue/core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Material Design 3 inspired Vue 3 component library with dynamic theming and golden-ratio design tokens",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
},
|
|
37
37
|
"files": [
|
|
38
38
|
"dist",
|
|
39
|
-
"src/css"
|
|
39
|
+
"src/css",
|
|
40
|
+
"src/components"
|
|
40
41
|
],
|
|
41
42
|
"dependencies": {
|
|
42
43
|
"@material/material-color-utilities": "^0.3.0",
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { getOnToken } from '../utils/colorUtils'
|
|
4
|
+
|
|
5
|
+
interface LkBadgeProps {
|
|
6
|
+
icon?: string
|
|
7
|
+
color?: LkColorWithOnToken
|
|
8
|
+
scale?: 'md' | 'lg'
|
|
9
|
+
iconStrokeWidth?: number
|
|
10
|
+
scrim?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(defineProps<LkBadgeProps>(), {
|
|
14
|
+
icon: 'roller-coaster',
|
|
15
|
+
color: 'surface',
|
|
16
|
+
scale: 'md',
|
|
17
|
+
iconStrokeWidth: 0,
|
|
18
|
+
scrim: false,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
defineOptions({
|
|
22
|
+
inheritAttrs: false,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const iconColor = computed(() => getOnToken(props.color) as LkColor)
|
|
26
|
+
|
|
27
|
+
const defaultIconStrokeWidth = computed(() => {
|
|
28
|
+
switch (props.scale) {
|
|
29
|
+
case 'lg':
|
|
30
|
+
return 1
|
|
31
|
+
case 'md':
|
|
32
|
+
default:
|
|
33
|
+
return 1.5
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const resolvedStrokeWidth = computed(() =>
|
|
38
|
+
props.iconStrokeWidth || defaultIconStrokeWidth.value,
|
|
39
|
+
)
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<div
|
|
44
|
+
data-lk-component="badge"
|
|
45
|
+
:data-lk-badge-scale="props.scale"
|
|
46
|
+
:data-lk-badge-color="`lk-${props.color}`"
|
|
47
|
+
v-bind="$attrs"
|
|
48
|
+
>
|
|
49
|
+
<div data-lk-component="slot" data-lk-slot="icon">
|
|
50
|
+
<div data-lk-icon-element="wrapper">
|
|
51
|
+
<LkIcon
|
|
52
|
+
:name="props.icon"
|
|
53
|
+
:color="iconColor"
|
|
54
|
+
:stroke-width="resolvedStrokeWidth"
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div
|
|
59
|
+
v-if="props.scrim"
|
|
60
|
+
data-lk-component="badge-scrim"
|
|
61
|
+
:class="`bg-${iconColor}`"
|
|
62
|
+
></div>
|
|
63
|
+
</div>
|
|
64
|
+
</template>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { propsToDataAttrs } from '../utils/utilities'
|
|
4
|
+
import { getOnToken } from '../utils/colorUtils'
|
|
5
|
+
import LkStateLayer from './LkStateLayer.vue'
|
|
6
|
+
import LkIcon from './LkIcon.vue'
|
|
7
|
+
import type { LkStateLayerProps } from './LkStateLayer.vue'
|
|
8
|
+
|
|
9
|
+
export interface LkButtonProps {
|
|
10
|
+
label?: string
|
|
11
|
+
variant?: 'fill' | 'outline' | 'text'
|
|
12
|
+
color?: LkColorWithOnToken
|
|
13
|
+
size?: 'sm' | 'md' | 'lg'
|
|
14
|
+
material?: string
|
|
15
|
+
startIcon?: string
|
|
16
|
+
endIcon?: string
|
|
17
|
+
opticIconShift?: boolean
|
|
18
|
+
modifiers?: string
|
|
19
|
+
stateLayerOverride?: LkStateLayerProps
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const props = withDefaults(defineProps<LkButtonProps>(), {
|
|
23
|
+
label: 'Button',
|
|
24
|
+
variant: 'fill',
|
|
25
|
+
color: 'primary',
|
|
26
|
+
size: 'md',
|
|
27
|
+
opticIconShift: true,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
defineOptions({
|
|
31
|
+
inheritAttrs: false,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const lkButtonAttrs = computed(() =>
|
|
35
|
+
propsToDataAttrs(
|
|
36
|
+
{
|
|
37
|
+
variant: props.variant,
|
|
38
|
+
color: props.color,
|
|
39
|
+
size: props.size,
|
|
40
|
+
startIcon: props.startIcon,
|
|
41
|
+
endIcon: props.endIcon,
|
|
42
|
+
opticIconShift: props.opticIconShift,
|
|
43
|
+
},
|
|
44
|
+
'button',
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const onColorToken = computed(() => getOnToken(props.color) as LkColor)
|
|
49
|
+
|
|
50
|
+
const baseButtonClasses = computed(() => {
|
|
51
|
+
let classes = ''
|
|
52
|
+
switch (props.variant) {
|
|
53
|
+
case 'fill':
|
|
54
|
+
classes = `bg-${props.color} color-${onColorToken.value}`
|
|
55
|
+
break
|
|
56
|
+
case 'outline':
|
|
57
|
+
case 'text':
|
|
58
|
+
classes = `color-${props.color}`
|
|
59
|
+
break
|
|
60
|
+
default:
|
|
61
|
+
classes = `bg-${props.color} color-${onColorToken.value}`
|
|
62
|
+
break
|
|
63
|
+
}
|
|
64
|
+
if (props.modifiers) {
|
|
65
|
+
classes += ` ${props.modifiers}`
|
|
66
|
+
}
|
|
67
|
+
return classes
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const localStateLayerProps = computed(() => {
|
|
71
|
+
if (props.stateLayerOverride) {
|
|
72
|
+
return props.stateLayerOverride
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
bgColor: props.variant === 'fill' ? onColorToken.value : props.color,
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const iconColor = computed(() =>
|
|
80
|
+
props.variant === 'fill' ? onColorToken.value : props.color,
|
|
81
|
+
)
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<template>
|
|
85
|
+
<button
|
|
86
|
+
v-bind="{ ...$attrs, ...lkButtonAttrs }"
|
|
87
|
+
type="button"
|
|
88
|
+
data-lk-component="button"
|
|
89
|
+
:class="`${baseButtonClasses} ${modifiers || ''}`"
|
|
90
|
+
>
|
|
91
|
+
<div data-lk-button-content-wrap="true">
|
|
92
|
+
<div v-if="startIcon" data-lk-icon-position="start">
|
|
93
|
+
<LkIcon :name="startIcon" :color="iconColor" data-lk-icon-position="start" />
|
|
94
|
+
</div>
|
|
95
|
+
<span data-lk-button-child="button-text">{{ label ?? 'Button' }}</span>
|
|
96
|
+
<div v-if="endIcon" data-lk-icon-position="end">
|
|
97
|
+
<LkIcon :name="endIcon" :color="iconColor" data-lk-icon-position="end" />
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
<LkStateLayer v-bind="localStateLayerProps" />
|
|
101
|
+
</button>
|
|
102
|
+
</template>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useAttrs } from 'vue'
|
|
3
|
+
import { propsToDataAttrs } from '../utils/utilities'
|
|
4
|
+
|
|
5
|
+
export interface LkCardProps {
|
|
6
|
+
scaleFactor?: LkFontClass | 'none'
|
|
7
|
+
variant?: 'fill' | 'outline' | 'transparent'
|
|
8
|
+
material?: 'flat' | 'glass'
|
|
9
|
+
materialProps?: LkMatProps
|
|
10
|
+
opticalCorrection?: 'top' | 'left' | 'right' | 'bottom' | 'x' | 'y' | 'all' | 'none'
|
|
11
|
+
isClickable?: boolean
|
|
12
|
+
bgColor?: LkColorWithOnToken | 'transparent'
|
|
13
|
+
isScrollable?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<LkCardProps>(), {
|
|
17
|
+
scaleFactor: 'body',
|
|
18
|
+
variant: 'fill',
|
|
19
|
+
material: 'flat',
|
|
20
|
+
materialProps: () => ({}),
|
|
21
|
+
opticalCorrection: 'none',
|
|
22
|
+
isClickable: false,
|
|
23
|
+
isScrollable: false,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
defineOptions({
|
|
27
|
+
inheritAttrs: false,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const attrs = useAttrs()
|
|
31
|
+
|
|
32
|
+
const lkCardAttrs = computed(() =>
|
|
33
|
+
propsToDataAttrs(
|
|
34
|
+
{
|
|
35
|
+
scaleFactor: props.scaleFactor,
|
|
36
|
+
variant: props.variant,
|
|
37
|
+
material: props.material,
|
|
38
|
+
},
|
|
39
|
+
'card',
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const rootClass = computed(() => {
|
|
44
|
+
const classes: string[] = []
|
|
45
|
+
if (props.isClickable) classes.push('clickable')
|
|
46
|
+
return classes.join(' ')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const materialBgColor = computed(() => {
|
|
50
|
+
if (props.variant === 'fill') return props.bgColor
|
|
51
|
+
return 'transparent'
|
|
52
|
+
})
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<div
|
|
57
|
+
data-lk-component="card"
|
|
58
|
+
:class="rootClass"
|
|
59
|
+
v-bind="{ ...lkCardAttrs, ...attrs }"
|
|
60
|
+
>
|
|
61
|
+
<div
|
|
62
|
+
data-lk-card-element="padding-box"
|
|
63
|
+
:class="isScrollable ? 'overflow-auto' : ''"
|
|
64
|
+
:data-lk-card-optical-correction="opticalCorrection"
|
|
65
|
+
>
|
|
66
|
+
<div data-lk-component="slot" data-lk-slot="children">
|
|
67
|
+
<slot />
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
<!-- Glass material layer -->
|
|
71
|
+
<div
|
|
72
|
+
v-if="material === 'glass'"
|
|
73
|
+
data-lk-component="material-layer"
|
|
74
|
+
data-lk-material-layer-type="glass"
|
|
75
|
+
v-bind="propsToDataAttrs(materialProps as Record<string, unknown>, 'material')"
|
|
76
|
+
/>
|
|
77
|
+
<!-- Flat material layer -->
|
|
78
|
+
<div
|
|
79
|
+
v-if="material === 'flat'"
|
|
80
|
+
data-lk-component="material-layer"
|
|
81
|
+
data-lk-material-layer-type="flat"
|
|
82
|
+
:data-lk-material-bg-color="materialBgColor"
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { propsToDataAttrs } from '../utils/utilities'
|
|
4
|
+
|
|
5
|
+
interface LkColumnProps {
|
|
6
|
+
alignItems?: 'start' | 'center' | 'end' | 'stretch'
|
|
7
|
+
justifyContent?: 'start' | 'center' | 'end' | 'space-between' | 'space-around'
|
|
8
|
+
gap?: LkSizeUnit | 'none'
|
|
9
|
+
wrapChildren?: boolean
|
|
10
|
+
defaultChildBehavior?: 'auto-grow' | 'auto-shrink' | 'ignoreFlexRules' | 'ignoreIntrinsicSize'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(defineProps<LkColumnProps>(), {
|
|
14
|
+
alignItems: 'stretch',
|
|
15
|
+
justifyContent: 'start',
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
defineOptions({
|
|
19
|
+
inheritAttrs: false,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const dataAttrs = computed(() =>
|
|
23
|
+
propsToDataAttrs(
|
|
24
|
+
{
|
|
25
|
+
alignItems: props.alignItems,
|
|
26
|
+
justifyContent: props.justifyContent,
|
|
27
|
+
gap: props.gap,
|
|
28
|
+
wrapChildren: props.wrapChildren,
|
|
29
|
+
defaultChildBehavior: props.defaultChildBehavior,
|
|
30
|
+
},
|
|
31
|
+
'column',
|
|
32
|
+
),
|
|
33
|
+
)
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<div v-bind="{ ...$attrs, ...dataAttrs }" data-lk-component="column">
|
|
38
|
+
<slot />
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { propsToDataAttrs } from '../utils/utilities'
|
|
4
|
+
|
|
5
|
+
interface LkContainerProps {
|
|
6
|
+
maxWidth?: LkContainerWidth
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const props = withDefaults(defineProps<LkContainerProps>(), {
|
|
10
|
+
maxWidth: 'md',
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
defineOptions({
|
|
14
|
+
inheritAttrs: false,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const dataAttrs = computed(() =>
|
|
18
|
+
propsToDataAttrs({ maxWidth: props.maxWidth }, 'container'),
|
|
19
|
+
)
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<div v-bind="{ ...$attrs, ...dataAttrs }">
|
|
24
|
+
<slot />
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { provideDropdown } from '../../composables'
|
|
3
|
+
|
|
4
|
+
const { open, toggle, setOpen } = provideDropdown()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<div
|
|
9
|
+
data-lk-component="dropdown"
|
|
10
|
+
v-bind="$attrs"
|
|
11
|
+
>
|
|
12
|
+
<slot :is-open="open" :toggle="toggle" :close="() => setOpen(false)" />
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watch, onMounted, onBeforeUnmount } from 'vue'
|
|
3
|
+
import { useDropdown } from '../../composables'
|
|
4
|
+
import LkCard from '../LkCard.vue'
|
|
5
|
+
|
|
6
|
+
export interface LkDropdownMenuProps {
|
|
7
|
+
scaleFactor?: LkFontClass
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const props = withDefaults(defineProps<LkDropdownMenuProps>(), {
|
|
11
|
+
scaleFactor: 'body',
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const { open, triggerRef, contentRef } = useDropdown()
|
|
15
|
+
const localContentRef = ref<HTMLDivElement | null>(null)
|
|
16
|
+
const positionStyle = ref<Record<string, string>>({})
|
|
17
|
+
const triggerQuadrant = ref<string>('bottom-left')
|
|
18
|
+
|
|
19
|
+
function calculatePosition() {
|
|
20
|
+
if (!triggerRef.value) return
|
|
21
|
+
|
|
22
|
+
const rect = triggerRef.value.getBoundingClientRect()
|
|
23
|
+
const windowWidth = window.innerWidth
|
|
24
|
+
const windowHeight = window.innerHeight
|
|
25
|
+
|
|
26
|
+
const isTop = rect.top < windowHeight / 2
|
|
27
|
+
const isLeft = rect.left < windowWidth / 2
|
|
28
|
+
|
|
29
|
+
if (isTop) {
|
|
30
|
+
triggerQuadrant.value = isLeft ? 'bottom-left' : 'bottom-right'
|
|
31
|
+
} else {
|
|
32
|
+
triggerQuadrant.value = isLeft ? 'top-left' : 'top-right'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
switch (triggerQuadrant.value) {
|
|
36
|
+
case 'top-left':
|
|
37
|
+
positionStyle.value = {
|
|
38
|
+
top: `${rect.top + window.scrollY}px`,
|
|
39
|
+
left: `${rect.left + window.scrollX}px`,
|
|
40
|
+
}
|
|
41
|
+
break
|
|
42
|
+
case 'top-right':
|
|
43
|
+
positionStyle.value = {
|
|
44
|
+
top: `${rect.top + window.scrollY}px`,
|
|
45
|
+
left: `${rect.right + window.scrollX}px`,
|
|
46
|
+
}
|
|
47
|
+
break
|
|
48
|
+
case 'bottom-right':
|
|
49
|
+
positionStyle.value = {
|
|
50
|
+
top: `${rect.bottom + window.scrollY}px`,
|
|
51
|
+
left: `${rect.right + window.scrollX}px`,
|
|
52
|
+
}
|
|
53
|
+
break
|
|
54
|
+
case 'bottom-left':
|
|
55
|
+
default:
|
|
56
|
+
positionStyle.value = {
|
|
57
|
+
top: `${rect.bottom + window.scrollY}px`,
|
|
58
|
+
left: `${rect.left + window.scrollX}px`,
|
|
59
|
+
}
|
|
60
|
+
break
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
watch(open, (isOpen) => {
|
|
65
|
+
if (isOpen) {
|
|
66
|
+
calculatePosition()
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
onMounted(() => {
|
|
71
|
+
if (localContentRef.value) {
|
|
72
|
+
contentRef.value = localContentRef.value
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
onBeforeUnmount(() => {
|
|
77
|
+
contentRef.value = null
|
|
78
|
+
})
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<template>
|
|
82
|
+
<Teleport to="body">
|
|
83
|
+
<div
|
|
84
|
+
v-if="open && triggerRef"
|
|
85
|
+
ref="localContentRef"
|
|
86
|
+
:style="positionStyle"
|
|
87
|
+
role="menu"
|
|
88
|
+
data-lk-component="dropdown-menu"
|
|
89
|
+
:data-isactive="open"
|
|
90
|
+
:data-lk-dropdown-trigger-quadrant="triggerQuadrant"
|
|
91
|
+
>
|
|
92
|
+
<LkCard class="shadow-xl" :scale-factor="scaleFactor">
|
|
93
|
+
<div data-lk-component="column" data-lk-column-gap="none" :class="scaleFactor">
|
|
94
|
+
<slot />
|
|
95
|
+
</div>
|
|
96
|
+
</LkCard>
|
|
97
|
+
</div>
|
|
98
|
+
</Teleport>
|
|
99
|
+
</template>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useDropdown } from '../../composables'
|
|
3
|
+
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
|
4
|
+
|
|
5
|
+
const { open, toggle, triggerRef } = useDropdown()
|
|
6
|
+
const localRef = ref<HTMLElement | null>(null)
|
|
7
|
+
|
|
8
|
+
onMounted(() => {
|
|
9
|
+
// Assign the first child element as the trigger ref
|
|
10
|
+
if (localRef.value) {
|
|
11
|
+
const firstChild = localRef.value.firstElementChild as HTMLElement | null
|
|
12
|
+
if (firstChild) {
|
|
13
|
+
triggerRef.value = firstChild
|
|
14
|
+
} else {
|
|
15
|
+
triggerRef.value = localRef.value
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
onBeforeUnmount(() => {
|
|
21
|
+
triggerRef.value = null
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
function handleClick() {
|
|
25
|
+
toggle()
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<div
|
|
31
|
+
ref="localRef"
|
|
32
|
+
data-lk-component="dropdown-trigger"
|
|
33
|
+
:aria-expanded="open"
|
|
34
|
+
aria-haspopup="menu"
|
|
35
|
+
@click="handleClick"
|
|
36
|
+
>
|
|
37
|
+
<slot />
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useSlots } from 'vue'
|
|
3
|
+
import { propsToDataAttrs } from '../utils/utilities'
|
|
4
|
+
|
|
5
|
+
interface LkGridProps {
|
|
6
|
+
columns?: number
|
|
7
|
+
gap?: LkSizeUnit
|
|
8
|
+
autoResponsive?: boolean
|
|
9
|
+
class?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const props = withDefaults(defineProps<LkGridProps>(), {
|
|
13
|
+
columns: 2,
|
|
14
|
+
gap: 'md',
|
|
15
|
+
autoResponsive: false,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
defineOptions({
|
|
19
|
+
inheritAttrs: false,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const slots = useSlots()
|
|
23
|
+
|
|
24
|
+
const dataAttrs = computed(() =>
|
|
25
|
+
propsToDataAttrs(
|
|
26
|
+
{ autoResponsive: props.autoResponsive, gap: props.gap },
|
|
27
|
+
'grid',
|
|
28
|
+
),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const columnStyle = computed(() => {
|
|
32
|
+
if (!props.class) {
|
|
33
|
+
return { gridTemplateColumns: `repeat(${props.columns}, 1fr)` }
|
|
34
|
+
}
|
|
35
|
+
return {}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const hasSlotContent = computed(() => !!slots.default)
|
|
39
|
+
|
|
40
|
+
const placeholderCount = computed(() => {
|
|
41
|
+
if (hasSlotContent.value) return 0
|
|
42
|
+
return props.class ? props.columns : props.columns * 2
|
|
43
|
+
})
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<div
|
|
48
|
+
data-lk-component="grid"
|
|
49
|
+
v-bind="{ ...$attrs, ...dataAttrs }"
|
|
50
|
+
:class="props.class"
|
|
51
|
+
:style="columnStyle"
|
|
52
|
+
>
|
|
53
|
+
<slot />
|
|
54
|
+
<template v-if="!hasSlotContent">
|
|
55
|
+
<LkPlaceholderBlock v-for="i in placeholderCount" :key="i" />
|
|
56
|
+
</template>
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { propsToDataAttrs } from '../utils/utilities'
|
|
4
|
+
|
|
5
|
+
type LkHeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
|
6
|
+
|
|
7
|
+
interface LkHeadingProps {
|
|
8
|
+
tag?: LkHeadingTag
|
|
9
|
+
fontClass?: string
|
|
10
|
+
fontColor?: string
|
|
11
|
+
class?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<LkHeadingProps>(), {
|
|
15
|
+
tag: 'h2',
|
|
16
|
+
fontClass: 'display2-bold',
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
defineOptions({
|
|
20
|
+
inheritAttrs: false,
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const headingAttrs = computed(() => propsToDataAttrs({}, 'heading'))
|
|
24
|
+
|
|
25
|
+
const headingClass = computed(() => {
|
|
26
|
+
const classes: string[] = []
|
|
27
|
+
if (props.fontClass) classes.push(props.fontClass)
|
|
28
|
+
if (props.fontColor) classes.push(`color-${props.fontColor}`)
|
|
29
|
+
if (props.class) classes.push(props.class)
|
|
30
|
+
return classes.join(' ')
|
|
31
|
+
})
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<component
|
|
36
|
+
:is="props.tag"
|
|
37
|
+
data-lk-component="heading"
|
|
38
|
+
:class="headingClass"
|
|
39
|
+
v-bind="{ ...$attrs, ...headingAttrs }"
|
|
40
|
+
>
|
|
41
|
+
<slot />
|
|
42
|
+
</component>
|
|
43
|
+
</template>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface LkIconProps {
|
|
5
|
+
name?: string
|
|
6
|
+
fontClass?: Exclude<LkFontClass, `${string}-bold` | `${string}-mono`>
|
|
7
|
+
color?: LkColor | 'currentColor'
|
|
8
|
+
display?: 'block' | 'inline-block' | 'inline'
|
|
9
|
+
strokeWidth?: number
|
|
10
|
+
opticShift?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(defineProps<LkIconProps>(), {
|
|
14
|
+
name: 'roller-coaster',
|
|
15
|
+
color: 'onsurface',
|
|
16
|
+
strokeWidth: 2,
|
|
17
|
+
opticShift: false,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
defineOptions({
|
|
21
|
+
inheritAttrs: false,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const iconStyle = computed(() => ({
|
|
25
|
+
width: '1em',
|
|
26
|
+
height: '1em',
|
|
27
|
+
color: `var(--lk-${props.color})`,
|
|
28
|
+
strokeWidth: props.strokeWidth,
|
|
29
|
+
}))
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div
|
|
34
|
+
data-lk-component="icon"
|
|
35
|
+
:data-lk-icon-offset="props.opticShift"
|
|
36
|
+
:data-lk-icon-font-class="props.fontClass"
|
|
37
|
+
v-bind="$attrs"
|
|
38
|
+
>
|
|
39
|
+
<slot>
|
|
40
|
+
<!-- Default: render lucide icon via dynamic component or slot override -->
|
|
41
|
+
<svg
|
|
42
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
43
|
+
width="1em"
|
|
44
|
+
height="1em"
|
|
45
|
+
viewBox="0 0 24 24"
|
|
46
|
+
fill="none"
|
|
47
|
+
:stroke="`var(--lk-${props.color})`"
|
|
48
|
+
:stroke-width="props.strokeWidth"
|
|
49
|
+
stroke-linecap="round"
|
|
50
|
+
stroke-linejoin="round"
|
|
51
|
+
>
|
|
52
|
+
<!-- Icon content should be provided by lucide-vue-next or slot -->
|
|
53
|
+
</svg>
|
|
54
|
+
</slot>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|