ketekny-ui-kit 1.0.3 → 1.0.5
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/README.md +174 -6
- package/index.js +2 -1
- package/package.json +9 -2
- package/src/ui/componentPreview.vue +289 -0
- package/src/ui/kSkeleton.vue +21 -0
package/README.md
CHANGED
|
@@ -1,22 +1,190 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Ketekny UI Kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A comprehensive Vue 3 UI component library built with Tailwind CSS. This library provides a set of reusable, customizable components for modern web applications.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install ketekny-ui-kit
|
|
6
10
|
```
|
|
11
|
+
|
|
12
|
+
## Tailwind Configuration
|
|
13
|
+
|
|
14
|
+
Add the preset to your `tailwind.config.js`:
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
7
17
|
// tailwind.config.js
|
|
8
18
|
const preset = require('ketekny-ui-kit/tailwind-preset.js')
|
|
9
19
|
|
|
10
20
|
module.exports = {
|
|
11
21
|
presets: [preset],
|
|
12
|
-
content: [
|
|
22
|
+
content: [
|
|
23
|
+
'./src/**/*.{vue,js,ts}',
|
|
24
|
+
'./node_modules/ketekny-ui-kit/**/*.{vue,js,ts}'
|
|
25
|
+
],
|
|
13
26
|
safelist: [
|
|
14
27
|
{ pattern: /text-semantic-(success|danger|info|warning)-text/ },
|
|
15
28
|
{ pattern: /border-semantic-(success|danger|info|warning)-border/ },
|
|
16
|
-
|
|
29
|
+
{ pattern: /bg-semantic-(success|warning|error|info)-(bg|disabled|button)/ },
|
|
17
30
|
],
|
|
18
31
|
}
|
|
19
32
|
```
|
|
20
33
|
|
|
34
|
+
## Components
|
|
35
|
+
|
|
36
|
+
### UI Components
|
|
37
|
+
- `kButton` - Versatile button component
|
|
38
|
+
- `kInput` - Input field with validation
|
|
39
|
+
- `kDateSelector` - Date picker with year/month modes
|
|
40
|
+
- `kSelect` - Dropdown select component
|
|
41
|
+
- `kToggle` - Toggle switch
|
|
42
|
+
- `kChip` - Tag/label component
|
|
43
|
+
- `kSpinner` - Loading spinner
|
|
44
|
+
- `kIcon` - Icon component using Lucide icons
|
|
45
|
+
- `kMessage` - Alert/message component
|
|
46
|
+
- `kCode` - Code display component
|
|
47
|
+
- `kSearch` - Search input component
|
|
48
|
+
- `kTags` - Tag input component
|
|
49
|
+
- `kSelectButton` - Button group select
|
|
50
|
+
- `kArrayList` - Array/list management
|
|
51
|
+
- `kToolbar` - Toolbar component
|
|
52
|
+
- `kUploader` - File upload component
|
|
53
|
+
- `kEditor` - Rich text editor
|
|
54
|
+
- `kDialog` - Modal dialog
|
|
55
|
+
- `kDrawer` - Slide-out drawer
|
|
56
|
+
- `kTabs` - Tab navigation
|
|
57
|
+
- `kTable` - Data table
|
|
58
|
+
- `kDatatable` - Advanced data table
|
|
59
|
+
|
|
60
|
+
### Layout Components
|
|
61
|
+
- `kAppHeader` - Application header
|
|
62
|
+
- `kAppFooter` - Application footer
|
|
63
|
+
- `kAppMain` - Main content area
|
|
64
|
+
- `kHero` - Hero section
|
|
65
|
+
|
|
66
|
+
### Plugins
|
|
67
|
+
- `toastPlugin` - Toast notifications
|
|
68
|
+
- `confirmPlugin` - Confirmation dialogs
|
|
69
|
+
- `alertPlugin` - Alert dialogs
|
|
70
|
+
- `inputDialogPlugin` - Input dialogs
|
|
71
|
+
- `tooltipPlugin` - Tooltip functionality
|
|
72
|
+
|
|
73
|
+
## Usage
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
// main.js
|
|
77
|
+
import { createApp } from 'vue'
|
|
78
|
+
import App from './App.vue'
|
|
79
|
+
|
|
80
|
+
// Import components
|
|
81
|
+
import {
|
|
82
|
+
kButton,
|
|
83
|
+
kInput,
|
|
84
|
+
kDateSelector,
|
|
85
|
+
kSelect,
|
|
86
|
+
kToggle
|
|
87
|
+
} from 'ketekny-ui-kit'
|
|
88
|
+
|
|
89
|
+
// Import plugins
|
|
90
|
+
import toastPlugin from 'ketekny-ui-kit'
|
|
91
|
+
import confirmPlugin from 'ketekny-ui-kit'
|
|
92
|
+
|
|
93
|
+
const app = createApp(App)
|
|
94
|
+
|
|
95
|
+
// Register components globally
|
|
96
|
+
app.component('kButton', kButton)
|
|
97
|
+
app.component('kInput', kInput)
|
|
98
|
+
app.component('kDateSelector', kDateSelector)
|
|
99
|
+
app.component('kSelect', kSelect)
|
|
100
|
+
app.component('kToggle', kToggle)
|
|
101
|
+
|
|
102
|
+
// Use plugins
|
|
103
|
+
app.use(toastPlugin)
|
|
104
|
+
app.use(confirmPlugin)
|
|
105
|
+
|
|
106
|
+
app.mount('#app')
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Example Component Usage
|
|
110
|
+
|
|
111
|
+
```vue
|
|
112
|
+
<template>
|
|
113
|
+
<div class="p-4 space-y-4">
|
|
114
|
+
<!-- Button -->
|
|
115
|
+
<k-button @click="handleClick" variant="primary">
|
|
116
|
+
Click me
|
|
117
|
+
</k-button>
|
|
118
|
+
|
|
119
|
+
<!-- Input -->
|
|
120
|
+
<k-input
|
|
121
|
+
v-model="email"
|
|
122
|
+
label="Email"
|
|
123
|
+
placeholder="Enter your email"
|
|
124
|
+
type="email"
|
|
125
|
+
/>
|
|
126
|
+
|
|
127
|
+
<!-- Date Selector -->
|
|
128
|
+
<k-date-selector
|
|
129
|
+
v-model="date"
|
|
130
|
+
label="Select Date"
|
|
131
|
+
type="date"
|
|
132
|
+
/>
|
|
133
|
+
|
|
134
|
+
<!-- Select -->
|
|
135
|
+
<k-select
|
|
136
|
+
v-model="selectedOption"
|
|
137
|
+
:options="options"
|
|
138
|
+
label="Choose option"
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
</template>
|
|
142
|
+
|
|
143
|
+
<script>
|
|
144
|
+
import { kButton, kInput, kDateSelector, kSelect } from 'ketekny-ui-kit'
|
|
145
|
+
|
|
146
|
+
export default {
|
|
147
|
+
components: {
|
|
148
|
+
kButton,
|
|
149
|
+
kInput,
|
|
150
|
+
kDateSelector,
|
|
151
|
+
kSelect
|
|
152
|
+
},
|
|
153
|
+
data() {
|
|
154
|
+
return {
|
|
155
|
+
email: '',
|
|
156
|
+
date: null,
|
|
157
|
+
selectedOption: null,
|
|
158
|
+
options: [
|
|
159
|
+
{ value: 'option1', label: 'Option 1' },
|
|
160
|
+
{ value: 'option2', label: 'Option 2' }
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
methods: {
|
|
165
|
+
handleClick() {
|
|
166
|
+
// Handle button click
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
</script>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Dependencies
|
|
174
|
+
|
|
175
|
+
This library depends on:
|
|
176
|
+
- Vue 3.x
|
|
177
|
+
- Tailwind CSS 3.x
|
|
178
|
+
- Lucide Vue Next (icons)
|
|
179
|
+
- Vue Datepicker
|
|
180
|
+
- Moment.js
|
|
181
|
+
- PrimeVue
|
|
182
|
+
- Quill (rich text editor)
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
ISC
|
|
187
|
+
|
|
188
|
+
## Links
|
|
21
189
|
|
|
22
|
-
npm
|
|
190
|
+
- [npm](https://www.npmjs.com/package/ketekny-ui-kit)
|
package/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import kMenu from './src/ui/kMenu.vue'
|
|
|
22
22
|
import kTags from './src/ui/kTags.vue'
|
|
23
23
|
import kSearch from './src/ui/kSearch.vue'
|
|
24
24
|
import kArrayList from './src/ui/kArrayList.vue'
|
|
25
|
+
import kSkeleton from './src/ui/kSkeleton.vue'
|
|
25
26
|
|
|
26
27
|
// Layout Components
|
|
27
28
|
import kAppHeader from './src/layout/kAppHeader.vue'
|
|
@@ -42,7 +43,7 @@ import tailwindPreset from './tailwind-preset.js'
|
|
|
42
43
|
// Named exports (tree-shaking friendly)
|
|
43
44
|
export {
|
|
44
45
|
// UI Components
|
|
45
|
-
kMessage, kCode, kToolbar, kTable, kTabs, kChip, kSpinner, kDatatable, kIcon, kMenu,
|
|
46
|
+
kMessage, kCode, kToolbar, kTable, kTabs, kChip, kSpinner, kDatatable, kIcon, kMenu, kSkeleton,
|
|
46
47
|
|
|
47
48
|
// Form Components
|
|
48
49
|
kButton, kSelect, kUploader, kToggle, kInput, kDateSelector, kEditor, kSelectButton, kTags, kSearch, kArrayList,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ketekny-ui-kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.5",
|
|
5
5
|
"description": "A Vue 3 UI component library with Tailwind CSS styling",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
"tailwind-preset.js",
|
|
12
12
|
"tailwind.config.js"
|
|
13
13
|
],
|
|
14
|
-
"keywords": [
|
|
14
|
+
"keywords": [
|
|
15
|
+
"vue",
|
|
16
|
+
"ui",
|
|
17
|
+
"components",
|
|
18
|
+
"tailwind",
|
|
19
|
+
"library"
|
|
20
|
+
],
|
|
15
21
|
"peerDependencies": {
|
|
16
22
|
"vue": "^3.0.0"
|
|
17
23
|
},
|
|
@@ -40,6 +46,7 @@
|
|
|
40
46
|
"primevue": "^4.3.4",
|
|
41
47
|
"quill": "^2.0.3",
|
|
42
48
|
"simple-code-editor": "^2.0.9",
|
|
49
|
+
"vue-router": "^4.6.4",
|
|
43
50
|
"vue3-easy-data-table": "^1.5.47"
|
|
44
51
|
}
|
|
45
52
|
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="overflow-hidden border rounded-lg shadow-sm component-preview cp-card cp-border">
|
|
3
|
+
<!-- Header -->
|
|
4
|
+
<div class="flex items-center justify-between px-4 py-3 border-b cp-border cp-muted-50">
|
|
5
|
+
<h3 class="text-lg font-semibold cp-text">{{ componentName }}</h3>
|
|
6
|
+
|
|
7
|
+
<div class="flex items-center gap-1">
|
|
8
|
+
<button
|
|
9
|
+
v-for="tab in tabs"
|
|
10
|
+
:key="tab.id"
|
|
11
|
+
@click="activeTab = tab.id"
|
|
12
|
+
:class="['px-3 py-1.5 text-sm font-medium rounded-md transition-colors', activeTab === tab.id ? 'cp-tab-active' : 'cp-tab']">
|
|
13
|
+
{{ tab.label }}
|
|
14
|
+
</button>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<!-- Content -->
|
|
19
|
+
<div class="p-4">
|
|
20
|
+
<!-- Preview Tab -->
|
|
21
|
+
<div v-show="activeTab === 'preview'" class="min-h-[120px] flex items-center justify-center rounded-lg p-6 border cp-muted-30 cp-border-50">
|
|
22
|
+
<slot name="preview"></slot>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<!-- Props Tab -->
|
|
26
|
+
<div v-show="activeTab === 'props'">
|
|
27
|
+
<div v-if="props.length > 0" class="overflow-x-auto">
|
|
28
|
+
<table class="w-full text-sm">
|
|
29
|
+
<thead>
|
|
30
|
+
<tr class="border-b cp-border">
|
|
31
|
+
<th class="px-4 py-3 font-semibold text-left cp-text">Name</th>
|
|
32
|
+
<th class="px-4 py-3 font-semibold text-left cp-text">Type</th>
|
|
33
|
+
<th class="px-4 py-3 font-semibold text-left cp-text">Default</th>
|
|
34
|
+
<th class="px-4 py-3 font-semibold text-left cp-text">Description</th>
|
|
35
|
+
</tr>
|
|
36
|
+
</thead>
|
|
37
|
+
|
|
38
|
+
<tbody>
|
|
39
|
+
<tr v-for="(prop, index) in props" :key="prop.name" :class="index % 2 === 0 ? 'cp-muted-30' : ''">
|
|
40
|
+
<td class="px-4 py-3">
|
|
41
|
+
<code class="text-sm font-mono px-1.5 py-0.5 rounded cp-chip cp-chip-text">
|
|
42
|
+
{{ prop.name }}
|
|
43
|
+
</code>
|
|
44
|
+
</td>
|
|
45
|
+
|
|
46
|
+
<td class="px-4 py-3">
|
|
47
|
+
<span class="font-mono text-sm text-blue-600">{{ prop.type }}</span>
|
|
48
|
+
</td>
|
|
49
|
+
|
|
50
|
+
<td class="px-4 py-3">
|
|
51
|
+
<code v-if="prop.default !== undefined" class="font-mono text-sm text-emerald-600">
|
|
52
|
+
{{ prop.default }}
|
|
53
|
+
</code>
|
|
54
|
+
<span v-else class="cp-text-muted">—</span>
|
|
55
|
+
</td>
|
|
56
|
+
|
|
57
|
+
<td class="px-4 py-3 cp-text-muted">{{ prop.description }}</td>
|
|
58
|
+
</tr>
|
|
59
|
+
</tbody>
|
|
60
|
+
</table>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div v-else class="flex items-center justify-center py-8 cp-text-muted">
|
|
64
|
+
<span>No props defined for this component</span>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<!-- Slots Tab -->
|
|
69
|
+
<div v-show="activeTab === 'slots'">
|
|
70
|
+
<div v-if="slots.length > 0" class="overflow-x-auto">
|
|
71
|
+
<table class="w-full text-sm">
|
|
72
|
+
<thead>
|
|
73
|
+
<tr class="border-b cp-border">
|
|
74
|
+
<th class="px-4 py-3 font-semibold text-left cp-text">Name</th>
|
|
75
|
+
<th class="px-4 py-3 font-semibold text-left cp-text">Scoped</th>
|
|
76
|
+
<th class="px-4 py-3 font-semibold text-left cp-text">Description</th>
|
|
77
|
+
</tr>
|
|
78
|
+
</thead>
|
|
79
|
+
|
|
80
|
+
<tbody>
|
|
81
|
+
<tr v-for="(slot, index) in slots" :key="slot.name" :class="index % 2 === 0 ? 'cp-muted-30' : ''">
|
|
82
|
+
<td class="px-4 py-3">
|
|
83
|
+
<code class="text-sm font-mono px-1.5 py-0.5 rounded cp-chip cp-chip-text">
|
|
84
|
+
{{ slot.name }}
|
|
85
|
+
</code>
|
|
86
|
+
</td>
|
|
87
|
+
|
|
88
|
+
<td class="px-4 py-3">
|
|
89
|
+
<span
|
|
90
|
+
:class="[
|
|
91
|
+
'inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium',
|
|
92
|
+
slot.scoped ? 'bg-emerald-100 text-emerald-700' : 'cp-badge-muted',
|
|
93
|
+
]">
|
|
94
|
+
{{ slot.scoped ? "Yes" : "No" }}
|
|
95
|
+
</span>
|
|
96
|
+
</td>
|
|
97
|
+
|
|
98
|
+
<td class="px-4 py-3 cp-text-muted">{{ slot.description }}</td>
|
|
99
|
+
</tr>
|
|
100
|
+
</tbody>
|
|
101
|
+
</table>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div v-else class="flex items-center justify-center py-8 cp-text-muted">
|
|
105
|
+
<span>No slots defined for this component</span>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<!-- Code Tab -->
|
|
110
|
+
<div v-show="activeTab === 'code'" class="relative">
|
|
111
|
+
<div class="absolute z-10 top-3 right-3">
|
|
112
|
+
<button
|
|
113
|
+
@click="copyCode"
|
|
114
|
+
class="flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-md transition-colors cp-chip cp-chip-hover cp-chip-text">
|
|
115
|
+
<svg
|
|
116
|
+
v-if="!copied"
|
|
117
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
118
|
+
width="16"
|
|
119
|
+
height="16"
|
|
120
|
+
viewBox="0 0 24 24"
|
|
121
|
+
fill="none"
|
|
122
|
+
stroke="currentColor"
|
|
123
|
+
stroke-width="2"
|
|
124
|
+
stroke-linecap="round"
|
|
125
|
+
stroke-linejoin="round">
|
|
126
|
+
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
|
|
127
|
+
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
|
|
128
|
+
</svg>
|
|
129
|
+
|
|
130
|
+
<svg
|
|
131
|
+
v-else
|
|
132
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
133
|
+
width="16"
|
|
134
|
+
height="16"
|
|
135
|
+
viewBox="0 0 24 24"
|
|
136
|
+
fill="none"
|
|
137
|
+
stroke="currentColor"
|
|
138
|
+
stroke-width="2"
|
|
139
|
+
stroke-linecap="round"
|
|
140
|
+
stroke-linejoin="round"
|
|
141
|
+
class="text-emerald-600">
|
|
142
|
+
<polyline points="20 6 9 17 4 12" />
|
|
143
|
+
</svg>
|
|
144
|
+
|
|
145
|
+
{{ copied ? "Copied!" : "Copy" }}
|
|
146
|
+
</button>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<pre class="p-4 overflow-x-auto rounded-lg cp-pre">
|
|
150
|
+
<code class="font-mono text-sm" v-html="highlightedCode"></code>
|
|
151
|
+
</pre>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</template>
|
|
156
|
+
|
|
157
|
+
<script>
|
|
158
|
+
import hljs from "highlight.js/lib/core";
|
|
159
|
+
import xml from "highlight.js/lib/languages/xml";
|
|
160
|
+
|
|
161
|
+
hljs.registerLanguage("xml", xml);
|
|
162
|
+
|
|
163
|
+
export default {
|
|
164
|
+
name: "ComponentPreview",
|
|
165
|
+
props: {
|
|
166
|
+
componentName: { type: String, required: true },
|
|
167
|
+
props: { type: Array, default: () => [] },
|
|
168
|
+
slots: { type: Array, default: () => [] },
|
|
169
|
+
code: { type: String, default: "" },
|
|
170
|
+
},
|
|
171
|
+
data() {
|
|
172
|
+
return {
|
|
173
|
+
activeTab: "preview",
|
|
174
|
+
copied: false,
|
|
175
|
+
tabs: [
|
|
176
|
+
{ id: "preview", label: "Preview" },
|
|
177
|
+
{ id: "props", label: "Props" },
|
|
178
|
+
{ id: "slots", label: "Slots" },
|
|
179
|
+
{ id: "code", label: "Code" },
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
computed: {
|
|
184
|
+
highlightedCode() {
|
|
185
|
+
if (!this.code) return "";
|
|
186
|
+
try {
|
|
187
|
+
return hljs.highlight(this.code.trim(), { language: "xml" }).value;
|
|
188
|
+
} catch {
|
|
189
|
+
return this.escapeHtml(this.code.trim());
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
methods: {
|
|
194
|
+
async copyCode() {
|
|
195
|
+
try {
|
|
196
|
+
await navigator.clipboard.writeText(this.code.trim());
|
|
197
|
+
this.copied = true;
|
|
198
|
+
setTimeout(() => (this.copied = false), 2000);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
console.error("Failed to copy code:", err);
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
escapeHtml(text) {
|
|
204
|
+
const div = document.createElement("div");
|
|
205
|
+
div.textContent = text;
|
|
206
|
+
return div.innerHTML;
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
</script>
|
|
211
|
+
|
|
212
|
+
<style scoped>
|
|
213
|
+
/* Component-local theme tokens */
|
|
214
|
+
.component-preview {
|
|
215
|
+
--cp-foreground: #0a0a0a;
|
|
216
|
+
--cp-card: #ffffff;
|
|
217
|
+
--cp-border: #e5e5e5;
|
|
218
|
+
--cp-muted: #f5f5f5;
|
|
219
|
+
--cp-muted-foreground: #737373;
|
|
220
|
+
--cp-primary: #0a0a0a;
|
|
221
|
+
--cp-primary-foreground: #fafafa;
|
|
222
|
+
--cp-accent: #f5f5f5;
|
|
223
|
+
--cp-accent-foreground: #0a0a0a;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.cp-text {
|
|
227
|
+
color: var(--cp-foreground);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.cp-text-muted {
|
|
231
|
+
color: var(--cp-muted-foreground);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.cp-card {
|
|
235
|
+
background-color: var(--cp-card);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.cp-border {
|
|
239
|
+
border-color: var(--cp-border);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.cp-border-50 {
|
|
243
|
+
border-color: color-mix(in srgb, var(--cp-border) 50%, transparent);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.cp-muted-50 {
|
|
247
|
+
background-color: color-mix(in srgb, var(--cp-muted) 50%, transparent);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.cp-muted-30 {
|
|
251
|
+
background-color: color-mix(in srgb, var(--cp-muted) 30%, transparent);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.cp-tab {
|
|
255
|
+
color: var(--cp-muted-foreground);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.cp-tab:hover {
|
|
259
|
+
color: var(--cp-foreground);
|
|
260
|
+
background-color: var(--cp-accent);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.cp-tab-active {
|
|
264
|
+
background-color: var(--cp-primary);
|
|
265
|
+
color: var(--cp-primary-foreground);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.cp-chip {
|
|
269
|
+
background-color: var(--cp-accent);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.cp-chip-hover:hover {
|
|
273
|
+
background-color: color-mix(in srgb, var(--cp-accent) 80%, transparent);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.cp-chip-text {
|
|
277
|
+
color: var(--cp-accent-foreground);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.cp-pre {
|
|
281
|
+
background-color: var(--cp-muted);
|
|
282
|
+
color: var(--cp-foreground);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.cp-badge-muted {
|
|
286
|
+
background-color: var(--cp-muted);
|
|
287
|
+
color: var(--cp-muted-foreground);
|
|
288
|
+
}
|
|
289
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<template lang="">
|
|
2
|
+
<template v-if="type === 'text'">
|
|
3
|
+
<div class="space-y-2 animate-pulse">
|
|
4
|
+
<div v-for="n in normalizedRows" :key="n" class="w-full h-4 bg-gray-300 rounded"></div>
|
|
5
|
+
</div>
|
|
6
|
+
</template>
|
|
7
|
+
</template>
|
|
8
|
+
<script>
|
|
9
|
+
export default {
|
|
10
|
+
props: {
|
|
11
|
+
type: { type: String, default: "text" },
|
|
12
|
+
rows: { type: [Number, String], default: 1 },
|
|
13
|
+
},
|
|
14
|
+
computed: {
|
|
15
|
+
normalizedRows() {
|
|
16
|
+
return Number(this.rows) || 1;
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
</script>
|
|
21
|
+
<style lang=""></style>
|