vue3-router-tab 1.2.0 → 1.2.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.
@@ -1,80 +1,47 @@
1
1
  @use "sass:math";
2
2
  @use "sass:color";
3
+ @use "variables" as *;
3
4
 
4
- $default-primary: #0f172a;
5
- $default-border: #e2e8f0;
6
- $default-text: #1e293b;
7
- $default-bg: #f8fafc;
8
- $default-dark-bg: #0f172a;
9
- $default-dark-text: #e2e8f0;
10
- $default-dark-border: rgba(148, 163, 184, 0.35);
11
-
12
- $font-size: 14px;
13
- $tab-trans: all 0.3s ease-in-out;
14
- $hd-height: 40px;
15
- $tab-padding: 20px;
16
- $close-icon-margin: 4px;
17
- $close-icon-size: 13px;
5
+ // =============================================================================
6
+ // CSS Custom Properties & Theme System
7
+ // =============================================================================
18
8
 
19
9
  :root {
20
- --theme-primary: #{$default-primary};
21
- --router-tab-primary: var(--theme-primary);
22
- --router-tab-background: #{$default-bg};
23
- --router-tab-foreground: #{$default-text};
24
- --router-tab-border: #{$default-border};
25
- --router-tab-header-bg: #{$default-bg};
26
- color-scheme: light;
27
- }
28
-
29
- :root[data-theme="light"] {
30
- color-scheme: light;
31
- --router-tab-background: #{$default-bg};
32
- --router-tab-foreground: #{$default-text};
33
- --router-tab-border: #{$default-border};
34
- --router-tab-header-bg: #{$default-bg};
10
+
11
+ // Layout variables
12
+ --router-tab-header-height: #{$router-tab-header-height};
13
+ --router-tab-padding: #{$router-tab-padding};
14
+ --router-tab-font-size: #{$router-tab-font-size};
35
15
  }
36
16
 
37
- :root[data-theme="dark"] {
38
- color-scheme: dark;
39
- --router-tab-background: #{$default-dark-bg};
40
- --router-tab-foreground: #{$default-dark-text};
41
- --router-tab-border: #{$default-dark-border};
42
- --router-tab-header-bg: #{$default-dark-bg};
43
- }
44
-
45
- @media (prefers-color-scheme: dark) {
46
- :root:not([data-theme]) {
47
- color-scheme: dark;
48
- --router-tab-background: #{$default-dark-bg};
49
- --router-tab-foreground: #{$default-dark-text};
50
- --router-tab-border: #{$default-dark-border};
51
- --router-tab-header-bg: #{$default-dark-bg};
52
- }
53
- }
17
+ // =============================================================================
18
+ // Main Component Styles
19
+ // =============================================================================
54
20
 
55
21
  .router-tab {
56
22
  display: flex;
57
23
  flex-direction: column;
58
24
  min-height: 300px;
59
25
  background-color: var(--router-tab-background);
60
- color: var(--router-tab-foreground);
26
+ color: var(--router-tab-text);
61
27
 
62
28
  &__header {
63
29
  position: relative;
64
- z-index: 9;
30
+ z-index: 10;
65
31
  display: flex;
66
32
  flex: none;
33
+ align-items: center;
67
34
  box-sizing: border-box;
68
- height: $hd-height;
35
+ height: var(--router-tab-header-height);
69
36
  background-color: var(--router-tab-header-bg);
70
- border-bottom: 1px solid var(--router-tab-border); // FIXED: Added border
71
- transition: all 0.2s ease-in-out;
37
+ border-bottom: 1px solid var(--router-tab-border);
38
+ transition: $router-tab-transition-fast;
72
39
  }
73
40
 
74
41
  &__scroll {
75
42
  position: relative;
76
43
  flex: 1 1 0px;
77
- height: $hd-height;
44
+ height: 100%;
78
45
  overflow: hidden;
79
46
 
80
47
  &-container {
@@ -85,40 +52,12 @@ $close-icon-size: 13px;
85
52
  &.is-mobile {
86
53
  overflow-x: auto;
87
54
  overflow-y: hidden;
88
- }
89
- }
90
- }
91
-
92
- &__scrollbar {
93
- $h: 3px;
94
-
95
- position: absolute;
96
- right: 0;
97
- bottom: 0;
98
- left: 0;
99
- height: $h;
100
- background-color: rgba(0, 0, 0, 0.1);
101
- border-radius: $h;
102
- opacity: 0;
103
- transition: opacity 0.3s ease-in-out;
104
-
105
- .router-tab__scroll:hover &,
106
- &.is-dragging {
107
- opacity: 1;
108
- }
55
+ scrollbar-width: none;
56
+ -ms-overflow-style: none;
109
57
 
110
- &-thumb {
111
- position: absolute;
112
- top: 0;
113
- left: 0;
114
- height: 100%;
115
- background-color: rgba(0, 0, 0, 0.1);
116
- border-radius: $h;
117
- transition: background-color 0.3s ease-in-out;
118
-
119
- &:hover,
120
- .router-tab__scrollbar.is-dragging & {
121
- background-color: rgba(0, 0, 0, 0.2);
58
+ &::-webkit-scrollbar {
59
+ display: none;
60
+ }
122
61
  }
123
62
  }
124
63
  }
@@ -138,170 +77,120 @@ $close-icon-size: 13px;
138
77
  display: flex;
139
78
  flex: none;
140
79
  align-items: center;
141
- padding: 0 $tab-padding;
142
- color: inherit;
143
- font-size: $font-size;
144
- transform-origin: left bottom;
145
- cursor: pointer;
146
- transition: $tab-trans;
147
- user-select: none;
80
+ gap: 0.5rem;
81
+ padding: 0 var(--router-tab-padding);
82
+ color: var(--router-tab-text);
83
+ font-size: var(--router-tab-font-size);
148
84
  background-color: transparent;
149
-
150
- // FIXED: Added proper borders
85
+ border: none;
151
86
  border-right: 1px solid var(--router-tab-border);
152
- border-top: 1px solid transparent;
153
- border-bottom: 2px solid transparent;
87
+ cursor: pointer;
88
+ user-select: none;
89
+ transition: var(--router-tab-transition);
154
90
 
155
91
  &:first-child {
156
92
  border-left: 1px solid var(--router-tab-border);
157
93
  }
158
94
 
159
- &.is-contextmenu {
95
+ &:hover {
96
+ background-color: color-mix(in srgb, var(--router-tab-primary) 4%, transparent);
160
97
  color: var(--router-tab-primary);
161
98
  }
162
99
 
163
- // NEW: Drag states
164
- &.is-dragging {
165
- opacity: 0.5;
166
- cursor: move;
167
- }
168
-
169
- &.is-drag-over {
170
- background: color-mix(in srgb, var(--router-tab-primary) 10%, transparent 90%);
171
- border-left: 2px solid var(--router-tab-primary);
172
- transition: background 0.15s ease, border-left 0.15s ease;
173
- }
174
-
175
- &-title {
176
- min-width: 30px;
177
- max-width: 120px;
178
- overflow: hidden;
179
- white-space: nowrap;
180
- text-overflow: ellipsis;
181
- }
182
-
183
- &-icon {
184
- margin-right: 5px;
185
- font-size: 16px;
186
- }
187
-
188
- &:hover,
189
100
  &.is-active {
190
- color: var(--router-tab-primary);
191
- background-color: color-mix(in srgb, var(--router-tab-primary) 5%, transparent 95%);
101
+ background-color: var(--router-tab-active-background);
102
+ color: var(--router-tab-active-text);
103
+ font-weight: 510;
192
104
 
193
- &.is-closable {
194
- padding: 0 ($tab-padding - math.div($close-icon-size + $close-icon-margin, 2));
105
+ &::after {
106
+ content: "";
107
+ position: absolute;
108
+ bottom: -1px;
109
+ left: 0;
110
+ right: 0;
111
+ height: 2px;
112
+ background-color: var(--router-tab-active-border);
195
113
  }
196
114
 
197
- .router-tab__item-close {
198
- width: $close-icon-size;
199
- margin-left: $close-icon-margin;
200
-
201
- &::before,
202
- &::after {
203
- background-color: currentColor;
204
- }
115
+ & + .router-tab__item {
116
+ border-left-color: transparent;
205
117
  }
206
118
  }
207
119
 
208
- // FIXED: Enhanced active state
209
- &.is-active {
210
- border-bottom: 2px solid var(--router-tab-primary);
211
- background-color: var(--router-tab-background);
212
- font-weight: 500;
120
+ // Icon styling using the dedicated color
121
+ &-icon {
122
+ flex-shrink: 0;
123
+ font-size: $router-tab-icon-size;
124
+ color: var(--router-tab-icon-color);
125
+
126
+ .router-tab__item.is-active & {
127
+ color: var(--router-tab-active-text);
128
+ }
213
129
 
214
- &:not(:first-child) {
215
- border-left-color: var(--router-tab-border);
130
+ .router-tab__item:hover & {
131
+ color: currentColor;
216
132
  }
217
133
  }
218
134
 
219
- &-close {
220
- $inner: 8px;
135
+ &-title {
136
+ min-width: $router-tab-title-min-width;
137
+ max-width: $router-tab-title-max-width;
138
+ overflow: hidden;
139
+ white-space: nowrap;
140
+ text-overflow: ellipsis;
141
+ }
221
142
 
143
+ // Close button using button colors
144
+ &-close {
145
+ $close-size: $router-tab-close-icon-size;
146
+ $inner-line: 8px;
147
+
222
148
  position: relative;
223
149
  display: block;
224
150
  width: 0;
225
- height: $close-icon-size;
226
- margin-left: 0;
151
+ height: $close-size;
152
+ flex-shrink: 0;
227
153
  overflow: hidden;
154
+ border: none;
228
155
  border-radius: 50%;
156
+ background: var(--router-tab-button-bg);
157
+ color: var(--router-tab-button-color);
229
158
  cursor: pointer;
230
- transition: $tab-trans;
231
- background: transparent;
232
- border: none;
159
+ transition: var(--router-tab-transition);
233
160
 
234
161
  &::before,
235
162
  &::after {
236
163
  position: absolute;
237
- top: 6px;
164
+ top: 50%;
238
165
  left: 50%;
239
166
  display: block;
240
- width: $inner;
167
+ width: $inner-line;
241
168
  height: 1px;
242
- margin-left: math.div(-$inner, 2);
169
+ margin-left: math.div(-$inner-line, 2);
243
170
  background-color: currentColor;
244
171
  transition: background-color 0.2s ease-in-out;
245
172
  content: "";
246
173
  }
247
174
 
248
- &::before {
249
- transform: rotate(-45deg);
250
- }
251
-
252
- &::after {
253
- transform: rotate(45deg);
254
- }
175
+ &::before { transform: translateY(-50%) rotate(-45deg); }
176
+ &::after { transform: translateY(-50%) rotate(45deg); }
255
177
 
256
178
  &:hover {
257
- background-color: color-mix(in srgb, var(--router-tab-primary) 40%, #ffffff 60%);
258
-
259
- &::before,
260
- &::after {
261
- background-color: #fff;
262
- }
179
+ background: var(--router-tab-active-button-bg);
180
+ color: var(--router-tab-active-button-color);
263
181
  }
264
182
  }
265
- }
266
-
267
- // FIXED: Improved context menu z-index
268
- &__contextmenu {
269
- position: fixed;
270
- z-index: 9999; // Increased from 1000
271
- min-width: 140px;
272
- padding: 8px 0;
273
- font-size: $font-size;
274
- background: var(--router-tab-background);
275
- border: 1px solid var(--router-tab-border);
276
- box-shadow: 0 10px 30px rgba(15, 23, 42, 0.15);
277
- border-radius: 8px;
278
183
 
279
- a,
280
- button {
281
- display: block;
282
- width: 100%;
283
- padding: 0 16px;
284
- line-height: 30px;
285
- text-align: left;
286
- background: transparent;
287
- border: none;
288
- color: inherit;
289
- cursor: pointer;
290
- font: inherit;
291
- text-decoration: none;
292
- transition: background-color 0.2s ease-in-out;
293
-
294
- &[aria-disabled="true"] {
295
- color: rgba(148, 163, 184, 0.6);
296
- pointer-events: none;
297
- cursor: not-allowed;
184
+ // Show close button on hover/active
185
+ &:hover,
186
+ &.is-active {
187
+ &.is-closable {
188
+ padding-right: calc(var(--router-tab-padding) - #{($router-tab-close-icon-size + $router-tab-close-icon-margin) * 0.5});
298
189
  }
299
190
 
300
- &:not([aria-disabled="true"]):hover,
301
- &:not([aria-disabled="true"]):focus-visible {
302
- background: color-mix(in srgb, var(--router-tab-primary) 15%, transparent 85%);
303
- color: var(--router-tab-primary);
304
- outline: none;
191
+ .router-tab__item-close {
192
+ width: $router-tab-close-icon-size;
193
+ margin-left: $router-tab-close-icon-margin;
305
194
  }
306
195
  }
307
196
  }
@@ -310,32 +199,47 @@ $close-icon-size: 13px;
310
199
  position: relative;
311
200
  flex: 1 1 auto;
312
201
  background-color: var(--router-tab-background);
313
- border: 1px solid var(--router-tab-border);
314
- border-top: none; // FIXED: Avoid double border with header
202
+ overflow: hidden;
315
203
  }
316
204
  }
317
205
 
318
- // FIXED: Proper adjacent tab borders
319
- .router-tab__item {
320
- + .router-tab__item {
321
- border-left-color: var(--router-tab-border);
322
- }
206
+ // Context Menu using button colors
207
+ .router-tab__contextmenu {
208
+ position: fixed;
209
+ z-index: 1000;
210
+ min-width: $router-tab-contextmenu-min-width;
211
+ padding: $router-tab-contextmenu-padding;
212
+ font-size: var(--router-tab-font-size);
213
+ background: var(--router-tab-background);
214
+ border: 1px solid var(--router-tab-border);
215
+ border-radius: $router-tab-contextmenu-border-radius;
216
+ box-shadow: $router-tab-contextmenu-shadow;
217
+
218
+ &-item {
219
+ display: block;
220
+ width: 100%;
221
+ padding: $router-tab-contextmenu-item-padding;
222
+ line-height: $router-tab-contextmenu-item-height;
223
+ text-align: left;
224
+ text-decoration: none;
225
+ background: var(--router-tab-button-bg);
226
+ border: none;
227
+ color: var(--router-tab-button-color);
228
+ cursor: pointer;
229
+ font: inherit;
230
+ border-radius: calc(#{$router-tab-contextmenu-border-radius} - 4px);
231
+ transition: all 0.2s ease-in-out;
323
232
 
324
- &.is-active + .router-tab__item {
325
- border-left-color: var(--router-tab-border);
326
- }
327
- }
233
+ &[aria-disabled="true"] {
234
+ color: color-mix(in srgb, var(--router-tab-text) 40%);
235
+ pointer-events: none;
236
+ cursor: not-allowed;
237
+ }
328
238
 
329
- // NEW: Smooth drag animations
330
- @keyframes router-tab-drag-hint {
331
- 0%, 100% {
332
- transform: translateY(0);
333
- }
334
- 50% {
335
- transform: translateY(-2px);
239
+ &:hover:not([aria-disabled="true"]),
240
+ &:focus-visible {
241
+ background: var(--router-tab-active-button-bg);
242
+ color: var(--router-tab-active-button-color);
243
+ }
336
244
  }
337
- }
338
-
339
- .router-tab__item.is-dragging {
340
- animation: router-tab-drag-hint 0.3s ease-in-out;
341
245
  }
package/lib/theme.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  const STYLE_KEY = 'tab-theme-style'
2
2
  const PRIMARY_KEY = 'tab-theme-primary-color'
3
3
  const DEFAULT_STYLE: 'light' | 'dark' | 'system' = 'system'
4
- const DEFAULT_PRIMARY = '#0f172a'
5
4
  const MEDIA_QUERY = '(prefers-color-scheme: dark)'
6
5
 
7
6
  let mediaListener: ((event: MediaQueryListEvent) => void) | null = null
@@ -12,11 +11,86 @@ export interface RouterTabsThemeOptions {
12
11
  defaultStyle?: 'light' | 'dark' | 'system'
13
12
  defaultPrimary?: string
14
13
  }
14
+ export interface ColorStyle {
15
+ // Core colors
16
+ primary: string;
17
+ background: string;
18
+ text: string;
19
+ border: string;
20
+
21
+ // Interactive states
22
+ activeBackground: string;
23
+ activeText: string;
24
+ activeBorder: string;
25
+
26
+ // Header specific
27
+ headerBackground: string;
28
+
29
+ // Button specific
30
+ buttonBackground: string;
31
+ buttonColor: string;
32
+ activeButtonBackground: string;
33
+ activeButtonColor: string;
34
+
35
+ // Icon specific
36
+ iconColor: string;
37
+ }
38
+
39
+ const defaultColors: ColorStyle = {
40
+ primary: "#034960",
41
+ background: "#ffffff",
42
+ text: "#1e293b",
43
+ border: "#e2e8f0",
44
+
45
+ activeBackground: "#034960",
46
+ activeText: "#ffffff",
47
+ activeBorder: "#034960",
48
+
49
+ headerBackground: "#ffff",
50
+
51
+ buttonBackground: "#f8fafc",
52
+ buttonColor: "#034960",
53
+ activeButtonBackground: "#034960",
54
+ activeButtonColor: "#ffffff",
55
+
56
+ iconColor: "#475569",
57
+ }
15
58
 
16
- function applyPrimary(color: string) {
59
+ const defaultDarkColor: ColorStyle = {
60
+ primary: "#38bdf8",
61
+ background: "#0f172a",
62
+ text: "#f1f5f9",
63
+ border: "#334155",
64
+
65
+ activeBackground: "#1e293b",
66
+ activeText: "#38bdf8",
67
+ activeBorder: "#38bdf8",
68
+
69
+ headerBackground: "#0c4a6e", // Darker shade of primary
70
+
71
+ buttonBackground: "#1e293b",
72
+ buttonColor: "#f1f5f9",
73
+ activeButtonBackground: "#38bdf8",
74
+ activeButtonColor: "#0f172a",
75
+
76
+ iconColor: "#cbd5e1",
77
+ }
78
+
79
+ function applyPrimary(color: ColorStyle) {
17
80
  if (typeof document === 'undefined') return
18
- document.documentElement.style.setProperty('--theme-primary', color)
19
- document.documentElement.style.setProperty('--router-tab-primary', color)
81
+ console.log('applyPrimary', color)
82
+ document.documentElement.style.setProperty('--router-tab-primary', color.primary ?? defaultColors.primary)
83
+ document.documentElement.style.setProperty('--router-tab-header-bg', color.headerBackground ?? defaultColors.headerBackground)
84
+ document.documentElement.style.setProperty('--router-tab-background', color.background ?? defaultColors.background)
85
+ document.documentElement.style.setProperty('--router-tab-active-background', color.activeBackground ?? defaultColors.activeBackground)
86
+ document.documentElement.style.setProperty('--router-tab-text', color.text ?? defaultColors.text)
87
+ document.documentElement.style.setProperty('--router-tab-active-text', color.activeText ?? defaultColors.activeText)
88
+ document.documentElement.style.setProperty('--router-tab-border', color.border ?? defaultColors.border)
89
+ document.documentElement.style.setProperty('--router-tab-active-border', color.activeBorder ?? defaultColors.activeBorder)
90
+ document.documentElement.style.setProperty('--router-tab-button-color', color.buttonColor ?? defaultColors.buttonColor)
91
+ document.documentElement.style.setProperty('--router-tab-active-button-color', color.activeButtonColor ?? defaultColors.activeButtonColor)
92
+ document.documentElement.style.setProperty('--router-tab-button-background', color.buttonBackground ?? defaultColors.buttonBackground)
93
+ document.documentElement.style.setProperty('--router-tab-active-button-background', color.activeButtonBackground ?? defaultColors.activeButtonBackground)
20
94
  }
21
95
 
22
96
  function applyStyle(style: 'light' | 'dark' | 'system') {
@@ -48,16 +122,17 @@ export function initRouterTabsTheme(options: RouterTabsThemeOptions = {}) {
48
122
 
49
123
  const {
50
124
  styleKey = STYLE_KEY,
51
- primaryKey = PRIMARY_KEY,
52
125
  defaultStyle = DEFAULT_STYLE,
53
- defaultPrimary = DEFAULT_PRIMARY
54
126
  } = options
55
127
 
56
128
  const storedStyle = (window.localStorage.getItem(styleKey) as 'light' | 'dark' | 'system' | null) ?? defaultStyle
57
- const storedPrimary = window.localStorage.getItem(primaryKey) ?? defaultPrimary
58
129
 
59
130
  applyStyle(storedStyle)
60
- applyPrimary(storedPrimary)
131
+ if (storedStyle === 'dark') {
132
+ applyPrimary(defaultDarkColor)
133
+ } else {
134
+ applyPrimary(defaultColors)
135
+ }
61
136
  }
62
137
 
63
138
  export function setRouterTabsTheme(style: 'light' | 'dark' | 'system', options?: RouterTabsThemeOptions) {
@@ -67,9 +142,9 @@ export function setRouterTabsTheme(style: 'light' | 'dark' | 'system', options?:
67
142
  applyStyle(style)
68
143
  }
69
144
 
70
- export function setRouterTabsPrimary(color: string, options?: RouterTabsThemeOptions) {
145
+ export function setRouterTabsPrimary(color: ColorStyle, options?: RouterTabsThemeOptions) {
71
146
  if (typeof window === 'undefined') return
72
147
  const key = options?.primaryKey ?? PRIMARY_KEY
73
- window.localStorage.setItem(key, color)
148
+ window.localStorage.setItem(key, JSON.stringify(color))
74
149
  applyPrimary(color)
75
150
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue3-router-tab",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",