ketekny-ui-kit 1.0.133 → 1.0.135

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.133",
4
+ "version": "1.0.135",
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,12 +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 items-start justify-between gap-3 border-slate-200 px-5 py-4"
6
- :class="{
7
- 'border-b': !isCollapsed,
8
- 'group cursor-pointer select-none transition-colors hover:bg-slate-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand/30': collapsible,
9
- }"
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
+ ]"
10
19
  :role="collapsible ? 'button' : undefined"
11
20
  :tabindex="collapsible ? 0 : undefined"
12
21
  :aria-label="collapsible ? (isCollapsed ? 'Expand card body' : 'Collapse card body') : undefined"
@@ -17,18 +26,30 @@
17
26
  >
18
27
  <div class="min-w-0 flex-1">
19
28
  <slot name="header">
20
- <p v-if="title" class="text-sm font-semibold text-slate-900">
21
- {{ title }}
22
- </p>
23
- <p v-if="subtitle" :class="title ? 'mt-1 text-sm text-slate-500' : 'text-sm text-slate-500'">
24
- {{ subtitle }}
25
- </p>
29
+ <div class="flex items-center gap-3">
30
+ <span
31
+ v-if="icon"
32
+ class="inline-flex size-8 shrink-0 items-center justify-center rounded-lg"
33
+ :class="iconClasses"
34
+ >
35
+ <kIcon :name="icon" :size="18" :color="iconColor" />
36
+ </span>
37
+ <div class="min-w-0">
38
+ <p v-if="title" class="text-sm font-semibold" :class="titleClasses">
39
+ {{ title }}
40
+ </p>
41
+ <p v-if="subtitle" class="text-sm" :class="[subtitleClasses, { 'mt-1': title }]">
42
+ {{ subtitle }}
43
+ </p>
44
+ </div>
45
+ </div>
26
46
  </slot>
27
47
  </div>
28
48
 
29
49
  <span
30
50
  v-if="collapsible"
31
- 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"
32
53
  >
33
54
  <ChevronDown
34
55
  class="size-5 transition-transform duration-200"
@@ -38,13 +59,17 @@
38
59
  </span>
39
60
  </header>
40
61
 
41
- <div v-show="!isCollapsed" class="px-5 py-4">
62
+ <div
63
+ v-show="!isCollapsed"
64
+ :class="[bodyClasses, dense ? 'p-0' : 'px-5 py-4']"
65
+ >
42
66
  <slot />
43
67
  </div>
44
68
 
45
69
  <footer
46
70
  v-if="$slots.footer"
47
- class="border-t border-slate-200 px-5 py-4"
71
+ class="border-t px-5 py-4"
72
+ :class="footerClasses"
48
73
  >
49
74
  <slot name="footer" />
50
75
  </footer>
@@ -53,10 +78,11 @@
53
78
 
54
79
  <script>
55
80
  import { ChevronDown } from '@lucide/vue'
81
+ import kIcon from './kIcon.vue'
56
82
 
57
83
  export default {
58
84
  name: 'kCard',
59
- components: { ChevronDown },
85
+ components: { ChevronDown, kIcon },
60
86
  emits: ['update:collapsed', 'toggle'],
61
87
  props: {
62
88
  title: {
@@ -67,6 +93,10 @@ export default {
67
93
  type: String,
68
94
  default: '',
69
95
  },
96
+ icon: {
97
+ type: String,
98
+ default: '',
99
+ },
70
100
  collapsible: {
71
101
  type: Boolean,
72
102
  default: false,
@@ -75,26 +105,97 @@ export default {
75
105
  type: Boolean,
76
106
  default: false,
77
107
  },
108
+ dense: {
109
+ type: Boolean,
110
+ default: false,
111
+ },
112
+ theme: {
113
+ type: String,
114
+ default: 'auto',
115
+ validator: (value) => ['light', 'dark', 'auto', 'invert'].includes(value),
116
+ },
78
117
  },
79
118
  data() {
80
119
  return {
81
120
  internalCollapsed: this.collapsed,
121
+ appTheme: 'light',
82
122
  }
83
123
  },
84
124
  computed: {
85
125
  hasHeader() {
86
- return Boolean(this.$slots.header || this.title || this.subtitle || this.collapsible)
126
+ return Boolean(this.$slots.header || this.title || this.subtitle || this.icon || this.collapsible)
87
127
  },
88
128
  isCollapsed() {
89
129
  return this.collapsible && this.internalCollapsed
90
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
+ },
91
177
  },
92
178
  watch: {
93
179
  collapsed(value) {
94
180
  this.internalCollapsed = value
95
181
  },
96
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
+ },
97
194
  methods: {
195
+ syncAppTheme() {
196
+ const root = document.documentElement
197
+ this.appTheme = root.dataset.theme === 'dark' || root.classList.contains('dark') ? 'dark' : 'light'
198
+ },
98
199
  toggleCollapsed() {
99
200
  this.internalCollapsed = !this.internalCollapsed
100
201
  this.$emit('update:collapsed', this.internalCollapsed)
package/src/ui/kList.vue CHANGED
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="w-full">
2
+ <div class="w-full" :class="{ 'k-list--dense': dense }">
3
3
  <dl
4
4
  v-if="!isRowFormat"
5
5
  class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3"
@@ -7,8 +7,11 @@
7
7
  <div
8
8
  v-for="(stat, index) in normalizedStats"
9
9
  :key="resolveKey(stat, index)"
10
- class="flex items-center gap-3 border border-gray-200 rounded-xl bg-white p-3 dark:border-slate-700 dark:bg-slate-800"
11
- :class="entryClass(stat)"
10
+ class="flex items-center gap-3"
11
+ :class="[
12
+ dense ? '' : 'border border-gray-200 rounded-xl bg-white p-3 dark:border-slate-700 dark:bg-slate-800',
13
+ entryClass(stat),
14
+ ]"
12
15
  :role="stat.action ? 'button' : undefined"
13
16
  :tabindex="stat.action ? 0 : undefined"
14
17
  @click="handleAction(stat, index)"
@@ -38,7 +41,11 @@
38
41
  </div>
39
42
  </dl>
40
43
 
41
- <div v-else class="overflow-hidden bg-white border border-gray-200 rounded-xl dark:bg-slate-800 dark:border-slate-700">
44
+ <div
45
+ v-else
46
+ class="overflow-hidden bg-white dark:bg-slate-800"
47
+ :class="{ 'border border-gray-200 rounded-xl dark:border-slate-700': !dense }"
48
+ >
42
49
  <table class="w-full">
43
50
  <tbody>
44
51
  <tr
@@ -52,7 +59,10 @@
52
59
  @keydown.enter.prevent="handleAction(stat, index)"
53
60
  @keydown.space.prevent="handleAction(stat, index)"
54
61
  >
55
- <td class="px-4 py-3 text-sm text-gray-600 dark:text-slate-400">
62
+ <td
63
+ class="py-3 text-sm text-gray-600 dark:text-slate-400"
64
+ :class="dense ? 'px-5' : 'px-4'"
65
+ >
56
66
  <div class="flex items-center gap-2">
57
67
  <div
58
68
  v-if="stat.icon"
@@ -72,7 +82,10 @@
72
82
  <span>{{ stat.label }}</span>
73
83
  </div>
74
84
  </td>
75
- <td class="px-4 py-3 text-base font-semibold text-right text-gray-900 dark:text-slate-100">
85
+ <td
86
+ class="py-3 text-base font-semibold text-right text-gray-900 dark:text-slate-100"
87
+ :class="dense ? 'px-5' : 'px-4'"
88
+ >
76
89
  {{ stat.value }}
77
90
  </td>
78
91
  </tr>
@@ -100,6 +113,10 @@ export default {
100
113
  default: 'col',
101
114
  validator: (value) => ['col', 'row'].includes(value),
102
115
  },
116
+ dense: {
117
+ type: Boolean,
118
+ default: false,
119
+ },
103
120
  },
104
121
  emits: ['action'],
105
122
  computed: {