@bobbykim/manguito-theme 0.3.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 CHANGED
@@ -5,9 +5,16 @@ import GenerateMCLClass, {
5
5
  scaleType,
6
6
  } from './theme'
7
7
  import type { ClassType, InputType } from './theme/index.js'
8
+ // base components
9
+ import DropdownContainer from './lib/dropdown/DropdownContainer.vue'
10
+ import DropdownContent from './lib/dropdown/DropdownContent.vue'
11
+ import Collapse from './lib/collapse/Collapse.vue'
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'
8
16
 
9
17
  const generateClass = (type: ClassType, value: InputType): string => {
10
- console.log('running generate class')
11
18
  const mclGenerate = new GenerateMCLClass(type, value)
12
19
  if ((<string[]>(<unknown>colorType)).includes(type)) {
13
20
  return mclGenerate.generateColorClass()
@@ -26,3 +33,15 @@ const generateClass = (type: ClassType, value: InputType): string => {
26
33
  }
27
34
 
28
35
  export default generateClass
36
+
37
+ export {
38
+ DropdownContainer,
39
+ DropdownContent,
40
+ Collapse,
41
+ HamburgerMenu,
42
+ Sidebar,
43
+ Modal,
44
+ }
45
+ export * from './lib/directives'
46
+ export * from './theme/theme.types'
47
+ export * from './lib/collapse/index.types'
@@ -0,0 +1,89 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch, Transition } from 'vue'
3
+ import type { CollapseEvent } from './index.types'
4
+ const props = withDefaults(
5
+ defineProps<{
6
+ collapseId: string
7
+ accordion?: string
8
+ className?: string
9
+ visible?: boolean
10
+ }>(),
11
+ {
12
+ className: '',
13
+ visible: false,
14
+ }
15
+ )
16
+ const emit = defineEmits(['toggle'])
17
+ const toggle = ref<boolean>(props.visible)
18
+
19
+ const handleToggleEvent = (e: Event): void => {
20
+ toggle.value = !toggle.value
21
+ emit('toggle', { ...e, visible: toggle.value } as CollapseEvent)
22
+ }
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'
36
+ }
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
+
48
+ watch(
49
+ () => props.visible,
50
+ (newValue) => {
51
+ toggle.value = newValue
52
+ }
53
+ )
54
+ </script>
55
+
56
+ <template>
57
+ <div>
58
+ <button
59
+ :id="collapseId"
60
+ @click="handleToggleEvent"
61
+ class="hidden"
62
+ :accordion="accordion"
63
+ :visible="toggle"
64
+ ></button>
65
+ <transition
66
+ name="collapse"
67
+ tag="div"
68
+ @enter="onEnter"
69
+ @after-enter="onAfterEnter"
70
+ @leave="onLeave"
71
+ class="overflow-hidden"
72
+ >
73
+ <div v-show="toggle">
74
+ <div :class="[className]">
75
+ <slot />
76
+ </div>
77
+ </div>
78
+ </transition>
79
+ </div>
80
+ </template>
81
+
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>
@@ -0,0 +1,3 @@
1
+ export interface CollapseEvent extends Event {
2
+ visible: boolean
3
+ }
@@ -0,0 +1,78 @@
1
+ import { Directive } from 'vue'
2
+
3
+ export const vClickOutside: Directive = {
4
+ mounted(el, binding) {
5
+ el.__ClickOutsideHandler = (event: Event) => {
6
+ if (!(el === event.target || el.contains(event.target))) {
7
+ binding.value(event)
8
+ }
9
+ }
10
+ document.body.addEventListener('click', el.__ClickOutsideHandler)
11
+ },
12
+ unmounted(el) {
13
+ document.body.removeEventListener('click', el.__ClickOutsideHandler)
14
+ },
15
+ }
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
+
40
+ export const vCollapse: Directive = {
41
+ mounted(el, binding) {
42
+ el.__HandleToggler = (event: Event): void => {
43
+ const eTarget = event.target as HTMLElement
44
+ let target: HTMLElement
45
+ if (eTarget.tagName === 'A') {
46
+ const targetId = eTarget.getAttribute('href')
47
+ const formattedTargetId =
48
+ targetId?.charAt(0) === '#' ? targetId.substring(1) : targetId
49
+ target = document.getElementById(formattedTargetId as string)!
50
+ } else {
51
+ target = document.getElementById(binding.arg as string) as HTMLElement
52
+ }
53
+ if (!target.hasAttribute('accordion')) {
54
+ // run when it is not accordion component case
55
+ target.click()
56
+ return
57
+ }
58
+ const accordionName = target.getAttribute('accordion')
59
+ const accordionList: NodeListOf<HTMLElement> = document.querySelectorAll(
60
+ `button[accordion="${accordionName}"]`
61
+ )
62
+ // loop through each accordion items and handle reaction for each case
63
+ for (let i = 0; i < accordionList.length; i++) {
64
+ const isVisible = accordionList[i].getAttribute('visible')
65
+ if (accordionList[i].id !== target.id && isVisible === 'true') {
66
+ accordionList[i].click()
67
+ }
68
+ if (accordionList[i].id === target.id) {
69
+ accordionList[i].click()
70
+ }
71
+ }
72
+ }
73
+ el.addEventListener('click', el.__HandleToggler)
74
+ },
75
+ unmounted(el) {
76
+ el.removeEventListener('click', el.__HandleToggler)
77
+ },
78
+ }
@@ -0,0 +1,47 @@
1
+ <script setup lang="ts">
2
+ import { reactive, provide, ref, onMounted } from 'vue'
3
+ import type { InjectType } from './index.types'
4
+ import { vClickOutside } from '../directives'
5
+
6
+ const emit = defineEmits(['click-outside', 'toggle'])
7
+ const dropdownTarget = ref()
8
+ const dropdownState = reactive<InjectType>({
9
+ active: false,
10
+ buttonHeight: 0,
11
+ })
12
+ provide('dropdownState', dropdownState)
13
+ const toggle = (e: Event): void => {
14
+ dropdownState.active = !dropdownState.active
15
+ emit('toggle', e)
16
+ }
17
+ const onClickOutside = (e: Event): void => {
18
+ dropdownState.active = false
19
+ emit('click-outside', e)
20
+ }
21
+ const onEscape = (): void => {
22
+ dropdownState.active = false
23
+ }
24
+ onMounted(() => {
25
+ if (typeof window !== undefined) {
26
+ dropdownState.buttonHeight = dropdownTarget.value.scrollHeight
27
+ }
28
+ })
29
+ </script>
30
+
31
+ <template>
32
+ <div
33
+ class="relative"
34
+ ref="dropdownTarget"
35
+ v-click-outside="onClickOutside"
36
+ @keyup.esc="onEscape"
37
+ >
38
+ <slot
39
+ name="toggler"
40
+ :toggle="toggle"
41
+ :dropdown-state="dropdownState.active"
42
+ />
43
+ <slot></slot>
44
+ </div>
45
+ </template>
46
+
47
+ <style lang="scss" scoped></style>
@@ -0,0 +1,70 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, inject, Transition } from 'vue'
3
+ import { InjectType } from './index.types'
4
+
5
+ const props = withDefaults(
6
+ defineProps<{
7
+ className?: string
8
+ }>(),
9
+ {
10
+ className: '',
11
+ }
12
+ )
13
+ const contentRef = ref<HTMLAreaElement>()
14
+ const dropdownState = inject<InjectType>('dropdownState', {
15
+ active: false,
16
+ buttonHeight: 0,
17
+ })
18
+ const active = computed<boolean>(() => {
19
+ return dropdownState.active
20
+ })
21
+ const handleItemClick = () => {
22
+ dropdownState.active = false
23
+ }
24
+ const buttonHeight = computed(() => {
25
+ return { '--button-height': `${dropdownState.buttonHeight}px` }
26
+ })
27
+ const dropdownDirection = computed<string | undefined>(() => {
28
+ if (typeof window === undefined) {
29
+ return
30
+ }
31
+ if (!dropdownState.active || !contentRef.value) {
32
+ return
33
+ }
34
+ if (
35
+ window.innerHeight - contentRef.value.getBoundingClientRect().bottom <
36
+ 0
37
+ ) {
38
+ return 'dropup-bottom'
39
+ }
40
+ })
41
+ </script>
42
+
43
+ <template>
44
+ <transition name="dropdown-content">
45
+ <div
46
+ v-if="active"
47
+ ref="contentRef"
48
+ class="absolute my-2xs max-w-[14rem] w-max overflow-hidden"
49
+ :class="[dropdownDirection, className]"
50
+ :style="buttonHeight"
51
+ >
52
+ <slot :item-click="handleItemClick" />
53
+ </div>
54
+ </transition>
55
+ </template>
56
+
57
+ <style scoped lang="scss">
58
+ .dropup-bottom {
59
+ bottom: var(--button-height);
60
+ }
61
+ .dropdown-content-enter-active,
62
+ .dropdown-content-leave-active {
63
+ transition: all 0.3s;
64
+ }
65
+ .dropdown-content-enter-from,
66
+ .dropdown-content-leave-to {
67
+ opacity: 0;
68
+ transform: translateY(-10px);
69
+ }
70
+ </style>
@@ -0,0 +1,4 @@
1
+ export type InjectType = {
2
+ active: boolean
3
+ buttonHeight: number
4
+ }
@@ -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.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": "2c169fa8137650bf3fbc02ef2158c4c4e147a9ce"
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
- Position,
10
+ Alignment,
11
11
  } from './theme.types'
12
12
 
13
13
  /**
@@ -21,6 +21,7 @@ export const colorType = [
21
21
  'BEFOREBG',
22
22
  'AFTERBG',
23
23
  'HVBGCOLOR',
24
+ 'FCBGCOLOR',
24
25
  'ACTIVEBG',
25
26
  'TEXTCOLOR',
26
27
  'HVTEXTCOLOR',
@@ -101,7 +102,7 @@ export type InputType =
101
102
  | Range<1, 12>
102
103
  | OpacityRange
103
104
  | SpacingLevel
104
- | Position
105
+ | Alignment
105
106
 
106
107
  class GenerateMCLClass extends MCLTheme {
107
108
  classType: ClassType
@@ -119,6 +120,8 @@ class GenerateMCLClass extends MCLTheme {
119
120
  return this.getBgColorClass()
120
121
  case 'HVBGCOLOR':
121
122
  return this.getHoverBgColorClass()
123
+ case 'FCBGCOLOR':
124
+ return this.getFocusBgColorClass()
122
125
  case 'BEFOREBG':
123
126
  return this.getBeforeBgColorClass()
124
127
  case 'AFTERBG':
@@ -274,6 +277,12 @@ class GenerateMCLClass extends MCLTheme {
274
277
  }
275
278
  return MCLTheme.hoverBgColor[this.classValue as ColorPalette]
276
279
  }
280
+ getFocusBgColorClass(): string {
281
+ if (!this.validateColorType) {
282
+ return ' '
283
+ }
284
+ return MCLTheme.focusBgColor[this.classValue as ColorPalette]
285
+ }
277
286
  getBeforeBgColorClass(): string {
278
287
  if (!this.validateColorType) {
279
288
  return ' '
@@ -440,7 +449,7 @@ class GenerateMCLClass extends MCLTheme {
440
449
  }
441
450
  // handle text align classes
442
451
  getTextAlignClass(): string {
443
- return MCLTheme.textAlign[this.classValue as Position]
452
+ return MCLTheme.textAlign[this.classValue as Alignment]
444
453
  }
445
454
  // handle border width classes
446
455
  getBorderWidthClass(): string {
package/theme/theme.ts CHANGED
@@ -60,7 +60,7 @@ abstract class MCLTheme {
60
60
  white: 'bg-white',
61
61
  transparent: 'bg-transparent',
62
62
  }
63
- // hover bg color class
63
+ // hover: bg color class
64
64
  protected static hoverBgColor: ColorClass = {
65
65
  primary: 'hover:bg-primary',
66
66
  secondary: 'hover:bg-secondary',
@@ -80,6 +80,26 @@ abstract class MCLTheme {
80
80
  white: 'hover:bg-white',
81
81
  transparent: 'hover:bg-transparent',
82
82
  }
83
+ // focus: bg color class
84
+ protected static focusBgColor: ColorClass = {
85
+ primary: 'focus:bg-primary',
86
+ secondary: 'focus:bg-secondary',
87
+ success: 'focus:bg-success',
88
+ danger: 'focus:bg-danger',
89
+ info: 'focus:bg-info',
90
+ warning: 'focus:bg-warning',
91
+ 'light-1': 'focus:bg-light-1',
92
+ 'light-2': 'focus:bg-light-2',
93
+ 'light-3': 'focus:bg-light-3',
94
+ 'light-4': 'focus:bg-light-4',
95
+ 'dark-1': 'focus:bg-dark-1',
96
+ 'dark-2': 'focus:bg-dark-2',
97
+ 'dark-3': 'focus:bg-dark-3',
98
+ 'dark-4': 'focus:bg-dark-4',
99
+ black: 'focus:bg-black',
100
+ white: 'focus:bg-white',
101
+ transparent: 'focus:bg-transparent',
102
+ }
83
103
  // ::before bg color class
84
104
  protected static beforeBgColor: ColorClass = {
85
105
  primary: 'before:bg-primary',
@@ -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
- // border positions
41
- export type Position = 'left' | 'center' | 'right'
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 Location = 'left' | 'right' | 'top' | 'bottom'
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'
@@ -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-white border-${key} text-${key}`]: {},
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
  },
@@ -83,6 +83,11 @@ const mclTheme = plugin.withOptions(
83
83
  '@apply ring-0': {},
84
84
  },
85
85
  },
86
+ [`.btn.btn-${e(key)}.btn-no-ring`]: {
87
+ '&:focus': {
88
+ '@apply ring-0': {},
89
+ },
90
+ },
86
91
  [`.btn.btn-progress.btn-${e(key)}`]: {
87
92
  [`@apply before:bg-${key} border-${key} text-${key} bg-transparent`]:
88
93
  {},
@@ -364,6 +369,12 @@ const mclTheme = plugin.withOptions(
364
369
  '@apply ring-0': {},
365
370
  },
366
371
  },
372
+ '.btn.btn-no-ring': {
373
+ '@apply ring-offset-0': {},
374
+ '&:focus': {
375
+ '@apply ring-0': {},
376
+ },
377
+ },
367
378
  '.btn.btn-invert': {
368
379
  '@apply border-2 border-primary text-primary bg-white': {},
369
380
  '&:hover': {