vome-core 0.0.26 → 0.0.31

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 (67) hide show
  1. package/dist/admin/components/vm-action-btn.vue +35 -8
  2. package/dist/admin/components/vm-aside-list-item.vue +18 -6
  3. package/dist/admin/components/vm-card-grid.vue +3 -2
  4. package/dist/admin/components/vm-card-head.vue +45 -0
  5. package/dist/admin/components/vm-card-meta.vue +22 -0
  6. package/dist/admin/components/vm-card-title.vue +56 -0
  7. package/dist/admin/components/vm-card.vue +72 -2
  8. package/dist/admin/components/vm-clamp-text.vue +39 -0
  9. package/dist/admin/components/vm-icon-btn.vue +88 -0
  10. package/dist/admin/components/vm-inline-search.vue +60 -0
  11. package/dist/admin/components/vm-page-tabs.vue +93 -0
  12. package/dist/admin/components/vm-split-layout.vue +36 -15
  13. package/dist/admin/components/vm-thumb.vue +49 -0
  14. package/dist/admin/config/plugin-dev.js +1 -1
  15. package/dist/admin/crud/components/vm-form-hint.vue +40 -22
  16. package/dist/admin/crud/components/vm-kv-row.vue +51 -0
  17. package/dist/admin/crud/components/vm-tag-list.vue +43 -17
  18. package/dist/admin/crud/components/vm-toggle-switch.vue +84 -0
  19. package/dist/admin/crud/config.js +1 -1
  20. package/dist/admin/crud/confirm.js +1 -1
  21. package/dist/admin/crud/dict.js +1 -1
  22. package/dist/admin/crud/index.d.ts +3 -1
  23. package/dist/admin/crud/index.js +1 -1
  24. package/dist/admin/crud/key.js +1 -1
  25. package/dist/admin/crud/mitt.js +1 -1
  26. package/dist/admin/crud/plugins.js +1 -1
  27. package/dist/admin/crud/span.js +1 -1
  28. package/dist/admin/crud/style.js +1 -1
  29. package/dist/admin/crud/validate.js +1 -1
  30. package/dist/admin/crud/vm-import-btn.vue +686 -0
  31. package/dist/admin/crud/vm-toolbar.vue +16 -0
  32. package/dist/admin/directives/perm.js +1 -1
  33. package/dist/admin/hooks/useUpload.js +1 -1
  34. package/dist/admin/lib/browser.js +1 -1
  35. package/dist/admin/lib/cn.js +1 -1
  36. package/dist/admin/lib/dialog-float.js +1 -1
  37. package/dist/admin/lib/export-excel.js +1 -1
  38. package/dist/admin/lib/file-preview.js +1 -1
  39. package/dist/admin/lib/import-excel.d.ts +18 -0
  40. package/dist/admin/lib/import-excel.js +1 -0
  41. package/dist/admin/lib/json.js +1 -1
  42. package/dist/admin/lib/menu.js +1 -1
  43. package/dist/admin/lib/tree.js +1 -1
  44. package/dist/admin/lib/upload.js +1 -1
  45. package/dist/admin/lib/video-frame.js +1 -1
  46. package/dist/admin/router/index.js +13 -3
  47. package/dist/client/vite-plugin-eps.d.ts +3 -3
  48. package/dist/client/vite-plugin-eps.js +6 -2
  49. package/dist/index.js +1 -1
  50. package/dist/server/index.js +1 -1
  51. package/dist/shared/excel.d.ts +10 -0
  52. package/dist/shared/excel.js +1 -1
  53. package/dist/shared/index.js +1 -1
  54. package/dist/shared/tree.js +1 -1
  55. package/dist/src/core/index.d.ts +1 -1
  56. package/dist/src/orm/import-excel.d.ts +31 -0
  57. package/dist/src/routing/index.d.ts +1 -0
  58. package/dist/src/routing/public-routes.d.ts +6 -0
  59. package/dist/src/server/index.d.ts +2 -2
  60. package/dist/src/shared/excel.d.ts +10 -0
  61. package/dist/src/shared/index.d.ts +1 -1
  62. package/dist/typings/routing/crud.d.ts +5 -0
  63. package/package.json +8 -3
  64. package/src/shared/excel.d.ts +10 -0
  65. package/src/shared/excel.ts +77 -0
  66. package/src/shared/index.ts +4 -0
  67. package/dist/admin/crud/components/vm-json-viewer.vue +0 -147
@@ -2,7 +2,7 @@
2
2
  <button
3
3
  type="button"
4
4
  class="vm-action-btn"
5
- :class="[`is-${variant}`]"
5
+ :class="[`is-${variant}`, `is-${size}`]"
6
6
  :disabled="disabled || loading"
7
7
  @click="emit('click', $event)"
8
8
  >
@@ -19,13 +19,15 @@ withDefaults(
19
19
  label?: string
20
20
  loadingText?: string
21
21
  icon?: string
22
- variant?: 'default' | 'primary' | 'success' | 'brand' | 'danger'
22
+ variant?: 'default' | 'primary' | 'success' | 'brand' | 'danger' | 'outline'
23
+ size?: 'md' | 'sm'
23
24
  loading?: boolean
24
25
  disabled?: boolean
25
26
  }>(),
26
27
  {
27
28
  label: '',
28
29
  variant: 'default',
30
+ size: 'md',
29
31
  loading: false,
30
32
  disabled: false,
31
33
  },
@@ -39,37 +41,62 @@ const emit = defineEmits<{
39
41
  <style lang="scss" scoped>
40
42
  .vm-action-btn {
41
43
  display: inline-flex;
42
- height: 36px;
43
44
  align-items: center;
44
45
  gap: 4px;
45
- padding: 0 14px;
46
46
  border: none;
47
47
  border-radius: 12px;
48
48
  background: var(--muted);
49
49
  color: var(--foreground);
50
- font-size: 13px;
51
50
  font-weight: 600;
52
51
  cursor: pointer;
53
52
 
53
+ &.is-md {
54
+ height: 36px;
55
+ padding: 0 14px;
56
+ font-size: 13px;
57
+ }
58
+
59
+ &.is-sm {
60
+ height: 26px;
61
+ padding: 0 10px;
62
+ border-radius: 8px;
63
+ font-size: 12px;
64
+ }
65
+
54
66
  &:hover:not(:disabled) {
55
67
  background: color-mix(in srgb, var(--foreground) 8%, var(--muted));
56
68
  }
57
69
 
70
+ &.is-outline {
71
+ border: 1px solid color-mix(in srgb, var(--foreground) 12%, transparent);
72
+ background: var(--card);
73
+
74
+ &:hover:not(:disabled) {
75
+ border-color: color-mix(in srgb, var(--brand) 40%, transparent);
76
+ background: var(--card);
77
+ color: var(--brand);
78
+ }
79
+ }
80
+
58
81
  &.is-primary {
82
+ border: 1px solid var(--brand);
59
83
  background: var(--brand);
60
84
  color: #fff;
61
85
 
62
86
  &:hover:not(:disabled) {
63
87
  background: var(--brand-deep);
88
+ color: #fff;
64
89
  }
65
90
  }
66
91
 
67
92
  &.is-success {
68
- background: color-mix(in srgb, var(--success) 14%, var(--card));
69
- color: var(--success);
93
+ border: 1px solid var(--success);
94
+ background: var(--success);
95
+ color: #fff;
70
96
 
71
97
  &:hover:not(:disabled) {
72
- background: color-mix(in srgb, var(--success) 22%, var(--card));
98
+ filter: brightness(0.95);
99
+ color: #fff;
73
100
  }
74
101
  }
75
102
 
@@ -2,7 +2,10 @@
2
2
  <button
3
3
  type="button"
4
4
  class="vm-aside-list-item"
5
- :class="{ 'is-active': active }"
5
+ :class="{
6
+ 'is-active': active,
7
+ 'has-arrow': active && showArrow,
8
+ }"
6
9
  @click="emit('click', $event)"
7
10
  >
8
11
  <span class="vm-aside-list-item__body">
@@ -42,21 +45,26 @@ const emit = defineEmits<{
42
45
 
43
46
  <style lang="scss" scoped>
44
47
  .vm-aside-list-item {
48
+ position: relative;
45
49
  display: flex;
46
50
  width: 100%;
47
51
  align-items: center;
48
52
  gap: 6px;
49
53
  margin-bottom: 4px;
50
- padding: 10px 10px 10px 12px;
54
+ padding: 10px 12px;
51
55
  border: none;
52
- border-radius: 12px;
56
+ border-radius: 10px;
53
57
  background: transparent;
54
58
  text-align: left;
55
59
  cursor: pointer;
56
60
  transition: background 0.15s ease;
57
61
 
62
+ &.has-arrow {
63
+ padding-right: 28px;
64
+ }
65
+
58
66
  &:hover {
59
- background: var(--muted);
67
+ background: color-mix(in srgb, var(--brand) 8%, transparent);
60
68
 
61
69
  .vm-aside-list-item__ops {
62
70
  opacity: 1;
@@ -71,7 +79,8 @@ const emit = defineEmits<{
71
79
  color: color-mix(in srgb, #fff 75%, transparent);
72
80
  }
73
81
 
74
- :deep(.vm-aside__icon-btn) {
82
+ :deep(.vm-aside__icon-btn),
83
+ :deep(.vm-icon-btn) {
75
84
  color: #fff;
76
85
 
77
86
  &:hover:not(:disabled) {
@@ -118,7 +127,10 @@ const emit = defineEmits<{
118
127
  }
119
128
 
120
129
  .vm-aside-list-item__arrow {
121
- flex-shrink: 0;
130
+ position: absolute;
131
+ top: 50%;
132
+ right: 8px;
122
133
  font-size: 16px;
134
+ transform: translateY(-50%);
123
135
  }
124
136
  </style>
@@ -29,8 +29,9 @@ const gridStyle = computed(() => ({
29
29
  <style lang="scss" scoped>
30
30
  .vm-card-grid {
31
31
  display: grid;
32
- grid-template-columns: repeat(auto-fill, minmax(var(--vm-card-min), 1fr));
33
- gap: var(--vm-card-gap);
32
+ width: 100%;
33
+ grid-template-columns: repeat(auto-fill, minmax(var(--vm-card-min, 280px), 1fr));
34
+ gap: var(--vm-card-gap, 16px);
34
35
  align-content: start;
35
36
  }
36
37
  </style>
@@ -0,0 +1,45 @@
1
+ <template>
2
+ <div class="vm-card-head">
3
+ <div v-if="$slots.media" class="vm-card-head__media">
4
+ <slot name="media" />
5
+ </div>
6
+ <div v-if="$slots.tags" class="vm-card-head__tags">
7
+ <slot name="tags" />
8
+ </div>
9
+ <div v-if="$slots.actions" class="vm-card-head__actions">
10
+ <slot name="actions" />
11
+ </div>
12
+ </div>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ defineOptions({ name: 'vm-card-head' })
17
+ </script>
18
+
19
+ <style lang="scss" scoped>
20
+ .vm-card-head {
21
+ display: flex;
22
+ align-items: flex-start;
23
+ gap: 10px;
24
+ margin-bottom: 8px;
25
+ }
26
+
27
+ .vm-card-head__media {
28
+ flex-shrink: 0;
29
+ }
30
+
31
+ .vm-card-head__tags {
32
+ display: flex;
33
+ min-width: 0;
34
+ flex: 1;
35
+ flex-wrap: wrap;
36
+ gap: 4px;
37
+ padding-top: 2px;
38
+ }
39
+
40
+ .vm-card-head__actions {
41
+ display: flex;
42
+ flex-shrink: 0;
43
+ gap: 2px;
44
+ }
45
+ </style>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <p class="vm-card-meta">
3
+ <slot>{{ text }}</slot>
4
+ </p>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ defineOptions({ name: 'vm-card-meta' })
9
+
10
+ defineProps<{
11
+ text?: string
12
+ }>()
13
+ </script>
14
+
15
+ <style lang="scss" scoped>
16
+ .vm-card-meta {
17
+ margin: 8px 0 0;
18
+ color: var(--muted-foreground);
19
+ font-size: 10px;
20
+ line-height: 1.45;
21
+ }
22
+ </style>
@@ -0,0 +1,56 @@
1
+ <template>
2
+ <component
3
+ :is="clickable ? 'button' : 'h3'"
4
+ class="vm-card-title"
5
+ :type="clickable ? 'button' : undefined"
6
+ :title="title"
7
+ @click="clickable ? emit('click', $event) : undefined"
8
+ >
9
+ <slot>{{ text }}</slot>
10
+ </component>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ defineOptions({ name: 'vm-card-title' })
15
+
16
+ withDefaults(
17
+ defineProps<{
18
+ text?: string
19
+ title?: string
20
+ clickable?: boolean
21
+ }>(),
22
+ {
23
+ clickable: false,
24
+ },
25
+ )
26
+
27
+ const emit = defineEmits<{
28
+ click: [e: MouseEvent]
29
+ }>()
30
+ </script>
31
+
32
+ <style lang="scss" scoped>
33
+ .vm-card-title {
34
+ margin: 0 0 4px;
35
+ overflow: hidden;
36
+ border: none;
37
+ background: transparent;
38
+ color: var(--foreground);
39
+ font-size: 14px;
40
+ font-weight: 700;
41
+ line-height: 1.35;
42
+ text-align: left;
43
+ text-overflow: ellipsis;
44
+ white-space: nowrap;
45
+ }
46
+
47
+ button.vm-card-title {
48
+ width: 100%;
49
+ padding: 0;
50
+ cursor: pointer;
51
+
52
+ &:hover {
53
+ color: var(--brand);
54
+ }
55
+ }
56
+ </style>
@@ -1,5 +1,8 @@
1
1
  <template>
2
- <article class="vm-card" :class="{ 'is-add': add }">
2
+ <article
3
+ class="vm-card"
4
+ :class="{ 'is-add': add, 'is-bordered': bordered, 'is-compact': compact }"
5
+ >
3
6
  <div v-if="$slots.toolbar" class="vm-card__toolbar">
4
7
  <slot name="toolbar" />
5
8
  </div>
@@ -7,11 +10,16 @@
7
10
  :is="add ? 'button' : 'div'"
8
11
  class="vm-card__inner"
9
12
  :type="add ? 'button' : undefined"
13
+ :disabled="add && disabled"
10
14
  @click="add ? emit('click', $event) : undefined"
11
15
  >
12
16
  <slot />
13
17
  </component>
14
- <footer v-if="$slots.footer" class="vm-card__foot">
18
+ <footer
19
+ v-if="$slots.footer"
20
+ class="vm-card__foot"
21
+ :class="{ 'is-end': footerEnd }"
22
+ >
15
23
  <slot name="footer" />
16
24
  </footer>
17
25
  </article>
@@ -23,9 +31,17 @@ defineOptions({ name: 'vm-card' })
23
31
  withDefaults(
24
32
  defineProps<{
25
33
  add?: boolean
34
+ bordered?: boolean
35
+ compact?: boolean
36
+ footerEnd?: boolean
37
+ disabled?: boolean
26
38
  }>(),
27
39
  {
28
40
  add: false,
41
+ bordered: false,
42
+ compact: false,
43
+ footerEnd: false,
44
+ disabled: false,
29
45
  },
30
46
  )
31
47
 
@@ -44,11 +60,55 @@ const emit = defineEmits<{
44
60
  background: var(--card);
45
61
  box-shadow: var(--shadow-soft);
46
62
  overflow: hidden;
63
+ text-align: left;
64
+
65
+ &.is-bordered {
66
+ border: 1px solid color-mix(in srgb, var(--foreground) 6%, transparent);
67
+ }
68
+
69
+ &.is-compact {
70
+ min-height: 180px;
71
+ border-radius: 14px;
72
+
73
+ .vm-card__inner {
74
+ padding: 12px;
75
+ }
76
+
77
+ .vm-card__foot {
78
+ padding: 10px 12px 12px;
79
+ }
80
+ }
47
81
 
48
82
  &.is-add {
49
83
  border: 1px dashed color-mix(in srgb, var(--brand) 36%, transparent);
50
84
  background: color-mix(in srgb, var(--brand) 4%, var(--card));
51
85
  box-shadow: none;
86
+
87
+ &.is-compact {
88
+ min-height: 180px;
89
+ border-style: solid;
90
+ border-color: color-mix(in srgb, var(--foreground) 6%, transparent);
91
+ background: var(--card);
92
+
93
+ .vm-card__inner {
94
+ align-items: center;
95
+ justify-content: center;
96
+ gap: 6px;
97
+ color: var(--muted-foreground);
98
+ font-size: 12px;
99
+ font-weight: 600;
100
+
101
+ i {
102
+ font-size: 32px;
103
+ line-height: 1;
104
+ }
105
+
106
+ &:hover {
107
+ border-color: color-mix(in srgb, var(--brand) 45%, transparent);
108
+ color: var(--brand);
109
+ }
110
+ }
111
+ }
52
112
  }
53
113
  }
54
114
 
@@ -86,6 +146,11 @@ const emit = defineEmits<{
86
146
  i {
87
147
  font-size: 28px;
88
148
  }
149
+
150
+ &:disabled {
151
+ opacity: 0.6;
152
+ cursor: not-allowed;
153
+ }
89
154
  }
90
155
  }
91
156
 
@@ -94,7 +159,12 @@ const emit = defineEmits<{
94
159
  align-items: center;
95
160
  justify-content: space-between;
96
161
  gap: 8px;
162
+ margin-top: auto;
97
163
  padding: 10px 16px 14px;
98
164
  border-top: 1px solid color-mix(in srgb, var(--foreground) 6%, transparent);
165
+
166
+ &.is-end {
167
+ justify-content: flex-end;
168
+ }
99
169
  }
100
170
  </style>
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <p class="vm-clamp-text" :style="lineStyle">
3
+ <slot>{{ text }}</slot>
4
+ </p>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from 'vue'
9
+
10
+ defineOptions({ name: 'vm-clamp-text' })
11
+
12
+ const props = withDefaults(
13
+ defineProps<{
14
+ text?: string
15
+ lines?: number
16
+ }>(),
17
+ {
18
+ lines: 2,
19
+ },
20
+ )
21
+
22
+ const lineStyle = computed(() => ({
23
+ '--vm-clamp-lines': String(props.lines),
24
+ }))
25
+ </script>
26
+
27
+ <style lang="scss" scoped>
28
+ .vm-clamp-text {
29
+ display: -webkit-box;
30
+ margin: 0;
31
+ overflow: hidden;
32
+ color: var(--muted-foreground);
33
+ font-size: 11px;
34
+ line-height: 1.5;
35
+ -webkit-box-orient: vertical;
36
+ -webkit-line-clamp: var(--vm-clamp-lines, 2);
37
+ line-clamp: var(--vm-clamp-lines, 2);
38
+ }
39
+ </style>
@@ -0,0 +1,88 @@
1
+ <template>
2
+ <button
3
+ type="button"
4
+ class="vm-icon-btn"
5
+ :class="[`is-${size}`, variant ? `is-${variant}` : '']"
6
+ :title="title"
7
+ :disabled="disabled || loading"
8
+ @click="emit('click', $event)"
9
+ >
10
+ <i :class="[icon, { 'is-spin': loading }]" aria-hidden="true" />
11
+ </button>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ defineOptions({ name: 'vm-icon-btn' })
16
+
17
+ withDefaults(
18
+ defineProps<{
19
+ icon: string
20
+ title?: string
21
+ size?: 'md' | 'sm'
22
+ variant?: 'default' | 'danger'
23
+ loading?: boolean
24
+ disabled?: boolean
25
+ }>(),
26
+ {
27
+ size: 'md',
28
+ variant: 'default',
29
+ loading: false,
30
+ disabled: false,
31
+ },
32
+ )
33
+
34
+ const emit = defineEmits<{
35
+ click: [e: MouseEvent]
36
+ }>()
37
+ </script>
38
+
39
+ <style lang="scss" scoped>
40
+ .vm-icon-btn {
41
+ display: inline-flex;
42
+ align-items: center;
43
+ justify-content: center;
44
+ border: none;
45
+ border-radius: 8px;
46
+ background: transparent;
47
+ color: var(--muted-foreground);
48
+ cursor: pointer;
49
+
50
+ &.is-md {
51
+ width: 28px;
52
+ height: 28px;
53
+ font-size: 16px;
54
+ }
55
+
56
+ &.is-sm {
57
+ width: 22px;
58
+ height: 22px;
59
+ border-radius: 6px;
60
+ font-size: 14px;
61
+ }
62
+
63
+ &:hover:not(:disabled) {
64
+ background: var(--muted);
65
+ color: var(--foreground);
66
+ }
67
+
68
+ &.is-danger:hover:not(:disabled) {
69
+ background: var(--danger-soft);
70
+ color: var(--danger);
71
+ }
72
+
73
+ &:disabled {
74
+ opacity: 0.5;
75
+ cursor: not-allowed;
76
+ }
77
+
78
+ i.is-spin {
79
+ animation: vm-icon-btn-spin 0.8s linear infinite;
80
+ }
81
+ }
82
+
83
+ @keyframes vm-icon-btn-spin {
84
+ to {
85
+ transform: rotate(360deg);
86
+ }
87
+ }
88
+ </style>
@@ -0,0 +1,60 @@
1
+ <template>
2
+ <label class="vm-inline-search">
3
+ <i class="ri-search-line" aria-hidden="true" />
4
+ <input
5
+ :value="modelValue"
6
+ type="search"
7
+ :placeholder="placeholder"
8
+ @input="onInput"
9
+ @keydown.enter="emit('search')"
10
+ />
11
+ </label>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ defineOptions({ name: 'vm-inline-search' })
16
+
17
+ withDefaults(
18
+ defineProps<{
19
+ modelValue?: string
20
+ placeholder?: string
21
+ }>(),
22
+ {
23
+ modelValue: '',
24
+ placeholder: '搜索',
25
+ },
26
+ )
27
+
28
+ const emit = defineEmits<{
29
+ 'update:modelValue': [v: string]
30
+ search: []
31
+ }>()
32
+
33
+ function onInput(e: Event) {
34
+ emit('update:modelValue', (e.target as HTMLInputElement).value.trim())
35
+ }
36
+ </script>
37
+
38
+ <style lang="scss" scoped>
39
+ .vm-inline-search {
40
+ display: flex;
41
+ height: 36px;
42
+ min-width: 220px;
43
+ align-items: center;
44
+ gap: 8px;
45
+ padding: 0 12px;
46
+ border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent);
47
+ border-radius: 12px;
48
+ background: var(--card);
49
+ color: var(--muted-foreground);
50
+
51
+ input {
52
+ width: 100%;
53
+ border: 0;
54
+ background: transparent;
55
+ color: var(--foreground);
56
+ font-size: 13px;
57
+ outline: none;
58
+ }
59
+ }
60
+ </style>
@@ -0,0 +1,93 @@
1
+ <template>
2
+ <div class="vm-page-tabs" :class="[`is-${variant}`]" role="tablist">
3
+ <button
4
+ v-for="tab in tabs"
5
+ :key="tab.key"
6
+ type="button"
7
+ role="tab"
8
+ class="vm-page-tabs__item"
9
+ :class="{ 'is-active': modelValue === tab.key }"
10
+ :aria-selected="modelValue === tab.key"
11
+ @click="emit('update:modelValue', tab.key)"
12
+ >
13
+ {{ tab.label }}
14
+ </button>
15
+ </div>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ defineOptions({ name: 'vm-page-tabs' })
20
+
21
+ export type PageTab = { key: string; label: string }
22
+
23
+ withDefaults(
24
+ defineProps<{
25
+ modelValue: string
26
+ tabs: PageTab[]
27
+ variant?: 'pill' | 'segmented'
28
+ }>(),
29
+ {
30
+ variant: 'pill',
31
+ },
32
+ )
33
+
34
+ const emit = defineEmits<{
35
+ 'update:modelValue': [key: string]
36
+ }>()
37
+ </script>
38
+
39
+ <style lang="scss" scoped>
40
+ .vm-page-tabs {
41
+ display: flex;
42
+ flex-wrap: wrap;
43
+ gap: 8px;
44
+
45
+ &.is-segmented {
46
+ gap: 4px;
47
+ padding: 4px;
48
+ border-radius: 12px;
49
+ background: color-mix(in srgb, var(--foreground) 5%, var(--card));
50
+
51
+ .vm-page-tabs__item {
52
+ height: 32px;
53
+ padding: 0 14px;
54
+ border: none;
55
+ border-radius: 8px;
56
+ background: transparent;
57
+
58
+ &.is-active {
59
+ background: var(--card);
60
+ color: var(--brand);
61
+ box-shadow: var(--shadow-soft);
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ .vm-page-tabs__item {
68
+ height: 34px;
69
+ padding: 0 16px;
70
+ border: 1px solid color-mix(in srgb, var(--foreground) 12%, transparent);
71
+ border-radius: 999px;
72
+ background: var(--card);
73
+ color: var(--muted-foreground);
74
+ font-size: 13px;
75
+ font-weight: 600;
76
+ cursor: pointer;
77
+ transition:
78
+ border-color 0.15s ease,
79
+ background 0.15s ease,
80
+ color 0.15s ease;
81
+
82
+ &:hover:not(.is-active) {
83
+ border-color: color-mix(in srgb, var(--brand) 40%, transparent);
84
+ color: var(--brand);
85
+ }
86
+
87
+ &.is-active {
88
+ border-color: var(--brand);
89
+ background: var(--brand);
90
+ color: #fff;
91
+ }
92
+ }
93
+ </style>