@una-ui/nuxt 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +22 -0
  3. package/dist/module.cjs +5 -0
  4. package/dist/module.d.ts +31 -0
  5. package/dist/module.json +8 -0
  6. package/dist/module.mjs +93 -0
  7. package/dist/runtime/components/elements/Accordion.vue +201 -0
  8. package/dist/runtime/components/elements/Alert.vue +138 -0
  9. package/dist/runtime/components/elements/Avatar.vue +80 -0
  10. package/dist/runtime/components/elements/AvatarGroup.vue +27 -0
  11. package/dist/runtime/components/elements/Badge.vue +50 -0
  12. package/dist/runtime/components/elements/Button.vue +94 -0
  13. package/dist/runtime/components/elements/Icon.vue +9 -0
  14. package/dist/runtime/components/elements/Indicator.vue +60 -0
  15. package/dist/runtime/components/forms/FormGroup.vue +141 -0
  16. package/dist/runtime/components/forms/Input.vue +151 -0
  17. package/dist/runtime/components/forms/Switch.vue +117 -0
  18. package/dist/runtime/components/misc/ThemeSwitcher.vue +111 -0
  19. package/dist/runtime/components/slots/AvatarGroupDefault.d.ts +22 -0
  20. package/dist/runtime/components/slots/AvatarGroupDefault.mjs +44 -0
  21. package/dist/runtime/components/slots/FormGroupDefault.d.ts +25 -0
  22. package/dist/runtime/components/slots/FormGroupDefault.mjs +23 -0
  23. package/dist/runtime/composables/themes.d.ts +7 -0
  24. package/dist/runtime/composables/themes.mjs +119 -0
  25. package/dist/runtime/plugins/theme.client.d.ts +5 -0
  26. package/dist/runtime/plugins/theme.client.mjs +28 -0
  27. package/dist/runtime/plugins/theme.server.d.ts +2 -0
  28. package/dist/runtime/plugins/theme.server.mjs +24 -0
  29. package/dist/runtime/types/accordion.d.ts +112 -0
  30. package/dist/runtime/types/accordion.mjs +0 -0
  31. package/dist/runtime/types/alert.d.ts +55 -0
  32. package/dist/runtime/types/alert.mjs +0 -0
  33. package/dist/runtime/types/avatar-group.d.ts +26 -0
  34. package/dist/runtime/types/avatar-group.mjs +0 -0
  35. package/dist/runtime/types/avatar.d.ts +71 -0
  36. package/dist/runtime/types/avatar.mjs +0 -0
  37. package/dist/runtime/types/badge.d.ts +41 -0
  38. package/dist/runtime/types/badge.mjs +0 -0
  39. package/dist/runtime/types/button.d.ts +95 -0
  40. package/dist/runtime/types/button.mjs +0 -0
  41. package/dist/runtime/types/form-group.d.ts +83 -0
  42. package/dist/runtime/types/form-group.mjs +0 -0
  43. package/dist/runtime/types/icon.d.ts +9 -0
  44. package/dist/runtime/types/icon.mjs +0 -0
  45. package/dist/runtime/types/index.d.ts +11 -0
  46. package/dist/runtime/types/index.mjs +11 -0
  47. package/dist/runtime/types/indicator.d.ts +45 -0
  48. package/dist/runtime/types/indicator.mjs +0 -0
  49. package/dist/runtime/types/input.d.ts +91 -0
  50. package/dist/runtime/types/input.mjs +0 -0
  51. package/dist/runtime/types/switch.d.ts +69 -0
  52. package/dist/runtime/types/switch.mjs +0 -0
  53. package/dist/runtime/utils/index.d.ts +19 -0
  54. package/dist/runtime/utils/index.mjs +36 -0
  55. package/dist/types.d.ts +15 -0
  56. package/package.json +58 -0
@@ -0,0 +1,94 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { createReusableTemplate } from '@vueuse/core'
4
+ import NIcon from '../elements/Icon.vue'
5
+ import type { NButtonProps } from '../../types'
6
+
7
+ // @ts-expect-error tsconfig
8
+ import { NuxtLink } from '#components'
9
+
10
+ defineOptions({
11
+ inheritAttrs: false,
12
+ })
13
+
14
+ const props = withDefaults(defineProps<NButtonProps>(), {
15
+ type: 'button',
16
+ loadingPlacement: 'leading',
17
+ })
18
+
19
+ const btnVariants = ['solid', 'outline', 'soft', 'ghost', 'link', 'text'] as const
20
+ const hasVariant = computed(() => btnVariants.some(btnVariants => props.btn?.includes(btnVariants)))
21
+ const isBaseVariant = computed(() => props.btn?.includes('~'))
22
+
23
+ const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
24
+ </script>
25
+
26
+ <template>
27
+ <Component
28
+ :is="to ? NuxtLink : 'button'"
29
+ :to="to"
30
+ :type="to ? null : type"
31
+ class="btn"
32
+ :class="[
33
+ { 'btn-default-variant': !hasVariant && !isBaseVariant },
34
+ { 'btn-reverse': reverse },
35
+ una?.btn,
36
+ ]"
37
+ :disabled="to ? null : disabled || loading"
38
+ :btn="btn"
39
+ v-bind="$attrs"
40
+ >
41
+ <DefineTemplate v-if="loading">
42
+ <slot name="loading">
43
+ <NIcon
44
+ :name="una?.btnLoadingIcon ?? 'btn-loading-icon'"
45
+ :class="una?.btnLoading"
46
+ btn="loading"
47
+ />
48
+ </slot>
49
+ </DefineTemplate>
50
+
51
+ <ReuseTemplate v-if="loading && loadingPlacement === 'leading'" />
52
+ <slot
53
+ v-else
54
+ name="leading"
55
+ >
56
+ <NIcon
57
+ v-if="leading"
58
+ :name="leading"
59
+ :class="una?.btnLeading"
60
+ btn="leading"
61
+ />
62
+ </slot>
63
+
64
+ <ReuseTemplate v-if="loading && loadingPlacement === 'label'" />
65
+ <slot v-else>
66
+ <NIcon
67
+ v-if="label && icon"
68
+ :name="label"
69
+ btn="icon-label"
70
+ :class="una?.btnIconLabel"
71
+ />
72
+ <span
73
+ v-if="!icon"
74
+ btn="label"
75
+ :class="una?.btnLabel"
76
+ >
77
+ {{ label }}
78
+ </span>
79
+ </slot>
80
+
81
+ <ReuseTemplate v-if="loading && loadingPlacement === 'trailing'" />
82
+ <slot
83
+ v-else
84
+ name="trailing"
85
+ >
86
+ <NIcon
87
+ v-if="trailing"
88
+ :name="trailing"
89
+ :class="una?.btnLeading"
90
+ btn="trailing"
91
+ />
92
+ </slot>
93
+ </Component>
94
+ </template>
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts">
2
+ import type { NIconProps } from '../../types'
3
+
4
+ defineProps<NIconProps>()
5
+ </script>
6
+
7
+ <template>
8
+ <span icon-base :class="name" />
9
+ </template>
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { createReusableTemplate } from '@vueuse/core'
4
+ import type { NIndicatorProps } from '../../types'
5
+
6
+ defineOptions({
7
+ inheritAttrs: false,
8
+ })
9
+
10
+ const props = withDefaults(defineProps<NIndicatorProps>(), {
11
+ visible: true,
12
+ })
13
+
14
+ const indicatorPlacements = ['top-left', 'top-right', 'bottom-left', 'bottom-right'] as const
15
+ const hasPlacement = computed(() => indicatorPlacements.some(indicatorPlacements => props.indicator?.includes(indicatorPlacements)))
16
+
17
+ const indicatorVariants = ['solid'] as const
18
+ const hasVariant = computed(() => indicatorVariants.some(indicatorVariants => props.indicator?.includes(indicatorVariants)))
19
+
20
+ const isBaseVariant = computed(() => props.indicator?.includes('~'))
21
+
22
+ const [DefineTemplate, ReuseTemplate] = createReusableTemplate<{
23
+ ping?: boolean
24
+ }>()
25
+ </script>
26
+
27
+ <template>
28
+ <div
29
+ indicator="wrapper"
30
+ :class="una?.indicatorWrapper"
31
+ >
32
+ <slot />
33
+
34
+ <span v-if="visible" :size="size">
35
+ <DefineTemplate v-slot="{ ping }">
36
+ <slot name="indicator">
37
+ <span
38
+ v-bind="$attrs"
39
+ :indicator="indicator"
40
+ class="indicator whitespace-nowrap"
41
+ :class="[
42
+ { 'indicator-default-placement': !hasPlacement },
43
+ { 'indicator-default-variant': !hasVariant && !isBaseVariant },
44
+ { 'animate-ping ring-none': ping },
45
+ !label ? 'indicator-dot' : 'indicator-label',
46
+ una?.indicator,
47
+ ]"
48
+ >
49
+ <slot name="label">
50
+ {{ label }}
51
+ </slot>
52
+ </span>
53
+ </slot>
54
+ </DefineTemplate>
55
+
56
+ <ReuseTemplate :ping="ping" />
57
+ <ReuseTemplate />
58
+ </span>
59
+ </div>
60
+ </template>
@@ -0,0 +1,141 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import type { NFormGroupProps } from '../../types'
4
+ import NFormGroupDefaultSlot from '../slots/FormGroupDefault'
5
+ import { randomId } from '../../utils'
6
+
7
+ const props = defineProps<NFormGroupProps>()
8
+
9
+ const id = randomId('form-group')
10
+
11
+ const statusClassVariants = computed(() => {
12
+ const text = {
13
+ info: 'text-info',
14
+ success: 'text-success',
15
+ warning: 'text-warning',
16
+ error: 'text-error',
17
+ default: 'text-$c-gray-500',
18
+ }
19
+
20
+ return text[props.status ?? 'default']
21
+ })
22
+ </script>
23
+
24
+ <template>
25
+ <div
26
+ form-group
27
+ >
28
+ <slot name="top">
29
+ <div
30
+ form-group="message-wrapper"
31
+ :class="una?.formGroupMessageWrapper"
32
+ >
33
+ <div
34
+ v-if="label || hint || description"
35
+ form-group="top-wrapper"
36
+ :class="una?.formGroupTopWrapper"
37
+ >
38
+ <div
39
+ v-if="label || hint"
40
+ form-group="top-wrapper-inner"
41
+ :class="una?.formGroupTopWrapperInner"
42
+ >
43
+ <slot name="label">
44
+ <label
45
+ v-if="label"
46
+ :for="name ?? id"
47
+ form-group="label-wrapper"
48
+ :class="una?.formGroupLabelWrapper"
49
+ >
50
+ <span
51
+ form-group="label"
52
+ :class="una?.formGroupLabel"
53
+ >
54
+ {{ label }}
55
+ </span>
56
+ <span
57
+ v-if="required"
58
+ form-group="label-required"
59
+ :class="una?.formGroupLabelRequired"
60
+ />
61
+ </label>
62
+ </slot>
63
+
64
+ <slot name="hint">
65
+ <span
66
+ v-if="hint" form-group="hint"
67
+ :class="una?.formGroupHint"
68
+ >
69
+ {{ hint }}
70
+ </span>
71
+ </slot>
72
+ </div>
73
+
74
+ <slot name="description">
75
+ <span
76
+ v-if="description"
77
+ form-group="description"
78
+ :class="una?.formGroupDescription"
79
+ >
80
+ {{ description }}
81
+ </span>
82
+ </slot>
83
+ </div>
84
+ </div>
85
+ </slot>
86
+
87
+ <NFormGroupDefaultSlot
88
+ :id="name ?? id"
89
+ :status="status"
90
+ >
91
+ <slot />
92
+ </NFormGroupDefaultSlot>
93
+
94
+ <slot name="bottom">
95
+ <div
96
+ v-if="message || counter"
97
+ form-group="bottom-wrapper"
98
+ :class="[
99
+ { 'justify-end': !message && counter },
100
+ una?.formGroupBottomWrapper,
101
+ ]"
102
+ >
103
+ <slot name="message">
104
+ <div
105
+ v-if="message"
106
+ form-group="message-wrapper"
107
+ :class="una?.formGroupMessageWrapper"
108
+ >
109
+ <p
110
+ form-group="message"
111
+ :class="[
112
+ una?.formGroupMessage,
113
+ statusClassVariants,
114
+ ]"
115
+ >
116
+ {{ message }}
117
+ </p>
118
+ </div>
119
+ </slot>
120
+
121
+ <slot name="counter">
122
+ <div
123
+ v-if="counter"
124
+ form-group="counter-wrapper"
125
+ :class="una?.formGroupCounterWrapper"
126
+ >
127
+ <span
128
+ :class="`${counter?.value >= (counter?.max || 0) && counter?.max
129
+ ? 'form-group-counter-error'
130
+ : 'form-group-counter-current'}`"
131
+ >
132
+ {{ counter?.value }}
133
+ </span>
134
+ <span v-if="counter?.max" form-group="counter-separator">/</span>
135
+ <span v-if="counter?.max" form-group="counter-max">{{ counter?.max }}</span>
136
+ </div>
137
+ </slot>
138
+ </div>
139
+ </slot>
140
+ </div>
141
+ </template>
@@ -0,0 +1,151 @@
1
+ <script setup lang="ts">
2
+ import { useVModel } from '@vueuse/core'
3
+ import { computed } from 'vue'
4
+ import NIcon from '../elements/Icon.vue'
5
+ import type { NInputProps } from '../../types'
6
+ import { randomId } from '../../utils'
7
+
8
+ defineOptions({
9
+ inheritAttrs: false,
10
+ })
11
+
12
+ const props = withDefaults(defineProps<NInputProps>(), {
13
+ type: 'text',
14
+ })
15
+
16
+ const emit = defineEmits<{ (...args: any): void }>()
17
+
18
+ const slots = defineSlots<{
19
+ leading?: any
20
+ trailing?: any
21
+ }>()
22
+
23
+ const inputValue = useVModel(props, 'modelValue', emit, { passive: true })
24
+
25
+ const id = computed(() => props.id ?? randomId('input'))
26
+
27
+ const isLeading = computed(() => props.leading || slots.leading)
28
+ const isTrailing = computed(() => props.trailing || slots.trailing || props.status || props.loading)
29
+
30
+ const inputVariants = ['outline', 'solid'] as const
31
+ const hasVariant = computed(() => inputVariants.some(inputVariants => props.input?.includes(inputVariants)))
32
+ const isBaseVariant = computed(() => props.input?.includes('~'))
33
+
34
+ const statusClassVariants = computed(() => {
35
+ const input = {
36
+ info: 'input-status-info input-solid-info input-status-ring',
37
+ success: 'input-status-success input-solid-success input-status-ring',
38
+ warning: 'input-status-warning input-solid-warning input-status-ring',
39
+ error: 'input-status-error input-solid-error input-status-ring',
40
+ default: !hasVariant.value && !isBaseVariant.value ? 'input-default-variant' : '',
41
+ }
42
+
43
+ const text = {
44
+ info: 'text-info',
45
+ success: 'text-success',
46
+ warning: 'text-warning',
47
+ error: 'text-error',
48
+ default: '',
49
+ }
50
+
51
+ const icon = {
52
+ info: props.una?.inputWarningIcon ?? 'input-info-icon',
53
+ success: props.una?.inputSuccessIcon ?? 'input-success-icon',
54
+ warning: props.una?.inputWarningIcon ?? 'input-warning-icon',
55
+ error: props.una?.inputErrorIcon ?? 'input-error-icon',
56
+ default: '',
57
+ }
58
+
59
+ return {
60
+ input: input[props.status ?? 'default'],
61
+ text: text[props.status ?? 'default'],
62
+ icon: icon[props.status ?? 'default'],
63
+ }
64
+ })
65
+
66
+ const reverseClassVariants = computed(() => {
67
+ const input = {
68
+ false: [{ 'input-leading-padding': isLeading.value }, { 'input-trailing-padding': isTrailing.value }],
69
+ true: [{ 'input-trailing-padding': isLeading.value }, { 'input-leading-padding': isTrailing.value }],
70
+ }
71
+
72
+ return {
73
+ input: input[props.reverse ? 'true' : 'false'],
74
+ leadingWrapper: props.reverse ? 'input-trailing-wrapper' : 'input-leading-wrapper',
75
+ trailingWrapper: props.reverse ? 'input-leading-wrapper' : 'input-trailing-wrapper',
76
+ }
77
+ })
78
+ </script>
79
+
80
+ <template>
81
+ <div
82
+ input="wrapper"
83
+ :size="size"
84
+ :class="una?.inputWrapper"
85
+ >
86
+ <div
87
+ v-if="isLeading"
88
+ :class="[
89
+ una?.inputLeadingWrapper,
90
+ reverseClassVariants.leadingWrapper,
91
+ statusClassVariants.text,
92
+ ]"
93
+ >
94
+ <slot name="leading">
95
+ <NIcon
96
+ v-if="leading"
97
+ :name="leading"
98
+ input="leading"
99
+ :class="una?.inputLeading"
100
+ @click="emit('leading')"
101
+ />
102
+ </slot>
103
+ </div>
104
+
105
+ <input
106
+ :id="id"
107
+ v-model="inputValue"
108
+ :type="type"
109
+ class="input"
110
+ :class="[
111
+ statusClassVariants.input,
112
+ reverseClassVariants.input,
113
+ una?.input,
114
+ ]"
115
+ :input="input"
116
+ v-bind="$attrs"
117
+ >
118
+
119
+ <div
120
+ v-if="isTrailing"
121
+ :class="[
122
+ una?.inputTrailingWrapper,
123
+ reverseClassVariants.trailingWrapper,
124
+ statusClassVariants.text,
125
+ ]"
126
+ >
127
+ <NIcon
128
+ v-if="loading"
129
+ input="loading"
130
+ :name="una?.inputLoadingIcon ?? 'input-loading-icon'"
131
+ :class="una?.inputLoading"
132
+ />
133
+
134
+ <NIcon
135
+ v-if="status"
136
+ input="status-icon-base"
137
+ :name="statusClassVariants.icon"
138
+ />
139
+
140
+ <slot v-else name="trailing">
141
+ <NIcon
142
+ v-if="trailing"
143
+ input="trailing"
144
+ :class="una?.inputTrailing"
145
+ :name="trailing"
146
+ @click="emit('trailing')"
147
+ />
148
+ </slot>
149
+ </div>
150
+ </div>
151
+ </template>
@@ -0,0 +1,117 @@
1
+ <script setup lang="ts">
2
+ import { Switch } from '@headlessui/vue'
3
+ import { computed } from 'vue'
4
+ import { useVModel } from '@vueuse/core'
5
+ import type { NSwitchProps } from '../../types'
6
+ import NIcon from '../elements/Icon.vue'
7
+
8
+ defineOptions({
9
+ inheritAttrs: false,
10
+ })
11
+
12
+ const props = defineProps<NSwitchProps>()
13
+
14
+ const emit = defineEmits<{ (...args: any): void }>()
15
+
16
+ const on = useVModel(props, 'modelValue', emit, { passive: true })
17
+
18
+ const _switch = computed(() => props.switch)
19
+
20
+ const outsetClassVariants = computed(() => {
21
+ const switchWrapper = {
22
+ false: 'switch-inset',
23
+ true: 'switch-outset',
24
+ }
25
+
26
+ const switchTrack = {
27
+ false: 'switch-track-inset',
28
+ true: 'switch-track-outset',
29
+ }
30
+
31
+ const switchThumb = {
32
+ false: 'left-0.125em',
33
+ true: 'left-0 border-base border',
34
+ }
35
+
36
+ return {
37
+ switchWrapper: switchWrapper[!props.outset ? 'false' : 'true'],
38
+ switchTrack: switchTrack[!props.outset ? 'false' : 'true'],
39
+ switchThumb: switchThumb[!props.outset ? 'false' : 'true'],
40
+ }
41
+ })
42
+
43
+ const onClassVariants = computed(() => {
44
+ const switchTrack = {
45
+ true: `${props.una?.switchTrackOn ?? ''} switch-track-on`,
46
+ false: `${props.una?.switchTrackOff ?? ''} switch-track-off`,
47
+ }
48
+
49
+ const switchThumb = {
50
+ true: `${props.una?.switchThumbOn ?? ''} switch-thumb-on`,
51
+ false: `${props.una?.switchThumbOff ?? ''} switch-thumb-off`,
52
+ }
53
+
54
+ const switchIcon = {
55
+ true: `${props.onIcon ?? ''} switch-icon-on`,
56
+ false: `${props.offIcon ?? ''} switch-icon-off`,
57
+ }
58
+
59
+ return {
60
+ switchTrack: switchTrack[on.value ? 'true' : 'false'],
61
+ switchThumb: switchThumb[on.value ? 'true' : 'false'],
62
+ switchIcon: switchIcon[on.value ? 'true' : 'false'],
63
+ }
64
+ })
65
+ </script>
66
+
67
+ <template>
68
+ <Switch
69
+ v-model="on"
70
+ class="switch"
71
+ :class="[
72
+ { 'switch-disabled': disabled || loading },
73
+ outsetClassVariants?.switchWrapper,
74
+ ]"
75
+ :switch="_switch"
76
+ v-bind="$attrs"
77
+ :disabled="disabled"
78
+ >
79
+ <span class="sr-only">Track</span>
80
+ <span
81
+ aria-hidden="true"
82
+ switch="track"
83
+ :class="[
84
+ una?.switchTrack,
85
+ onClassVariants?.switchTrack,
86
+ outsetClassVariants?.switchTrack,
87
+ ]"
88
+ />
89
+
90
+ <span class="sr-only">Thumb</span>
91
+ <span
92
+ aria-hidden="true"
93
+ switch="thumb"
94
+ :class="[
95
+ una?.switchThumb,
96
+ onClassVariants?.switchThumb,
97
+ outsetClassVariants?.switchThumb,
98
+ ]"
99
+ >
100
+ <span class="sr-only">Icon</span>
101
+ <slot v-if="!loading" name="icon" :on="on">
102
+ <NIcon
103
+ switch="icon-base"
104
+ :name="onClassVariants?.switchIcon"
105
+ :class="una?.switchIconBase"
106
+ />
107
+ </slot>
108
+ <slot v-else name="loading-icon" :on="on">
109
+ <NIcon
110
+ switch="loading"
111
+ :class="una?.switchLoading"
112
+ :name="una?.switchloadingicon ?? 'switch-loading-icon'"
113
+ />
114
+ </slot>
115
+ </span>
116
+ </Switch>
117
+ </template>
@@ -0,0 +1,111 @@
1
+ <script setup lang="ts">
2
+ import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
3
+ import { useStorage, useToggle } from '@vueuse/core'
4
+ import { computed } from 'vue'
5
+ import { grayThemes, primaryThemes } from '../../composables/themes'
6
+
7
+ // TODO: globalize
8
+ export interface ThemeColors {
9
+ [key: string]: string
10
+ }
11
+
12
+ // TODO: confingurable in the app.config
13
+ const defaultSettings = {
14
+ primaryColors: primaryThemes.filter(([colorName, _]) => colorName === 'yellow')[0][1],
15
+ grayColors: grayThemes.filter(([colorName, _]) => colorName === 'stone')[0][1],
16
+ fontSize: 16,
17
+ }
18
+
19
+ const settings = useStorage('una-settings', defaultSettings)
20
+
21
+ // use yellow primary theme as default
22
+ const currentPrimaryTheme = computed(() => settings.value.primaryColors?.['--una-primary-hex'])
23
+ // get current theme name
24
+ const currentPrimaryThemeName = computed(() => {
25
+ const theme = primaryThemes.find(([, theme]) => theme['--una-primary-hex'] === currentPrimaryTheme.value)
26
+ return theme ? theme[0] : ''
27
+ })
28
+
29
+ // use stone primary theme as default
30
+ const currentGrayTheme = computed(() => settings.value.grayColors?.['--una-gray-hex'])
31
+ // get current theme name
32
+ const currentGrayThemeName = computed(() => {
33
+ const theme = grayThemes.find(([, theme]) => theme['--una-gray-hex'] === currentGrayTheme.value)
34
+ return theme ? theme[0] : ''
35
+ })
36
+
37
+ // update theme in storage
38
+ function updatePrimaryTheme(theme: ThemeColors) {
39
+ settings.value.primaryColors = theme
40
+ }
41
+ function updateGrayTheme(theme: ThemeColors) {
42
+ settings.value.grayColors = theme
43
+ }
44
+
45
+ const [value, toggle] = useToggle()
46
+ function shuffleTheme() {
47
+ const randomPrimaryTheme = primaryThemes[Math.floor(Math.random() * primaryThemes.length)][1]
48
+ const randomGrayTheme = grayThemes[Math.floor(Math.random() * grayThemes.length)][1]
49
+ updatePrimaryTheme(randomPrimaryTheme)
50
+ updateGrayTheme(randomGrayTheme)
51
+ toggle()
52
+ }
53
+ </script>
54
+
55
+ <template>
56
+ <div class="sm:ml-5">
57
+ <Popover class="relative inline-block">
58
+ <PopoverButton
59
+ btn="~ square soft"
60
+ class="rounded-lg"
61
+ >
62
+ <span i-heroicons-swatch-20-solid text-md />
63
+ </PopoverButton>
64
+
65
+ <transition enter-active-class="transition ease-out duration-200" enter-from-class="opacity-0 translate-y-1" enter-to-class="opacity-100 translate-y-0" leave-active-class="transition ease-in duration-150" leave-from-class="opacity-100 translate-y-0" leave-to-class="opacity-0 translate-y-1">
66
+ <PopoverPanel class="absolute right-0 z-100 mt-2 w-54 border-1 border-base rounded-xl bg-muted px-4 py-5 shadow-lg">
67
+ <div class="flex flex-col space-y-5">
68
+ <div class="grid grid-cols-5 gap-3">
69
+ <button
70
+ v-for="[key, theme] in primaryThemes"
71
+ :key="key"
72
+ :style="{ background: theme['--una-primary-hex'] }"
73
+ class="h-6.5 w-6.5 rounded-full transition-all" :class="[currentPrimaryThemeName === key ? 'ring-2' : 'scale-93']"
74
+ ring="primary offset-4 offset-base"
75
+ @click="updatePrimaryTheme(theme)"
76
+ />
77
+ </div>
78
+
79
+ <hr class="my-2 border-$c-divider">
80
+
81
+ <div class="grid grid-cols-5 gap-3">
82
+ <button
83
+ v-for="[key, theme] in grayThemes"
84
+ :key="key"
85
+ :style="{ background: theme['--una-gray-hex'] }"
86
+ :class="currentGrayThemeName === key ? 'ring-2' : 'scale-93'"
87
+ class="h-6.5 w-6.5 rounded-full transition-all"
88
+ ring="gray offset-4 offset-base"
89
+ @click="updateGrayTheme(theme)"
90
+ />
91
+ </div>
92
+
93
+ <hr class="my-2 border-$c-divider">
94
+
95
+ <button
96
+ btn="~ solid block"
97
+ class="rounded-lg transition"
98
+ @click="shuffleTheme"
99
+ >
100
+ Shuffle
101
+ <span
102
+ i-heroicons-adjustments-horizontal-20-solid ml-2
103
+ :class="value ? 'rotate-180 transform' : 'rotate-0'"
104
+ />
105
+ </button>
106
+ </div>
107
+ </PopoverPanel>
108
+ </transition>
109
+ </Popover>
110
+ </div>
111
+ </template>