@bobbykim/manguito-theme 0.4.0 → 0.5.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/index.ts +14 -2
- package/lib/collapse/Collapse.vue +45 -41
- package/lib/directives/index.ts +24 -3
- package/lib/modal/Modal.vue +188 -0
- package/lib/nav/HamburgerMenu.vue +121 -0
- package/lib/sidebar/Sidebar.vue +213 -0
- package/package.json +2 -2
- package/theme/index.ts +3 -3
- package/theme/theme.types.ts +6 -4
- package/themeVariables.cjs +1 -1
package/index.ts
CHANGED
|
@@ -9,7 +9,10 @@ import type { ClassType, InputType } from './theme/index.js'
|
|
|
9
9
|
import DropdownContainer from './lib/dropdown/DropdownContainer.vue'
|
|
10
10
|
import DropdownContent from './lib/dropdown/DropdownContent.vue'
|
|
11
11
|
import Collapse from './lib/collapse/Collapse.vue'
|
|
12
|
-
import
|
|
12
|
+
import Sidebar from './lib/sidebar/Sidebar.vue'
|
|
13
|
+
import Modal from './lib/modal/Modal.vue'
|
|
14
|
+
|
|
15
|
+
import HamburgerMenu from './lib/nav/HamburgerMenu.vue'
|
|
13
16
|
|
|
14
17
|
const generateClass = (type: ClassType, value: InputType): string => {
|
|
15
18
|
const mclGenerate = new GenerateMCLClass(type, value)
|
|
@@ -31,5 +34,14 @@ const generateClass = (type: ClassType, value: InputType): string => {
|
|
|
31
34
|
|
|
32
35
|
export default generateClass
|
|
33
36
|
|
|
34
|
-
export {
|
|
37
|
+
export {
|
|
38
|
+
DropdownContainer,
|
|
39
|
+
DropdownContent,
|
|
40
|
+
Collapse,
|
|
41
|
+
HamburgerMenu,
|
|
42
|
+
Sidebar,
|
|
43
|
+
Modal,
|
|
44
|
+
}
|
|
35
45
|
export * from './lib/directives'
|
|
46
|
+
export * from './theme/theme.types'
|
|
47
|
+
export * from './lib/collapse/index.types'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref,
|
|
2
|
+
import { ref, watch, Transition } from 'vue'
|
|
3
3
|
import type { CollapseEvent } from './index.types'
|
|
4
4
|
const props = withDefaults(
|
|
5
5
|
defineProps<{
|
|
@@ -15,43 +15,42 @@ const props = withDefaults(
|
|
|
15
15
|
)
|
|
16
16
|
const emit = defineEmits(['toggle'])
|
|
17
17
|
const toggle = ref<boolean>(props.visible)
|
|
18
|
-
const contentRef = ref<HTMLAreaElement>()
|
|
19
|
-
const slotHeight = ref<number>()
|
|
20
18
|
|
|
21
19
|
const handleToggleEvent = (e: Event): void => {
|
|
22
20
|
toggle.value = !toggle.value
|
|
23
21
|
emit('toggle', { ...e, visible: toggle.value } as CollapseEvent)
|
|
24
22
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @TransitionFunctions
|
|
26
|
+
* @param {HTMLElement} el - passed any since transition handlers don't accept HTMLElement (accepts Element)
|
|
27
|
+
*/
|
|
28
|
+
const onEnter = (el: any) => {
|
|
29
|
+
el.style.height = 'auto'
|
|
30
|
+
el.style.opacity = '0'
|
|
31
|
+
const endWidth = getComputedStyle(el).height
|
|
32
|
+
el.style.height = '0px'
|
|
33
|
+
el.offsetHeight // force repaint
|
|
34
|
+
el.style.height = endWidth
|
|
35
|
+
el.style.opacity = '1'
|
|
34
36
|
}
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const onAfterEnter = (el: any) => {
|
|
38
|
+
el.style.height = 'auto'
|
|
39
|
+
el.style.opacity = '1'
|
|
40
|
+
}
|
|
41
|
+
const onLeave = (el: any) => {
|
|
42
|
+
el.style.height = getComputedStyle(el).height
|
|
43
|
+
el.offsetHeight // force repaint
|
|
44
|
+
el.style.height = '0px'
|
|
45
|
+
el.style.opacity = '0'
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
watch(
|
|
41
49
|
() => props.visible,
|
|
42
50
|
(newValue) => {
|
|
43
51
|
toggle.value = newValue
|
|
44
52
|
}
|
|
45
53
|
)
|
|
46
|
-
// lifecycle hooks
|
|
47
|
-
onMounted(() => {
|
|
48
|
-
if (typeof window !== undefined) {
|
|
49
|
-
initObserver().observe(contentRef.value as Element)
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
onBeforeUnmount(() => {
|
|
53
|
-
initObserver().disconnect()
|
|
54
|
-
})
|
|
55
54
|
</script>
|
|
56
55
|
|
|
57
56
|
<template>
|
|
@@ -63,23 +62,28 @@ onBeforeUnmount(() => {
|
|
|
63
62
|
:accordion="accordion"
|
|
64
63
|
:visible="toggle"
|
|
65
64
|
></button>
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
<transition
|
|
66
|
+
name="collapse"
|
|
67
|
+
tag="div"
|
|
68
|
+
@enter="onEnter"
|
|
69
|
+
@after-enter="onAfterEnter"
|
|
70
|
+
@leave="onLeave"
|
|
71
|
+
class="overflow-hidden"
|
|
70
72
|
>
|
|
71
|
-
<div
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
toggle ? 'opacity-100 ease-in' : 'opacity-0 ease-out',
|
|
76
|
-
className,
|
|
77
|
-
]"
|
|
78
|
-
>
|
|
79
|
-
<slot />
|
|
73
|
+
<div v-show="toggle">
|
|
74
|
+
<div :class="[className]">
|
|
75
|
+
<slot />
|
|
76
|
+
</div>
|
|
80
77
|
</div>
|
|
81
|
-
</
|
|
78
|
+
</transition>
|
|
82
79
|
</div>
|
|
83
80
|
</template>
|
|
84
81
|
|
|
85
|
-
<style scoped
|
|
82
|
+
<style scoped lang="scss">
|
|
83
|
+
.collapse-enter-active,
|
|
84
|
+
.collapse-leave-active {
|
|
85
|
+
transition-property: height, opacity;
|
|
86
|
+
transition-duration: 500ms;
|
|
87
|
+
transition-timing-function: ease;
|
|
88
|
+
}
|
|
89
|
+
</style>
|
package/lib/directives/index.ts
CHANGED
|
@@ -14,6 +14,29 @@ export const vClickOutside: Directive = {
|
|
|
14
14
|
},
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export const vToggle: Directive = {
|
|
18
|
+
mounted(el, binding) {
|
|
19
|
+
el.__ClickToggleHandler = (e: Event): void => {
|
|
20
|
+
const eTarget = e.target as HTMLElement
|
|
21
|
+
let target: HTMLElement
|
|
22
|
+
if (eTarget.tagName === 'A') {
|
|
23
|
+
const targetId = eTarget.getAttribute('href')
|
|
24
|
+
const formattedTargetId =
|
|
25
|
+
targetId?.charAt(0) === '#' ? targetId.substring(1) : targetId
|
|
26
|
+
target = document.getElementById(formattedTargetId as string)!
|
|
27
|
+
} else {
|
|
28
|
+
target = document.getElementById(binding.arg as string)!
|
|
29
|
+
}
|
|
30
|
+
target.click()
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
el.addEventListener('click', el.__ClickToggleHandler)
|
|
34
|
+
},
|
|
35
|
+
unmounted(el) {
|
|
36
|
+
el.removeEventListener('click', el.__ClickToggleHandler)
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
17
40
|
export const vCollapse: Directive = {
|
|
18
41
|
mounted(el, binding) {
|
|
19
42
|
el.__HandleToggler = (event: Event): void => {
|
|
@@ -23,9 +46,7 @@ export const vCollapse: Directive = {
|
|
|
23
46
|
const targetId = eTarget.getAttribute('href')
|
|
24
47
|
const formattedTargetId =
|
|
25
48
|
targetId?.charAt(0) === '#' ? targetId.substring(1) : targetId
|
|
26
|
-
target = document.getElementById(
|
|
27
|
-
formattedTargetId as string
|
|
28
|
-
) as HTMLElement
|
|
49
|
+
target = document.getElementById(formattedTargetId as string)!
|
|
29
50
|
} else {
|
|
30
51
|
target = document.getElementById(binding.arg as string) as HTMLElement
|
|
31
52
|
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed, watch, Transition } from 'vue'
|
|
3
|
+
import generateClass, {
|
|
4
|
+
ColorPalette,
|
|
5
|
+
vClickOutside,
|
|
6
|
+
VerticalAlignment,
|
|
7
|
+
} from '../../'
|
|
8
|
+
|
|
9
|
+
const props = withDefaults(
|
|
10
|
+
defineProps<{
|
|
11
|
+
modalId: string
|
|
12
|
+
title?: string
|
|
13
|
+
titleColor?: ColorPalette
|
|
14
|
+
className?: string
|
|
15
|
+
visible?: boolean
|
|
16
|
+
noBackdrop?: boolean
|
|
17
|
+
noHeader?: boolean
|
|
18
|
+
color?: ColorPalette
|
|
19
|
+
backdropColor?: ColorPalette
|
|
20
|
+
placement?: VerticalAlignment
|
|
21
|
+
}>(),
|
|
22
|
+
{
|
|
23
|
+
titleColor: 'dark-3',
|
|
24
|
+
className: '',
|
|
25
|
+
visible: false,
|
|
26
|
+
noBackdrop: false,
|
|
27
|
+
noHeader: false,
|
|
28
|
+
color: 'light-1',
|
|
29
|
+
backdropColor: 'dark-4',
|
|
30
|
+
placement: 'center',
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const emit = defineEmits(['toggle', 'close'])
|
|
35
|
+
const toggle = ref<boolean>(props.visible)
|
|
36
|
+
const toggleComplete = ref<boolean>(false)
|
|
37
|
+
|
|
38
|
+
const handleToggleEvent = (e: Event): void => {
|
|
39
|
+
toggle.value = !toggle.value
|
|
40
|
+
emit('toggle', e)
|
|
41
|
+
}
|
|
42
|
+
const openModal = (): void => {
|
|
43
|
+
toggle.value = true
|
|
44
|
+
}
|
|
45
|
+
const closeModal = (e: Event): void => {
|
|
46
|
+
if (toggleComplete.value === true) {
|
|
47
|
+
toggle.value = false
|
|
48
|
+
emit('close', e)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const handlePlacementVar = computed(() => {
|
|
52
|
+
let placementVariable: number
|
|
53
|
+
if (props.placement === 'top') {
|
|
54
|
+
placementVariable = 25
|
|
55
|
+
} else if (props.placement === 'bottom') {
|
|
56
|
+
placementVariable = 75
|
|
57
|
+
} else {
|
|
58
|
+
placementVariable = 50
|
|
59
|
+
}
|
|
60
|
+
return { '--vertical-placement': placementVariable + '%' }
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @TransitionFunctions
|
|
65
|
+
* Handle toggleComplete value after completion of animation
|
|
66
|
+
*/
|
|
67
|
+
const onAfterEnter = () => {
|
|
68
|
+
toggleComplete.value = true
|
|
69
|
+
}
|
|
70
|
+
const onAfterLeave = () => {
|
|
71
|
+
toggleComplete.value = false
|
|
72
|
+
}
|
|
73
|
+
watch(
|
|
74
|
+
() => props.visible,
|
|
75
|
+
(newValue) => {
|
|
76
|
+
toggle.value = newValue
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
defineExpose({
|
|
80
|
+
toggle: handleToggleEvent,
|
|
81
|
+
close: closeModal,
|
|
82
|
+
open: openModal,
|
|
83
|
+
})
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<template>
|
|
87
|
+
<div :style="handlePlacementVar">
|
|
88
|
+
<button :id="modalId" @click="handleToggleEvent" class="hidden"></button>
|
|
89
|
+
<Transition name="fade" appear tag="div" v-if="!noBackdrop">
|
|
90
|
+
<section
|
|
91
|
+
v-if="toggle"
|
|
92
|
+
@click="closeModal"
|
|
93
|
+
class="fixed inset-0 overflow-y-auto bg-opacity-70 backdrop-blur z-[100]"
|
|
94
|
+
:class="generateClass('BGCOLOR', backdropColor)"
|
|
95
|
+
></section>
|
|
96
|
+
</Transition>
|
|
97
|
+
<Transition
|
|
98
|
+
name="slide-down"
|
|
99
|
+
tag="div"
|
|
100
|
+
@after-enter="onAfterEnter"
|
|
101
|
+
@after-leave="onAfterLeave"
|
|
102
|
+
appear
|
|
103
|
+
>
|
|
104
|
+
<div
|
|
105
|
+
v-if="toggle"
|
|
106
|
+
class="vertical-placement fixed w-full max-w-md z-[110] px-xs"
|
|
107
|
+
>
|
|
108
|
+
<div
|
|
109
|
+
v-click-outside="closeModal"
|
|
110
|
+
:class="[generateClass('BGCOLOR', color), className]"
|
|
111
|
+
class="relative overscroll-contain overflow-y-scroll max-h-[80vh] md:max-h-[60vh]"
|
|
112
|
+
>
|
|
113
|
+
<div v-if="!noHeader" class="sticky top-0">
|
|
114
|
+
<slot name="header" :close="closeModal" :status="toggle">
|
|
115
|
+
<div
|
|
116
|
+
class="flex justify-between items-center p-xs border-b-2"
|
|
117
|
+
:class="generateClass('BGCOLOR', color)"
|
|
118
|
+
>
|
|
119
|
+
<h3
|
|
120
|
+
v-if="title"
|
|
121
|
+
class="h3-md"
|
|
122
|
+
:class="generateClass('TEXTCOLOR', titleColor)"
|
|
123
|
+
>
|
|
124
|
+
{{ title }}
|
|
125
|
+
</h3>
|
|
126
|
+
<button @click="closeModal">
|
|
127
|
+
<svg
|
|
128
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
129
|
+
height="1em"
|
|
130
|
+
viewBox="0 0 384 512"
|
|
131
|
+
class="hover:opacity-75 focus:opacity-75 transition-opacity duration-300 ease-in h-sm"
|
|
132
|
+
:class="[generateClass('SVGFILL', titleColor)]"
|
|
133
|
+
>
|
|
134
|
+
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
|
135
|
+
<path
|
|
136
|
+
d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"
|
|
137
|
+
/>
|
|
138
|
+
</svg>
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
</slot>
|
|
142
|
+
</div>
|
|
143
|
+
<slot name="body" :close="closeModal" :status="toggle" />
|
|
144
|
+
<div class="sticky bottom-0">
|
|
145
|
+
<slot name="footer" :close="closeModal" :status="toggle" />
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</Transition>
|
|
150
|
+
</div>
|
|
151
|
+
</template>
|
|
152
|
+
|
|
153
|
+
<style scoped lang="scss">
|
|
154
|
+
.vertical-placement {
|
|
155
|
+
top: var(--vertical-placement);
|
|
156
|
+
left: 50%;
|
|
157
|
+
transform: translateY(calc(-1 * var(--vertical-placement))) translateX(-50%);
|
|
158
|
+
}
|
|
159
|
+
.fade-enter-active,
|
|
160
|
+
.fade-leave-active {
|
|
161
|
+
transition-property: opacity;
|
|
162
|
+
transition-duration: 300ms;
|
|
163
|
+
transition-timing-function: linear;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.fade-enter-from,
|
|
167
|
+
.fade-leave-to {
|
|
168
|
+
opacity: 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.slide-down-enter-active {
|
|
172
|
+
animation: slide-down 300ms ease;
|
|
173
|
+
}
|
|
174
|
+
.slide-down-leave-active {
|
|
175
|
+
animation: slide-down reverse 300ms ease;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@keyframes slide-down {
|
|
179
|
+
0% {
|
|
180
|
+
opacity: 0;
|
|
181
|
+
transform: translateY(-100%) translateX(-50%);
|
|
182
|
+
}
|
|
183
|
+
100% {
|
|
184
|
+
opacity: 1;
|
|
185
|
+
transform: translateY(calc(-1 * var(--vertical-placement))) translateX(-50%);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
</style>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { ColorPalette } from '@bobbykim/manguito-theme'
|
|
4
|
+
import generateClass from '../../index'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(
|
|
7
|
+
defineProps<{
|
|
8
|
+
color?: ColorPalette
|
|
9
|
+
displayBorder?: boolean
|
|
10
|
+
toggle: boolean
|
|
11
|
+
navColor: ColorPalette
|
|
12
|
+
}>(),
|
|
13
|
+
{
|
|
14
|
+
color: 'dark-2',
|
|
15
|
+
displayBorder: true,
|
|
16
|
+
}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
const emit = defineEmits(['hbg-click'])
|
|
20
|
+
|
|
21
|
+
const handleButtonToggle = (e: Event): void => {
|
|
22
|
+
emit('hbg-click', e)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const hamburgetButtonState = computed({
|
|
26
|
+
get() {
|
|
27
|
+
return props.toggle
|
|
28
|
+
},
|
|
29
|
+
set(value) {
|
|
30
|
+
return value
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const getBorderClass = (
|
|
35
|
+
bColor: ColorPalette,
|
|
36
|
+
border: boolean,
|
|
37
|
+
nColor: ColorPalette
|
|
38
|
+
): string => {
|
|
39
|
+
/**
|
|
40
|
+
* @bColor - color
|
|
41
|
+
* @border - displayBorder
|
|
42
|
+
* @nColor - navColor
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
const classArray: string[] = [
|
|
46
|
+
generateClass('RINGCOLOR', bColor),
|
|
47
|
+
generateClass('OFFSETRING', nColor),
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
if (border) {
|
|
51
|
+
classArray.push(generateClass('BORDER', bColor))
|
|
52
|
+
classArray.push('border')
|
|
53
|
+
}
|
|
54
|
+
return classArray.join(' ')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const getHamburgerButtonClass = (color: ColorPalette): string => {
|
|
58
|
+
/**
|
|
59
|
+
* @color - color
|
|
60
|
+
*/
|
|
61
|
+
const classArray: string[] = [
|
|
62
|
+
generateClass('BGCOLOR', color),
|
|
63
|
+
generateClass('BEFOREBG', color),
|
|
64
|
+
generateClass('AFTERBG', color),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
return classArray.join(' ')
|
|
68
|
+
}
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<template>
|
|
72
|
+
<button
|
|
73
|
+
class="p-3xs rounded-md aspect-square outline-none focus:ring-4 ring-offset-2 transition-all duration-300 ease-linear hover:opacity-70 focus:opacity-70 hamburger__button"
|
|
74
|
+
:class="getBorderClass(color, displayBorder, navColor)"
|
|
75
|
+
@click="handleButtonToggle"
|
|
76
|
+
>
|
|
77
|
+
<input
|
|
78
|
+
type="checkbox"
|
|
79
|
+
v-model="hamburgetButtonState"
|
|
80
|
+
class="hidden hamburger__checkbox"
|
|
81
|
+
/>
|
|
82
|
+
<div
|
|
83
|
+
class="relative h-[3px] w-sm hamburger__icon before:w-sm after:w-sm before:h-[3px] after:h-[3px] transition-all duration-300 ease-linear"
|
|
84
|
+
:class="getHamburgerButtonClass(color)"
|
|
85
|
+
></div>
|
|
86
|
+
</button>
|
|
87
|
+
</template>
|
|
88
|
+
|
|
89
|
+
<style lang="scss" scoped>
|
|
90
|
+
.hamburger__icon {
|
|
91
|
+
&::before,
|
|
92
|
+
&::after {
|
|
93
|
+
position: absolute;
|
|
94
|
+
left: 0;
|
|
95
|
+
transition: all 0.3s linear;
|
|
96
|
+
}
|
|
97
|
+
&::before {
|
|
98
|
+
top: -0.5rem;
|
|
99
|
+
}
|
|
100
|
+
&::after {
|
|
101
|
+
top: 0.5rem;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
.hamburger__button {
|
|
105
|
+
&:focus .hamburger__icon,
|
|
106
|
+
&:hover .hamburger__icon {
|
|
107
|
+
scale: 1.05;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
.hamburger__checkbox:checked + .hamburger__icon {
|
|
111
|
+
background-color: transparent;
|
|
112
|
+
}
|
|
113
|
+
.hamburger__checkbox:checked + .hamburger__icon::before {
|
|
114
|
+
top: 0;
|
|
115
|
+
transform: rotate(135deg);
|
|
116
|
+
}
|
|
117
|
+
.hamburger__checkbox:checked + .hamburger__icon::after {
|
|
118
|
+
top: 0;
|
|
119
|
+
transform: rotate(-135deg);
|
|
120
|
+
}
|
|
121
|
+
</style>
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed, watch, Transition } from 'vue'
|
|
3
|
+
import generateClass, { ColorPalette, vClickOutside, DirectionX } from '../../'
|
|
4
|
+
|
|
5
|
+
const props = withDefaults(
|
|
6
|
+
defineProps<{
|
|
7
|
+
sidebarId: string
|
|
8
|
+
title?: string
|
|
9
|
+
titleColor?: ColorPalette
|
|
10
|
+
className?: string
|
|
11
|
+
visible?: boolean
|
|
12
|
+
noBackdrop?: boolean
|
|
13
|
+
noHeader?: boolean
|
|
14
|
+
color?: ColorPalette
|
|
15
|
+
backdropColor?: ColorPalette
|
|
16
|
+
placement?: DirectionX
|
|
17
|
+
width?: string | number
|
|
18
|
+
}>(),
|
|
19
|
+
{
|
|
20
|
+
titleColor: 'dark-3',
|
|
21
|
+
className: '',
|
|
22
|
+
visible: false,
|
|
23
|
+
noBackdrop: false,
|
|
24
|
+
noHeader: false,
|
|
25
|
+
color: 'light-1',
|
|
26
|
+
backdropColor: 'dark-4',
|
|
27
|
+
placement: 'left',
|
|
28
|
+
width: 300,
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const emit = defineEmits(['toggle', 'close'])
|
|
33
|
+
const toggle = ref<boolean>(props.visible)
|
|
34
|
+
const toggleComplete = ref<boolean>(false)
|
|
35
|
+
const headerRef = ref<HTMLElement>()
|
|
36
|
+
const footerRef = ref<HTMLElement>()
|
|
37
|
+
|
|
38
|
+
const handleToggleEvent = (e: Event): void => {
|
|
39
|
+
toggle.value = !toggle.value
|
|
40
|
+
emit('toggle', e)
|
|
41
|
+
}
|
|
42
|
+
const openSidebar = (): void => {
|
|
43
|
+
toggle.value = true
|
|
44
|
+
}
|
|
45
|
+
const closeSidebar = (e: Event): void => {
|
|
46
|
+
if (toggleComplete.value === true) {
|
|
47
|
+
toggle.value = false
|
|
48
|
+
emit('close', e)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const handleStyleVariables = computed(() => {
|
|
52
|
+
return {
|
|
53
|
+
'--sidebar-width': `${props.width}px`,
|
|
54
|
+
'--header-height':
|
|
55
|
+
headerRef.value && toggle.value
|
|
56
|
+
? headerRef.value.scrollHeight + 'px'
|
|
57
|
+
: '0px',
|
|
58
|
+
'--footer-height':
|
|
59
|
+
footerRef.value && toggle.value
|
|
60
|
+
? footerRef.value.scrollHeight + 'px'
|
|
61
|
+
: '0px',
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
const sidebarClass = computed(() => {
|
|
65
|
+
return props.placement === 'left' ? 'sidebar-left' : 'sidebar-right'
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @TransitionFunctions
|
|
70
|
+
* Handle toggleComplete value after completion of animation
|
|
71
|
+
*/
|
|
72
|
+
const onAfterEnter = () => {
|
|
73
|
+
toggleComplete.value = true
|
|
74
|
+
}
|
|
75
|
+
const onAfterLeave = () => {
|
|
76
|
+
toggleComplete.value = false
|
|
77
|
+
}
|
|
78
|
+
watch(
|
|
79
|
+
() => props.visible,
|
|
80
|
+
(newValue) => {
|
|
81
|
+
toggle.value = newValue
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
defineExpose({
|
|
85
|
+
toggle: handleToggleEvent,
|
|
86
|
+
open: openSidebar,
|
|
87
|
+
close: closeSidebar,
|
|
88
|
+
})
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<template>
|
|
92
|
+
<div :style="handleStyleVariables">
|
|
93
|
+
<button :id="sidebarId" @click="handleToggleEvent" class="hidden"></button>
|
|
94
|
+
<Transition name="fade" appear tag="div" v-if="!noBackdrop">
|
|
95
|
+
<section
|
|
96
|
+
v-if="toggle"
|
|
97
|
+
@click="closeSidebar"
|
|
98
|
+
class="fixed inset-0 overflow-y-auto bg-opacity-70 backdrop-blur z-[100]"
|
|
99
|
+
:class="generateClass('BGCOLOR', backdropColor)"
|
|
100
|
+
></section>
|
|
101
|
+
</Transition>
|
|
102
|
+
<Transition
|
|
103
|
+
:name="sidebarClass"
|
|
104
|
+
@after-enter="onAfterEnter"
|
|
105
|
+
@after-leave="onAfterLeave"
|
|
106
|
+
appear
|
|
107
|
+
>
|
|
108
|
+
<div
|
|
109
|
+
v-if="toggle"
|
|
110
|
+
v-click-outside="closeSidebar"
|
|
111
|
+
class="sidebar-body fixed h-full overflow-hidden inset-y-0 shadow z-[110]"
|
|
112
|
+
:class="[placement === 'left' ? 'left-0' : 'right-0']"
|
|
113
|
+
>
|
|
114
|
+
<div
|
|
115
|
+
class="relative h-full overscroll-contain overflow-y-scroll"
|
|
116
|
+
:class="[generateClass('BGCOLOR', color), className]"
|
|
117
|
+
>
|
|
118
|
+
<div v-if="!noHeader" class="sticky top-0" ref="headerRef">
|
|
119
|
+
<slot name="header" :close="closeSidebar" :status="toggle">
|
|
120
|
+
<div
|
|
121
|
+
class="flex justify-between items-center p-xs"
|
|
122
|
+
:class="generateClass('BGCOLOR', color)"
|
|
123
|
+
>
|
|
124
|
+
<h3
|
|
125
|
+
v-if="title"
|
|
126
|
+
class="h3-md"
|
|
127
|
+
:class="generateClass('TEXTCOLOR', titleColor)"
|
|
128
|
+
>
|
|
129
|
+
{{ title }}
|
|
130
|
+
</h3>
|
|
131
|
+
<button @click="closeSidebar">
|
|
132
|
+
<svg
|
|
133
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
134
|
+
height="1em"
|
|
135
|
+
viewBox="0 0 384 512"
|
|
136
|
+
class="hover:opacity-75 focus:opacity-75 transition-opacity duration-300 ease-in h-sm"
|
|
137
|
+
:class="[generateClass('SVGFILL', titleColor)]"
|
|
138
|
+
>
|
|
139
|
+
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
|
140
|
+
<path
|
|
141
|
+
d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"
|
|
142
|
+
/>
|
|
143
|
+
</svg>
|
|
144
|
+
</button>
|
|
145
|
+
</div>
|
|
146
|
+
</slot>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<div class="sidebar-body-height">
|
|
150
|
+
<slot name="body" :close="closeSidebar" :status="toggle" />
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div class="sticky bottom-0" ref="footerRef">
|
|
154
|
+
<slot name="footer" :close="closeSidebar" :status="toggle" />
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</Transition>
|
|
159
|
+
</div>
|
|
160
|
+
</template>
|
|
161
|
+
|
|
162
|
+
<style scoped lang="scss">
|
|
163
|
+
.sidebar-body {
|
|
164
|
+
width: 100%;
|
|
165
|
+
}
|
|
166
|
+
.sidebar-body-height {
|
|
167
|
+
min-height: calc(100vh - var(--header-height) - var(--footer-height));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.fade-enter-active,
|
|
171
|
+
.fade-leave-active {
|
|
172
|
+
transition-property: opacity;
|
|
173
|
+
transition-duration: 300ms;
|
|
174
|
+
transition-timing-function: linear;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.fade-enter-from,
|
|
178
|
+
.fade-leave-to {
|
|
179
|
+
opacity: 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.sidebar-left-enter-active,
|
|
183
|
+
.sidebar-left-leave-active,
|
|
184
|
+
.sidebar-right-enter-active,
|
|
185
|
+
.sidebar-right-leave-active {
|
|
186
|
+
transition-property: transform, opacity;
|
|
187
|
+
transition-duration: 300ms;
|
|
188
|
+
transition-timing-function: linear;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.sidebar-left-enter-from,
|
|
192
|
+
.sidebar-left-leave-to {
|
|
193
|
+
transform: translateX(-100%);
|
|
194
|
+
}
|
|
195
|
+
.sidebar-right-enter-from,
|
|
196
|
+
.sidebar-right-leave-to {
|
|
197
|
+
transform: translateX(100%);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@media screen and (min-width: 768px) {
|
|
201
|
+
.sidebar-body {
|
|
202
|
+
width: var(--sidebar-width);
|
|
203
|
+
}
|
|
204
|
+
.sidebar-left-enter-from,
|
|
205
|
+
.sidebar-left-leave-to {
|
|
206
|
+
transform: translateX(calc(-1 * var(--sidebar-width)));
|
|
207
|
+
}
|
|
208
|
+
.sidebar-right-enter-from,
|
|
209
|
+
.sidebar-right-leave-to {
|
|
210
|
+
transform: translateX(var(--sidebar-width));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
</style>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobbykim/manguito-theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "main theme file for manguito components library. Other MCL components will need to import this file to run.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -17,5 +17,5 @@
|
|
|
17
17
|
"url": "https://github.com/bobbykim89/manguito-component-library.git"
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
|
-
"gitHead": "
|
|
20
|
+
"gitHead": "c095c399687fa416347eaec8a6d2003a39fc19ac"
|
|
21
21
|
}
|
package/theme/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
Range,
|
|
8
8
|
OpacityRange,
|
|
9
9
|
SpacingLevel,
|
|
10
|
-
|
|
10
|
+
Alignment,
|
|
11
11
|
} from './theme.types'
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -102,7 +102,7 @@ export type InputType =
|
|
|
102
102
|
| Range<1, 12>
|
|
103
103
|
| OpacityRange
|
|
104
104
|
| SpacingLevel
|
|
105
|
-
|
|
|
105
|
+
| Alignment
|
|
106
106
|
|
|
107
107
|
class GenerateMCLClass extends MCLTheme {
|
|
108
108
|
classType: ClassType
|
|
@@ -449,7 +449,7 @@ class GenerateMCLClass extends MCLTheme {
|
|
|
449
449
|
}
|
|
450
450
|
// handle text align classes
|
|
451
451
|
getTextAlignClass(): string {
|
|
452
|
-
return MCLTheme.textAlign[this.classValue as
|
|
452
|
+
return MCLTheme.textAlign[this.classValue as Alignment]
|
|
453
453
|
}
|
|
454
454
|
// handle border width classes
|
|
455
455
|
getBorderWidthClass(): string {
|
package/theme/theme.types.ts
CHANGED
|
@@ -37,12 +37,15 @@ export type HeadingSize = 'sm' | 'md' | 'lg' | 'xl'
|
|
|
37
37
|
export type BodyText = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
38
38
|
// Font weight
|
|
39
39
|
export type FontWeight = 'light' | 'normal' | 'bold'
|
|
40
|
-
//
|
|
41
|
-
export type
|
|
40
|
+
// alignment
|
|
41
|
+
export type Alignment = 'left' | 'center' | 'right'
|
|
42
|
+
export type VerticalAlignment = 'top' | 'center' | 'bottom'
|
|
42
43
|
// link targets
|
|
43
44
|
export type CtaTarget = '_self' | '_blank'
|
|
44
45
|
// Highlight positions
|
|
45
|
-
export type
|
|
46
|
+
export type Direction = 'left' | 'right' | 'top' | 'bottom'
|
|
47
|
+
export type DirectionY = 'top' | 'bottom'
|
|
48
|
+
export type DirectionX = 'left' | 'right'
|
|
46
49
|
// available ranges
|
|
47
50
|
type _RangeArray<
|
|
48
51
|
T extends number,
|
|
@@ -60,7 +63,6 @@ export type Range<T extends number, R extends number> = IntRange<
|
|
|
60
63
|
|
|
61
64
|
export type ButtonSize = 'small' | 'medium' | 'large'
|
|
62
65
|
export type OpacityRange = 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 100
|
|
63
|
-
export type Directions = 'left' | 'right'
|
|
64
66
|
export type ColumnWidth = 25 | 50 | 75 | 100
|
|
65
67
|
export type CrossOrigin = 'use-credentials' | 'anonymous'
|
|
66
68
|
export type InputType = 'text' | 'email' | 'password'
|
package/themeVariables.cjs
CHANGED
|
@@ -70,7 +70,7 @@ const mclTheme = plugin.withOptions(
|
|
|
70
70
|
},
|
|
71
71
|
},
|
|
72
72
|
[`.btn.btn-invert.btn-${e(key)}`]: {
|
|
73
|
-
[`@apply border-2 bg-
|
|
73
|
+
[`@apply border-2 bg-transparent border-${key} text-${key}`]: {},
|
|
74
74
|
'&:hover': {
|
|
75
75
|
[`@apply bg-${key} text-white bg-opacity-100`]: {},
|
|
76
76
|
},
|