orio-ui 0.1.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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +237 -0
  3. package/dist/module.cjs +5 -0
  4. package/dist/module.d.mts +3 -0
  5. package/dist/module.d.ts +3 -0
  6. package/dist/module.json +12 -0
  7. package/dist/module.mjs +16 -0
  8. package/dist/runtime/assets/css/animation.css +1 -0
  9. package/dist/runtime/assets/css/colors.css +1 -0
  10. package/dist/runtime/assets/css/cool-gradient-hover.css +23 -0
  11. package/dist/runtime/assets/css/main.css +1 -0
  12. package/dist/runtime/assets/css/scroll.css +1 -0
  13. package/dist/runtime/components/Button.vue +102 -0
  14. package/dist/runtime/components/CheckBox.vue +93 -0
  15. package/dist/runtime/components/ControlElement.vue +39 -0
  16. package/dist/runtime/components/DashedContainer.vue +59 -0
  17. package/dist/runtime/components/DatePicker.vue +30 -0
  18. package/dist/runtime/components/DateRangePicker.vue +73 -0
  19. package/dist/runtime/components/EmptyState.vue +81 -0
  20. package/dist/runtime/components/Icon.vue +40 -0
  21. package/dist/runtime/components/Input.vue +48 -0
  22. package/dist/runtime/components/LoadingSpinner.vue +6 -0
  23. package/dist/runtime/components/Modal.vue +69 -0
  24. package/dist/runtime/components/Popover.vue +249 -0
  25. package/dist/runtime/components/Selector.vue +208 -0
  26. package/dist/runtime/components/Tag.vue +21 -0
  27. package/dist/runtime/components/Textarea.vue +53 -0
  28. package/dist/runtime/components/view/Dates.vue +59 -0
  29. package/dist/runtime/components/view/Separator.vue +26 -0
  30. package/dist/runtime/components/view/Text.vue +79 -0
  31. package/dist/runtime/composables/index.d.ts +4 -0
  32. package/dist/runtime/composables/index.js +4 -0
  33. package/dist/runtime/composables/useApi.d.ts +10 -0
  34. package/dist/runtime/composables/useApi.js +9 -0
  35. package/dist/runtime/composables/useFuzzySearch.d.ts +10 -0
  36. package/dist/runtime/composables/useFuzzySearch.js +22 -0
  37. package/dist/runtime/composables/useModal.d.ts +15 -0
  38. package/dist/runtime/composables/useModal.js +28 -0
  39. package/dist/runtime/composables/useTheme.d.ts +6 -0
  40. package/dist/runtime/composables/useTheme.js +23 -0
  41. package/dist/runtime/index.d.ts +20 -0
  42. package/dist/runtime/index.js +20 -0
  43. package/dist/runtime/utils/icon-registry.d.ts +2 -0
  44. package/dist/runtime/utils/icon-registry.js +26 -0
  45. package/dist/types.d.mts +7 -0
  46. package/dist/types.d.ts +7 -0
  47. package/nuxt.config.ts +38 -0
  48. package/package.json +99 -0
  49. package/src/module.ts +16 -0
  50. package/src/runtime/assets/css/animation.css +88 -0
  51. package/src/runtime/assets/css/colors.css +142 -0
  52. package/src/runtime/assets/css/cool-gradient-hover.scss +33 -0
  53. package/src/runtime/assets/css/main.css +11 -0
  54. package/src/runtime/assets/css/scroll.css +46 -0
  55. package/src/runtime/components/Button.vue +110 -0
  56. package/src/runtime/components/CheckBox.vue +103 -0
  57. package/src/runtime/components/ControlElement.vue +42 -0
  58. package/src/runtime/components/DashedContainer.vue +60 -0
  59. package/src/runtime/components/DatePicker.vue +84 -0
  60. package/src/runtime/components/DateRangePicker.vue +74 -0
  61. package/src/runtime/components/EmptyState.vue +87 -0
  62. package/src/runtime/components/Icon.vue +51 -0
  63. package/src/runtime/components/Input.vue +54 -0
  64. package/src/runtime/components/LoadingSpinner.vue +6 -0
  65. package/src/runtime/components/Modal.vue +111 -0
  66. package/src/runtime/components/Popover.vue +249 -0
  67. package/src/runtime/components/Selector.vue +224 -0
  68. package/src/runtime/components/Tag.vue +45 -0
  69. package/src/runtime/components/Textarea.vue +59 -0
  70. package/src/runtime/components/view/Dates.vue +61 -0
  71. package/src/runtime/components/view/Separator.vue +30 -0
  72. package/src/runtime/components/view/Text.vue +83 -0
  73. package/src/runtime/composables/index.ts +4 -0
  74. package/src/runtime/composables/useApi.ts +26 -0
  75. package/src/runtime/composables/useFuzzySearch.ts +51 -0
  76. package/src/runtime/composables/useModal.ts +47 -0
  77. package/src/runtime/composables/useTheme.ts +31 -0
  78. package/src/runtime/index.ts +25 -0
  79. package/src/runtime/utils/icon-registry.ts +41 -0
@@ -0,0 +1,103 @@
1
+ <script setup lang="ts">
2
+ const modelValue = defineModel<boolean>({ required: false });
3
+
4
+ defineProps<{
5
+ checkedIcon?: string; // optional: pass icon name for checked state
6
+ uncheckedIcon?: string; // optional: pass icon name for unchecked state
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <orio-control-element class="checkbox">
12
+ <label class="checkbox-label">
13
+ <input
14
+ v-model="modelValue"
15
+ type="checkbox"
16
+ class="checkbox-input"
17
+ tabindex="-1"
18
+ />
19
+ <span
20
+ class="checkbox-box"
21
+ :class="{
22
+ defaultChecked: !checkedIcon,
23
+ defaultUnchecked: !uncheckedIcon,
24
+ }"
25
+ >
26
+ <slot name="icon" :checked="modelValue">
27
+ <orio-icon v-if="modelValue && checkedIcon" :name="checkedIcon" />
28
+ <orio-icon
29
+ v-else-if="!modelValue && uncheckedIcon"
30
+ :name="uncheckedIcon"
31
+ />
32
+ </slot>
33
+ </span>
34
+ <slot />
35
+ </label>
36
+ </orio-control-element>
37
+ </template>
38
+
39
+ <style lang="scss" scoped>
40
+ .checkbox {
41
+ --box-size: 1rem;
42
+
43
+ &-label {
44
+ position: relative;
45
+ user-select: none;
46
+ display: inline-flex;
47
+ align-items: center;
48
+ gap: 0.4rem;
49
+ cursor: pointer;
50
+ font-size: 0.9rem;
51
+ color: var(--color-text);
52
+ }
53
+
54
+ &-input {
55
+ position: absolute;
56
+ inset: 0;
57
+ width: var(--box-size);
58
+ height: 1rem;
59
+ margin: 0;
60
+ opacity: 0;
61
+ }
62
+
63
+ &-box {
64
+ width: var(--box-size);
65
+ height: var(--box-size);
66
+ border: 2px solid var(--color-border);
67
+ border-radius: 4px;
68
+ background-color: var(--color-bg);
69
+ display: inline-flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ transition:
73
+ background-color 0.2s ease,
74
+ border-color 0.2s ease;
75
+ }
76
+
77
+ .checkbox-input:checked + .checkbox-box {
78
+ background-color: var(--color-accent);
79
+ border-color: var(--color-accent);
80
+ }
81
+
82
+ .checkbox-input:checked + .checkbox-box.defaultChecked {
83
+ &::after {
84
+ content: '';
85
+ width: 0.3rem;
86
+ height: 0.6rem;
87
+ position: relative;
88
+ bottom: 0.1rem;
89
+ border: solid var(--color-accent-ink);
90
+ border-width: 0 2px 2px 0;
91
+ transform: rotate(45deg);
92
+ }
93
+ }
94
+
95
+ &-label:hover .checkbox-box {
96
+ border-color: var(--color-accent);
97
+ }
98
+ .checkbox-input:focus-visible + .checkbox-box {
99
+ outline: 2px solid var(--color-accent);
100
+ outline-offset: 2px;
101
+ }
102
+ }
103
+ </style>
@@ -0,0 +1,42 @@
1
+ <script setup lang="ts">
2
+ export interface ControlProps {
3
+ /**
4
+ * Minimal will reset margin and remove border and box shadow from every element inside the slot
5
+ */
6
+ appearance?: "normal" | "minimal";
7
+ }
8
+
9
+ withDefaults(defineProps<ControlProps>(), {
10
+ appearance: "normal",
11
+ });
12
+ </script>
13
+
14
+ <template>
15
+ <div class="control" :class="[appearance]">
16
+ <label v-if="$attrs.label" class="control-label">{{ $attrs.label }}</label>
17
+ <div class="slot-wrapper" v-bind="$attrs">
18
+ <slot />
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <style lang="scss" scoped>
24
+ .control {
25
+ margin: 0.5rem;
26
+
27
+ .control-label {
28
+ user-select: none;
29
+ }
30
+
31
+ &.minimal {
32
+ margin: 0;
33
+
34
+ .slot-wrapper :first-child {
35
+ border: 0;
36
+ &:focus {
37
+ box-shadow: none;
38
+ }
39
+ }
40
+ }
41
+ }
42
+ </style>
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+
4
+ interface Props {
5
+ icon?: string;
6
+ text?: string;
7
+ size?: 'small' | 'medium' | 'large';
8
+ }
9
+ const props = withDefaults(defineProps<Props>(), {
10
+ size: 'medium',
11
+ });
12
+ defineEmits(['click']);
13
+
14
+ const iconSize = computed(() => {
15
+ switch (props.size) {
16
+ case 'small':
17
+ return '2rem';
18
+ case 'large':
19
+ return '5rem';
20
+ default:
21
+ return '3rem';
22
+ }
23
+ });
24
+ </script>
25
+ <template>
26
+ <div class="dashed-container gradient-hover" @click="$emit('click')">
27
+ <orio-icon
28
+ v-if="icon"
29
+ :name="icon"
30
+ class="icon-class"
31
+ :size="iconSize"
32
+ />
33
+ <span v-if="text" class="text-class" :size>{{ text }}</span>
34
+ </div>
35
+ </template>
36
+
37
+ <style scoped lang="scss">
38
+ .dashed-container {
39
+ cursor: pointer;
40
+ border: 3px dashed var(--color-border);
41
+ border-radius: 4px;
42
+ padding: 0.5rem;
43
+ display: flex;
44
+ flex-direction: column;
45
+ justify-content: center;
46
+ align-items: center;
47
+
48
+ &:hover {
49
+ .text-class {
50
+ color: var(--color-accent-hover);
51
+ }
52
+ }
53
+ }
54
+ .icon-class {
55
+ color: var(--color-muted);
56
+ }
57
+ .text-class {
58
+ color: var(--color-accent);
59
+ }
60
+ </style>
@@ -0,0 +1,84 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import { nanoid } from 'nanoid';
4
+
5
+ interface Props {
6
+ month?: boolean;
7
+ }
8
+ defineProps<Props>();
9
+
10
+ const date = defineModel<string | null | undefined>('date', {
11
+ required: true,
12
+ });
13
+
14
+ const randomName = computed(() => `date-${nanoid(8)}`);
15
+ </script>
16
+
17
+ <template>
18
+ <orio-control-element class="date-picker" v-bind="$attrs">
19
+ <input
20
+ v-model="date"
21
+ :type="month ? 'month' : 'date'"
22
+ class="date-input"
23
+ :name="randomName"
24
+ />
25
+ </orio-control-element>
26
+ </template>
27
+
28
+ <style scoped>
29
+ .date-picker * {
30
+ width: 100%;
31
+ cursor: pointer;
32
+ }
33
+
34
+ .date-picker-label {
35
+ display: inline-flex;
36
+ flex-direction: column;
37
+ gap: 0.25rem;
38
+ font-size: 0.9rem;
39
+ color: var(--color-text);
40
+ }
41
+
42
+ .date-input {
43
+ /* border-box allows correct sizing for input */
44
+ box-sizing: border-box;
45
+ appearance: none;
46
+ background-color: var(--color-bg);
47
+ border: 1px solid var(--color-border);
48
+ border-radius: 4px;
49
+ padding: 0.4rem 0.6rem;
50
+ color: var(--color-text);
51
+ font-size: 0.95rem;
52
+ transition:
53
+ border-color 0.2s ease,
54
+ box-shadow 0.2s ease;
55
+ }
56
+
57
+ /* Hover + focus */
58
+ .date-input:hover {
59
+ border-color: var(--color-accent);
60
+ }
61
+ .date-input:focus {
62
+ border-color: var(--color-accent);
63
+ box-shadow: 0 0 0 2px var(--color-accent-soft);
64
+ outline: none;
65
+ }
66
+
67
+ /* Disabled */
68
+ .date-input:disabled {
69
+ background-color: var(--color-surface);
70
+ color: var(--color-muted);
71
+ cursor: not-allowed;
72
+ }
73
+
74
+ /* Calendar icon (WebKit browsers like Chrome/Safari) */
75
+ .date-input::-webkit-calendar-picker-indicator {
76
+ cursor: pointer;
77
+ filter: invert(36%) sepia(65%) saturate(325%) hue-rotate(180deg);
78
+ opacity: 0.7;
79
+ transition: opacity 0.2s ease;
80
+ }
81
+ .date-input::-webkit-calendar-picker-indicator:hover {
82
+ opacity: 1;
83
+ }
84
+ </style>
@@ -0,0 +1,74 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch, computed } from 'vue';
3
+ import type { ResumeDate } from './view/Dates.vue';
4
+
5
+ export interface DateRangePickerProps {
6
+ month?: boolean;
7
+ }
8
+
9
+ defineProps<DateRangePickerProps>();
10
+
11
+ const dates = defineModel<ResumeDate>('dates', { required: true });
12
+
13
+ const present = ref(dates.value.endDate !== '' && !dates.value.endDate);
14
+
15
+ watch(present, (value) => {
16
+ if (value) {
17
+ dates.value.endDate = null; // Set end date to null when present is checked
18
+ } else {
19
+ dates.value.endDate = ''; // Reset end date when present is unchecked
20
+ }
21
+ });
22
+
23
+ const dateIsCorrect = computed(() => {
24
+ // Ensure that the start date is before the end date
25
+ if (dates.value.startDate && dates.value.endDate) {
26
+ return new Date(dates.value.startDate) <= new Date(dates.value.endDate);
27
+ }
28
+ return true; // If one of the dates is empty, consider it correct
29
+ });
30
+
31
+ defineExpose({ dateIsCorrect });
32
+ </script>
33
+
34
+ <template>
35
+ <div>
36
+ <orio-control-element>
37
+ <div class="date-range-picker">
38
+ <orio-date-picker v-model:date="dates.startDate" :month />
39
+ <orio-date-picker v-model:date="dates.endDate" :month />
40
+ <orio-check-box v-model="present"> Present </orio-check-box>
41
+ </div>
42
+ </orio-control-element>
43
+ <div v-if="!dateIsCorrect" class="error-message">
44
+ <p>Start date must be before end date.</p>
45
+ </div>
46
+ </div>
47
+ </template>
48
+
49
+ <style lang="scss" scoped>
50
+ .date-range-picker {
51
+ display: flex;
52
+ align-items: center;
53
+ flex-wrap: wrap;
54
+
55
+ & > * {
56
+ min-width: 0;
57
+ }
58
+
59
+ .date-picker {
60
+ margin-inline: 0;
61
+ }
62
+ .date-picker:first-child {
63
+ margin-inline-end: 0.5rem;
64
+ }
65
+ .checkbox {
66
+ margin-inline-start: 0.25rem;
67
+ }
68
+ }
69
+ .error-message {
70
+ color: red;
71
+ font-size: 0.875rem;
72
+ margin-top: 0.5rem;
73
+ }
74
+ </style>
@@ -0,0 +1,87 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ icon?: string;
4
+ title: string;
5
+ description?: string;
6
+ size?: 'small' | 'medium' | 'large';
7
+ }
8
+
9
+ const props = withDefaults(defineProps<Props>(), {
10
+ size: 'medium',
11
+ });
12
+ </script>
13
+
14
+ <template>
15
+ <div class="empty-state" :class="size">
16
+ <orio-icon v-if="icon" :name="icon" class="empty-state-icon" />
17
+ <orio-view-text
18
+ type="title"
19
+ :size="size === 'large' ? 'medium' : 'small'"
20
+ class="empty-state-title"
21
+ >
22
+ {{ title }}
23
+ </orio-view-text>
24
+ <orio-view-text
25
+ v-if="description"
26
+ type="subtitle"
27
+ class="empty-state-description"
28
+ >
29
+ {{ description }}
30
+ </orio-view-text>
31
+ <slot />
32
+ </div>
33
+ </template>
34
+
35
+ <style scoped lang="scss">
36
+ .empty-state {
37
+ display: flex;
38
+ flex-direction: column;
39
+ align-items: center;
40
+ justify-content: center;
41
+ text-align: center;
42
+ gap: 0.5rem;
43
+ padding: 2rem;
44
+ color: var(--color-text-secondary);
45
+
46
+ &.small {
47
+ padding: 1rem;
48
+ gap: 0.25rem;
49
+
50
+ .empty-state-icon {
51
+ font-size: 2rem;
52
+ }
53
+ }
54
+
55
+ &.medium {
56
+ padding: 2rem;
57
+ gap: 0.5rem;
58
+
59
+ .empty-state-icon {
60
+ font-size: 3rem;
61
+ }
62
+ }
63
+
64
+ &.large {
65
+ padding: 3rem;
66
+ gap: 1rem;
67
+
68
+ .empty-state-icon {
69
+ font-size: 4rem;
70
+ }
71
+ }
72
+ }
73
+
74
+ .empty-state-icon {
75
+ color: var(--color-text-tertiary);
76
+ opacity: 0.5;
77
+ }
78
+
79
+ .empty-state-title {
80
+ color: var(--color-text-secondary);
81
+ }
82
+
83
+ .empty-state-description {
84
+ color: var(--color-text-tertiary);
85
+ max-width: 30ch;
86
+ }
87
+ </style>
@@ -0,0 +1,51 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { iconRegistry, type IconName } from '../utils/icon-registry'
4
+
5
+ export interface IconProps {
6
+ name: IconName | string
7
+ size?: string | number
8
+ color?: string
9
+ }
10
+
11
+ const props = withDefaults(defineProps<IconProps>(), {
12
+ size: '1em',
13
+ color: 'currentColor'
14
+ })
15
+
16
+ const iconSvg = computed(() => iconRegistry[props.name] || '')
17
+ const sizeValue = computed(() =>
18
+ typeof props.size === 'number' ? `${props.size}px` : props.size
19
+ )
20
+ </script>
21
+
22
+ <template>
23
+ <span
24
+ class="orio-icon"
25
+ :style="{
26
+ width: sizeValue,
27
+ height: sizeValue,
28
+ color: color,
29
+ display: 'inline-flex',
30
+ alignItems: 'center',
31
+ justifyContent: 'center',
32
+ flexShrink: 0
33
+ }"
34
+ v-html="iconSvg"
35
+ />
36
+ </template>
37
+
38
+ <style scoped>
39
+ .orio-icon {
40
+ display: inline-flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ flex-shrink: 0;
44
+ }
45
+
46
+ .orio-icon :deep(svg) {
47
+ width: 100%;
48
+ height: 100%;
49
+ fill: currentColor;
50
+ }
51
+ </style>
@@ -0,0 +1,54 @@
1
+ <script setup lang="ts">
2
+ defineEmits<{
3
+ (e: 'input', value: string): void;
4
+ }>();
5
+
6
+ const text = defineModel<string>({ default: '' });
7
+ </script>
8
+
9
+ <template>
10
+ <orio-control-element v-bind="$attrs">
11
+ <input
12
+ v-bind="$attrs"
13
+ v-model="text"
14
+ type="text"
15
+ class="text-input"
16
+ >
17
+ </orio-control-element>
18
+ </template>
19
+
20
+ <style lang="scss" scoped>
21
+ .text-input {
22
+ width: 100%;
23
+ padding: 0.5rem 0.75rem;
24
+ border: 1px solid var(--color-border);
25
+ border-radius: 4px;
26
+ font-size: 1rem;
27
+ color: var(--color-text);
28
+ background-color: var(--color-bg);
29
+ box-sizing: border-box;
30
+ transition:
31
+ border-color 0.2s ease,
32
+ box-shadow 0.2s ease;
33
+
34
+ &::placeholder {
35
+ color: var(--color-muted);
36
+ }
37
+
38
+ &:hover {
39
+ border-color: var(--color-accent);
40
+ }
41
+
42
+ &:focus {
43
+ border-color: var(--color-accent);
44
+ box-shadow: 0 0 0 2px var(--color-accent-soft);
45
+ outline: none;
46
+ }
47
+
48
+ &:disabled {
49
+ background-color: var(--color-surface);
50
+ color: var(--color-muted);
51
+ cursor: not-allowed;
52
+ }
53
+ }
54
+ </style>
@@ -0,0 +1,6 @@
1
+ <script setup lang="ts">
2
+ // Loading spinner using the bundled loading-loop icon
3
+ </script>
4
+ <template>
5
+ <orio-icon name="loading-loop" />
6
+ </template>
@@ -0,0 +1,111 @@
1
+ <script setup lang="ts">
2
+ import { ref, nextTick, watch } from 'vue';
3
+ import { useWindowSize, useTimeoutFn } from '@vueuse/core';
4
+
5
+ export interface OriginRect {
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ }
11
+
12
+ const props = defineProps<{
13
+ origin: OriginRect | null;
14
+ }>();
15
+
16
+ const show = defineModel<Boolean>('show');
17
+
18
+ const wrapper = ref<HTMLDivElement | null>(null);
19
+
20
+ const { width: windowWidth, height: windowHeight } = useWindowSize();
21
+
22
+ function animateToCenter(el: HTMLDivElement) {
23
+ useTimeoutFn(() => {
24
+ requestAnimationFrame(() => {
25
+ el.style.transform = 'translate(0, 0) scale(1)';
26
+ el.style.opacity = '1';
27
+ });
28
+ }, 100);
29
+ }
30
+
31
+ async function animateOpen() {
32
+ await nextTick();
33
+
34
+ const el = wrapper.value;
35
+ if (!el) return;
36
+ if (!props.origin) {
37
+ animateToCenter(el);
38
+ return;
39
+ }
40
+
41
+ const { x, y, width, height } = props.origin;
42
+
43
+ el.style.transform = `
44
+ translate(${x - windowWidth.value / 2}px, ${y - windowHeight.value / 2}px)
45
+ scale(${width / el.offsetWidth}, ${height / el.offsetHeight})
46
+ `;
47
+ el.style.opacity = '0.5';
48
+
49
+ animateToCenter(el);
50
+ }
51
+
52
+ watch(show, (open) => {
53
+ if (open) animateOpen();
54
+ });
55
+ </script>
56
+
57
+ <template>
58
+ <transition name="overlay-fade">
59
+ <div v-if="show" class="overlay" @click.self="show = false">
60
+ <div ref="wrapper" class="modal">
61
+ <slot />
62
+ </div>
63
+ </div>
64
+ </transition>
65
+ </template>
66
+
67
+ <style scoped>
68
+ .overlay {
69
+ position: fixed;
70
+ inset: 0;
71
+ background: rgba(0, 0, 0, 0.45);
72
+ backdrop-filter: blur(6px);
73
+
74
+ display: flex;
75
+ justify-content: center;
76
+ align-items: center;
77
+
78
+ transition: opacity 0.25s ease;
79
+ }
80
+
81
+ .modal {
82
+ position: absolute;
83
+
84
+ width: 380px;
85
+ max-width: 90vw;
86
+ max-height: 90vh;
87
+
88
+ background: white;
89
+ border-radius: 1rem;
90
+ padding: 24px;
91
+
92
+ transform-origin: top left;
93
+ box-shadow: 0 25px 60px rgba(0, 0, 0, 0.25);
94
+
95
+ transition:
96
+ transform 0.35s cubic-bezier(0.2, 0.8, 0.2, 1),
97
+ opacity 0.3s ease;
98
+
99
+ color: var(--color-muted);
100
+ opacity: 0;
101
+ }
102
+
103
+ .overlay-fade-enter-from,
104
+ .overlay-fade-leave-to {
105
+ opacity: 0;
106
+ }
107
+ .overlay-fade-enter-active,
108
+ .overlay-fade-leave-active {
109
+ transition: opacity 0.25s ease;
110
+ }
111
+ </style>