ketekny-ui-kit 1.0.134 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/ui/kCard.vue +92 -16
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.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,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)