ketekny-ui-kit 1.0.134 → 1.0.136

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ketekny-ui-kit",
3
3
  "type": "module",
4
- "version": "1.0.134",
4
+ "version": "1.0.136",
5
5
  "description": "A Vue 3 UI component library with Tailwind CSS styling",
6
6
  "main": "index.js",
7
7
  "files": [
package/src/ui/kCard.vue CHANGED
@@ -1,14 +1,21 @@
1
1
  <template>
2
- <article class="self-start overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm">
2
+ <article
3
+ class="self-start overflow-hidden rounded-xl border shadow-sm"
4
+ :class="[cardClasses, { dark: isDark }]"
5
+ :data-theme="resolvedTheme"
6
+ >
3
7
  <header
4
8
  v-if="hasHeader"
5
- class="flex justify-between gap-3 border-slate-200 bg-slate-50/70 px-5 py-4"
6
- :class="{
7
- 'border-b': !isCollapsed,
8
- 'group cursor-pointer select-none transition-colors hover:bg-slate-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand/30': collapsible,
9
- 'items-start': subtitle,
10
- 'items-center': !subtitle,
11
- }"
9
+ class="flex justify-between gap-3 px-5 py-4"
10
+ :class="[
11
+ headerClasses,
12
+ {
13
+ 'border-b': !isCollapsed,
14
+ 'group cursor-pointer select-none transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand/30': collapsible,
15
+ 'items-start': subtitle,
16
+ 'items-center': !subtitle,
17
+ },
18
+ ]"
12
19
  :role="collapsible ? 'button' : undefined"
13
20
  :tabindex="collapsible ? 0 : undefined"
14
21
  :aria-label="collapsible ? (isCollapsed ? 'Expand card body' : 'Collapse card body') : undefined"
@@ -22,15 +29,16 @@
22
29
  <div class="flex items-center gap-3">
23
30
  <span
24
31
  v-if="icon"
25
- class="inline-flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary"
32
+ class="inline-flex size-8 shrink-0 items-center justify-center rounded-lg"
33
+ :class="iconClasses"
26
34
  >
27
- <kIcon :name="icon" :size="18" />
35
+ <kIcon :name="icon" :size="18" :color="iconColor" />
28
36
  </span>
29
37
  <div class="min-w-0">
30
- <p v-if="title" class="text-sm font-semibold text-slate-900">
38
+ <p v-if="title" class="text-sm font-semibold" :class="titleClasses">
31
39
  {{ title }}
32
40
  </p>
33
- <p v-if="subtitle" :class="title ? 'mt-1 text-sm text-slate-500' : 'text-sm text-slate-500'">
41
+ <p v-if="subtitle" class="text-sm" :class="[subtitleClasses, { 'mt-1': title }]">
34
42
  {{ subtitle }}
35
43
  </p>
36
44
  </div>
@@ -40,7 +48,8 @@
40
48
 
41
49
  <span
42
50
  v-if="collapsible"
43
- class="-mr-1 inline-flex size-8 shrink-0 items-center justify-center text-slate-500 transition-colors group-hover:text-slate-700"
51
+ class="-mr-1 inline-flex size-8 shrink-0 items-center justify-center transition-colors"
52
+ :class="collapseIconClasses"
44
53
  >
45
54
  <ChevronDown
46
55
  class="size-5 transition-transform duration-200"
@@ -52,15 +61,15 @@
52
61
 
53
62
  <div
54
63
  v-show="!isCollapsed"
55
- class="bg-white"
56
- :class="dense ? 'p-0' : 'px-5 py-4'"
64
+ :class="[bodyClasses, dense ? 'p-0' : 'px-5 py-4']"
57
65
  >
58
66
  <slot />
59
67
  </div>
60
68
 
61
69
  <footer
62
70
  v-if="$slots.footer"
63
- class="border-t border-slate-200 bg-slate-50 px-5 py-4"
71
+ class="border-t px-5 py-4"
72
+ :class="footerClasses"
64
73
  >
65
74
  <slot name="footer" />
66
75
  </footer>
@@ -100,10 +109,16 @@ export default {
100
109
  type: Boolean,
101
110
  default: false,
102
111
  },
112
+ theme: {
113
+ type: String,
114
+ default: 'auto',
115
+ validator: (value) => ['light', 'dark', 'auto', 'invert'].includes(value),
116
+ },
103
117
  },
104
118
  data() {
105
119
  return {
106
120
  internalCollapsed: this.collapsed,
121
+ appTheme: 'light',
107
122
  }
108
123
  },
109
124
  computed: {
@@ -113,13 +128,74 @@ export default {
113
128
  isCollapsed() {
114
129
  return this.collapsible && this.internalCollapsed
115
130
  },
131
+ resolvedTheme() {
132
+ if (this.theme === 'auto') return this.appTheme
133
+ if (this.theme === 'invert') return this.appTheme === 'dark' ? 'light' : 'dark'
134
+ return this.theme
135
+ },
136
+ isDark() {
137
+ return this.resolvedTheme === 'dark'
138
+ },
139
+ cardClasses() {
140
+ return this.isDark
141
+ ? 'border-slate-700 bg-slate-900 text-slate-100'
142
+ : 'border-slate-200 bg-white text-slate-900'
143
+ },
144
+ headerClasses() {
145
+ if (this.isDark) {
146
+ return `border-slate-700 bg-slate-800/70${this.collapsible ? ' hover:bg-slate-800' : ''}`
147
+ }
148
+ return `border-slate-200 bg-slate-50/70${this.collapsible ? ' hover:bg-slate-100' : ''}`
149
+ },
150
+ titleClasses() {
151
+ return this.isDark ? 'text-slate-100' : 'text-slate-900'
152
+ },
153
+ subtitleClasses() {
154
+ return this.isDark ? 'text-slate-400' : 'text-slate-500'
155
+ },
156
+ iconClasses() {
157
+ return this.isDark
158
+ ? 'bg-white/10 text-slate-100'
159
+ : 'bg-primary/10 text-primary'
160
+ },
161
+ iconColor() {
162
+ return this.isDark ? '#f1f5f9' : 'currentColor'
163
+ },
164
+ collapseIconClasses() {
165
+ return this.isDark
166
+ ? 'text-slate-400 group-hover:text-slate-200'
167
+ : 'text-slate-500 group-hover:text-slate-700'
168
+ },
169
+ bodyClasses() {
170
+ return this.isDark ? 'bg-slate-900' : 'bg-white'
171
+ },
172
+ footerClasses() {
173
+ return this.isDark
174
+ ? 'border-slate-700 bg-slate-800'
175
+ : 'border-slate-200 bg-slate-50'
176
+ },
116
177
  },
117
178
  watch: {
118
179
  collapsed(value) {
119
180
  this.internalCollapsed = value
120
181
  },
121
182
  },
183
+ mounted() {
184
+ this.syncAppTheme()
185
+ this.themeObserver = new MutationObserver(this.syncAppTheme)
186
+ this.themeObserver.observe(document.documentElement, {
187
+ attributes: true,
188
+ attributeFilter: ['class', 'data-theme'],
189
+ })
190
+ },
191
+ beforeUnmount() {
192
+ this.themeObserver?.disconnect()
193
+ },
122
194
  methods: {
195
+ syncAppTheme() {
196
+ const root = document.documentElement
197
+ this.appTheme = root.dataset.theme === 'dark' || root.classList.contains('dark') ? 'dark' : 'light'
198
+ },
123
199
  toggleCollapsed() {
124
200
  this.internalCollapsed = !this.internalCollapsed
125
201
  this.$emit('update:collapsed', this.internalCollapsed)
package/src/ui/kTabs.vue CHANGED
@@ -1,17 +1,30 @@
1
1
  <template>
2
- <div class="w-full overflow-hidden bg-white border border-slate-200 rounded-md dark:bg-slate-800 dark:border-slate-700">
3
- <div class="k-tabs-scroll flex overflow-x-auto">
2
+ <div class="w-full">
3
+ <div
4
+ class="k-tabs-scroll flex overflow-x-auto overflow-y-hidden border-b border-slate-200 dark:border-slate-700"
5
+ role="tablist"
6
+ >
4
7
  <button
5
8
  v-for="tab in tabs"
6
9
  :key="tab.id"
10
+ type="button"
11
+ role="tab"
12
+ :aria-selected="modelValue === tab.id"
13
+ :tabindex="modelValue === tab.id ? 0 : -1"
7
14
  @click="selectTab(tab.id)"
8
15
  :class="[
9
- 'shrink-0 whitespace-nowrap px-4 py-3 text-sm font-medium sm:px-8 sm:py-4 sm:text-lg',
16
+ '-mb-px inline-flex h-11 shrink-0 items-center gap-2 whitespace-nowrap border-b-2 px-4 text-base font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary/30',
10
17
  modelValue === tab.id
11
- ? 'bg-primary text-secondary'
12
- : 'text-gray-500 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-700/50',
18
+ ? 'border-primary text-primary'
19
+ : 'border-transparent text-slate-600 hover:border-slate-300 hover:text-slate-900 dark:text-slate-400 dark:hover:border-slate-600 dark:hover:text-slate-100',
13
20
  ]"
14
21
  >
22
+ <component
23
+ :is="iconComponent(tab.icon)"
24
+ v-if="tab.icon && iconComponent(tab.icon)"
25
+ class="size-4 shrink-0"
26
+ aria-hidden="true"
27
+ />
15
28
  {{ tab.title }}
16
29
  </button>
17
30
  </div>
@@ -19,8 +32,10 @@
19
32
  </template>
20
33
 
21
34
  <script>
35
+ import * as icons from "@lucide/vue";
36
+
22
37
  export default {
23
- name: "TabList",
38
+ name: "kTabs",
24
39
  props: {
25
40
  modelValue: { type: [String, Number], required: true },
26
41
  tabs: {
@@ -33,6 +48,14 @@ export default {
33
48
  },
34
49
  emits: ["update:modelValue"],
35
50
  methods: {
51
+ iconComponent(name) {
52
+ if (!name) return null;
53
+ const normalizedName = name
54
+ .split("-")
55
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
56
+ .join("");
57
+ return icons[normalizedName] || null;
58
+ },
36
59
  selectTab(id) {
37
60
  this.$emit("update:modelValue", id);
38
61
  },
@@ -43,6 +66,10 @@ export default {
43
66
  <style scoped>
44
67
  .k-tabs-scroll {
45
68
  -webkit-overflow-scrolling: touch;
46
- scrollbar-width: thin;
69
+ scrollbar-width: none;
70
+ }
71
+
72
+ .k-tabs-scroll::-webkit-scrollbar {
73
+ display: none;
47
74
  }
48
75
  </style>
@@ -5,8 +5,8 @@ export const K_BUTTON_BASE = "inline-flex flex-row items-center justify-center f
5
5
 
6
6
  export const K_BUTTON_SIZE_STYLES = {
7
7
  normal: {
8
- regular: "h-9 px-4 text-sm",
9
- iconOnly: "h-9 w-9 p-0",
8
+ regular: "h-10 px-4 text-sm",
9
+ iconOnly: "h-10 w-10 p-0",
10
10
  icon: "w-4 h-4",
11
11
  loader: "w-4 h-4",
12
12
  gap: "mr-1.5",
@@ -27,8 +27,8 @@ export const K_BUTTON_SIZE_STYLES = {
27
27
  },
28
28
  // Backward compatibility aliases
29
29
  default: {
30
- regular: "h-9 px-4 text-sm",
31
- iconOnly: "h-9 w-9 p-0",
30
+ regular: "h-10 px-4 text-sm",
31
+ iconOnly: "h-10 w-10 p-0",
32
32
  icon: "w-4 h-4",
33
33
  loader: "w-4 h-4",
34
34
  gap: "mr-1.5",