@bobbykim/manguito-theme 0.3.0 → 0.4.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,13 @@ 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 type { CollapseEvent } from './lib/collapse/index.types'
8
13
 
9
14
  const generateClass = (type: ClassType, value: InputType): string => {
10
- console.log('running generate class')
11
15
  const mclGenerate = new GenerateMCLClass(type, value)
12
16
  if ((<string[]>(<unknown>colorType)).includes(type)) {
13
17
  return mclGenerate.generateColorClass()
@@ -26,3 +30,6 @@ const generateClass = (type: ClassType, value: InputType): string => {
26
30
  }
27
31
 
28
32
  export default generateClass
33
+
34
+ export { DropdownContainer, DropdownContent, Collapse, CollapseEvent }
35
+ export * from './lib/directives'
@@ -0,0 +1,85 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, onMounted, onBeforeUnmount, watch } 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
+ const contentRef = ref<HTMLAreaElement>()
19
+ const slotHeight = ref<number>()
20
+
21
+ const handleToggleEvent = (e: Event): void => {
22
+ toggle.value = !toggle.value
23
+ emit('toggle', { ...e, visible: toggle.value } as CollapseEvent)
24
+ }
25
+ const initObserver = (): ResizeObserver => {
26
+ const observer = new ResizeObserver(() => {
27
+ if (contentRef.value) {
28
+ slotHeight.value = contentRef.value.scrollHeight
29
+ return
30
+ }
31
+ slotHeight.value = 0
32
+ })
33
+ return observer
34
+ }
35
+ const slotHeightVal = computed(() => {
36
+ return {
37
+ height: contentRef.value && toggle.value ? slotHeight.value + 'px' : '0px',
38
+ }
39
+ })
40
+ watch(
41
+ () => props.visible,
42
+ (newValue) => {
43
+ toggle.value = newValue
44
+ }
45
+ )
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
+ </script>
56
+
57
+ <template>
58
+ <div>
59
+ <button
60
+ :id="collapseId"
61
+ @click="handleToggleEvent"
62
+ class="hidden"
63
+ :accordion="accordion"
64
+ :visible="toggle"
65
+ ></button>
66
+ <div
67
+ class="overflow-hidden transition-[height] duration-500"
68
+ :style="slotHeightVal"
69
+ :class="[toggle ? 'ease-in' : 'ease-out']"
70
+ >
71
+ <div
72
+ ref="contentRef"
73
+ class="transition-opacity duration-500"
74
+ :class="[
75
+ toggle ? 'opacity-100 ease-in' : 'opacity-0 ease-out',
76
+ className,
77
+ ]"
78
+ >
79
+ <slot />
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </template>
84
+
85
+ <style scoped></style>
@@ -0,0 +1,3 @@
1
+ export interface CollapseEvent extends Event {
2
+ visible: boolean
3
+ }
@@ -0,0 +1,57 @@
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 vCollapse: Directive = {
18
+ mounted(el, binding) {
19
+ el.__HandleToggler = (event: Event): void => {
20
+ const eTarget = event.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(
27
+ formattedTargetId as string
28
+ ) as HTMLElement
29
+ } else {
30
+ target = document.getElementById(binding.arg as string) as HTMLElement
31
+ }
32
+ if (!target.hasAttribute('accordion')) {
33
+ // run when it is not accordion component case
34
+ target.click()
35
+ return
36
+ }
37
+ const accordionName = target.getAttribute('accordion')
38
+ const accordionList: NodeListOf<HTMLElement> = document.querySelectorAll(
39
+ `button[accordion="${accordionName}"]`
40
+ )
41
+ // loop through each accordion items and handle reaction for each case
42
+ for (let i = 0; i < accordionList.length; i++) {
43
+ const isVisible = accordionList[i].getAttribute('visible')
44
+ if (accordionList[i].id !== target.id && isVisible === 'true') {
45
+ accordionList[i].click()
46
+ }
47
+ if (accordionList[i].id === target.id) {
48
+ accordionList[i].click()
49
+ }
50
+ }
51
+ }
52
+ el.addEventListener('click', el.__HandleToggler)
53
+ },
54
+ unmounted(el) {
55
+ el.removeEventListener('click', el.__HandleToggler)
56
+ },
57
+ }
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobbykim/manguito-theme",
3
- "version": "0.3.0",
3
+ "version": "0.4.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": "79700f8b34204a6ecbe2f56d75f74a64c331055c"
21
21
  }
package/theme/index.ts CHANGED
@@ -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',
@@ -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 ' '
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',
@@ -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': {