@commonpub/editor 0.4.3 → 0.6.0

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 (36) hide show
  1. package/README.md +43 -10
  2. package/package.json +16 -4
  3. package/vue/components/BlockCanvas.vue +512 -0
  4. package/vue/components/BlockInsertZone.vue +86 -0
  5. package/vue/components/BlockPicker.vue +274 -0
  6. package/vue/components/BlockWrapper.vue +188 -0
  7. package/vue/components/EditorBlocks.vue +235 -0
  8. package/vue/components/EditorSection.vue +81 -0
  9. package/vue/components/EditorShell.vue +198 -0
  10. package/vue/components/EditorTagInput.vue +116 -0
  11. package/vue/components/EditorVisibility.vue +110 -0
  12. package/vue/components/blocks/BuildStepBlock.vue +103 -0
  13. package/vue/components/blocks/CalloutBlock.vue +123 -0
  14. package/vue/components/blocks/CheckpointBlock.vue +29 -0
  15. package/vue/components/blocks/CodeBlock.vue +178 -0
  16. package/vue/components/blocks/DividerBlock.vue +22 -0
  17. package/vue/components/blocks/DownloadsBlock.vue +43 -0
  18. package/vue/components/blocks/EmbedBlock.vue +22 -0
  19. package/vue/components/blocks/GalleryBlock.vue +235 -0
  20. package/vue/components/blocks/HeadingBlock.vue +97 -0
  21. package/vue/components/blocks/ImageBlock.vue +272 -0
  22. package/vue/components/blocks/MarkdownBlock.vue +259 -0
  23. package/vue/components/blocks/MathBlock.vue +39 -0
  24. package/vue/components/blocks/PartsListBlock.vue +354 -0
  25. package/vue/components/blocks/QuizBlock.vue +49 -0
  26. package/vue/components/blocks/QuoteBlock.vue +102 -0
  27. package/vue/components/blocks/SectionHeaderBlock.vue +132 -0
  28. package/vue/components/blocks/SliderBlock.vue +320 -0
  29. package/vue/components/blocks/TextBlock.vue +202 -0
  30. package/vue/components/blocks/ToolListBlock.vue +71 -0
  31. package/vue/components/blocks/VideoBlock.vue +24 -0
  32. package/vue/composables/useBlockEditor.ts +190 -0
  33. package/vue/index.ts +59 -0
  34. package/vue/provide.ts +28 -0
  35. package/vue/types.ts +40 -0
  36. package/vue/utils.ts +12 -0
@@ -0,0 +1,235 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Reusable block library sidebar for editors.
4
+ * Renders a searchable list of insertable blocks grouped by category.
5
+ * Clicking a block adds it to the end of the document via the block editor composable.
6
+ */
7
+ import { ref, computed } from 'vue';
8
+ import type { BlockDef, BlockGroup } from '../types.js';
9
+ import type { BlockEditor } from '../composables/useBlockEditor.js';
10
+
11
+ const props = defineProps<{
12
+ groups: BlockGroup[];
13
+ blockEditor: BlockEditor;
14
+ }>();
15
+
16
+ const blockSearch = ref('');
17
+
18
+ const filteredGroups = computed(() => {
19
+ const q = blockSearch.value.toLowerCase();
20
+ if (!q) return props.groups;
21
+ return props.groups
22
+ .map((g) => ({ ...g, blocks: g.blocks.filter((b) => b.label.toLowerCase().includes(q)) }))
23
+ .filter((g) => g.blocks.length > 0);
24
+ });
25
+
26
+ function insertBlock(block: BlockDef): void {
27
+ const selectedId = props.blockEditor.selectedBlockId.value;
28
+ if (selectedId) {
29
+ const idx = props.blockEditor.getBlockIndex(selectedId);
30
+ props.blockEditor.addBlock(block.type, block.attrs, idx + 1);
31
+ } else {
32
+ props.blockEditor.addBlock(block.type, block.attrs);
33
+ }
34
+ }
35
+ </script>
36
+
37
+ <template>
38
+ <div class="cpub-block-library">
39
+ <div class="cpub-bl-search">
40
+ <i class="fa-solid fa-magnifying-glass cpub-bl-search-icon"></i>
41
+ <input
42
+ v-model="blockSearch"
43
+ type="text"
44
+ placeholder="Search blocks..."
45
+ class="cpub-bl-search-input"
46
+ aria-label="Search blocks"
47
+ />
48
+ </div>
49
+ <div class="cpub-bl-groups">
50
+ <div v-for="group in filteredGroups" :key="group.name" class="cpub-bl-group">
51
+ <div class="cpub-bl-group-label">{{ group.name }}</div>
52
+ <div class="cpub-bl-blocks">
53
+ <button
54
+ v-for="block in group.blocks"
55
+ :key="block.type + (block.attrs?.variant ?? '')"
56
+ :data-block="block.type"
57
+ class="cpub-bl-block"
58
+ :class="group.variant"
59
+ :title="block.label"
60
+ @click="insertBlock(block)"
61
+ >
62
+ <span class="cpub-bl-block-icon"><i :class="['fa-solid', block.icon]"></i></span>
63
+ <span class="cpub-bl-block-label">{{ block.label }}</span>
64
+ <span class="cpub-bl-block-drag"><i class="fa-solid fa-grip-dots-vertical"></i></span>
65
+ </button>
66
+ </div>
67
+ </div>
68
+ <div v-if="filteredGroups.length === 0" class="cpub-bl-empty">
69
+ No blocks match "{{ blockSearch }}"
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </template>
74
+
75
+ <style scoped>
76
+ .cpub-block-library {
77
+ display: flex;
78
+ flex-direction: column;
79
+ height: 100%;
80
+ }
81
+
82
+ .cpub-bl-search {
83
+ display: flex;
84
+ align-items: center;
85
+ gap: 7px;
86
+ background: var(--surface2);
87
+ border: var(--border-width-default) solid var(--border);
88
+ padding: 5px 9px;
89
+ margin: 10px 8px 4px;
90
+ }
91
+
92
+ .cpub-bl-search-icon {
93
+ font-size: 10px;
94
+ color: var(--text-faint);
95
+ flex-shrink: 0;
96
+ }
97
+
98
+ .cpub-bl-search-input {
99
+ background: transparent;
100
+ border: none;
101
+ outline: none;
102
+ font-size: 12px;
103
+ color: var(--text);
104
+ width: 100%;
105
+ }
106
+
107
+ .cpub-bl-search-input::placeholder {
108
+ color: var(--text-faint);
109
+ }
110
+
111
+ .cpub-bl-groups {
112
+ flex: 1;
113
+ overflow-y: auto;
114
+ padding: 4px 0;
115
+ }
116
+
117
+ .cpub-bl-group {
118
+ padding: 4px 0;
119
+ }
120
+
121
+ .cpub-bl-group-label {
122
+ font-family: var(--font-mono);
123
+ font-size: 9px;
124
+ font-weight: 600;
125
+ letter-spacing: 0.14em;
126
+ text-transform: uppercase;
127
+ color: var(--text-faint);
128
+ padding: 6px 12px 4px;
129
+ }
130
+
131
+ .cpub-bl-blocks {
132
+ display: flex;
133
+ flex-direction: column;
134
+ gap: 1px;
135
+ }
136
+
137
+ .cpub-bl-block {
138
+ display: flex;
139
+ align-items: center;
140
+ gap: 9px;
141
+ padding: 10px 10px;
142
+ cursor: pointer;
143
+ border: var(--border-width-default) solid transparent;
144
+ background: transparent;
145
+ color: var(--text-dim);
146
+ font-size: 12px;
147
+ user-select: none;
148
+ transition: background 0.1s;
149
+ text-align: left;
150
+ width: 100%;
151
+ margin: 0 4px;
152
+ }
153
+
154
+ .cpub-bl-block:hover {
155
+ background: var(--surface2);
156
+ border-color: var(--border2);
157
+ color: var(--text);
158
+ }
159
+
160
+ .cpub-bl-block-icon {
161
+ width: 22px;
162
+ height: 22px;
163
+ background: var(--surface3);
164
+ border: var(--border-width-default) solid var(--border2);
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ font-size: 9px;
169
+ color: var(--text-faint);
170
+ flex-shrink: 0;
171
+ transition: background 0.1s, color 0.1s;
172
+ }
173
+
174
+ .cpub-bl-block:hover .cpub-bl-block-icon {
175
+ background: var(--accent-bg);
176
+ border-color: var(--accent-border);
177
+ color: var(--accent);
178
+ }
179
+
180
+ .cpub-bl-block-label {
181
+ font-size: 11px;
182
+ flex: 1;
183
+ }
184
+
185
+ .cpub-bl-block-drag {
186
+ font-size: 9px;
187
+ color: var(--text-faint);
188
+ opacity: 0;
189
+ transition: opacity 0.1s;
190
+ }
191
+
192
+ .cpub-bl-block:hover .cpub-bl-block-drag {
193
+ opacity: 1;
194
+ }
195
+
196
+ .cpub-bl-empty {
197
+ font-size: 11px;
198
+ color: var(--text-faint);
199
+ padding: 12px;
200
+ text-align: center;
201
+ }
202
+
203
+ @media (hover: none) {
204
+ .cpub-bl-block-drag { opacity: 1; }
205
+ }
206
+
207
+ /* Per-block-type icon colors */
208
+ [data-block="heading"] .cpub-bl-block-icon { color: var(--teal); background: color-mix(in srgb, var(--teal) 10%, transparent); }
209
+ [data-block="text"] .cpub-bl-block-icon,
210
+ [data-block="paragraph"] .cpub-bl-block-icon { color: var(--text-dim); background: var(--surface2); }
211
+ [data-block="image"] .cpub-bl-block-icon { color: #38bdf8; background: rgba(56, 189, 248, 0.08); }
212
+ [data-block="code"] .cpub-bl-block-icon,
213
+ [data-block="code_block"] .cpub-bl-block-icon { color: #c084fc; background: rgba(192, 132, 252, 0.08); }
214
+ [data-block="callout"] .cpub-bl-block-icon { color: #fbbf24; background: rgba(251, 191, 36, 0.08); }
215
+ [data-block="quote"] .cpub-bl-block-icon,
216
+ [data-block="blockquote"] .cpub-bl-block-icon { color: #94a3b8; background: rgba(148, 163, 184, 0.08); }
217
+ [data-block="embed"] .cpub-bl-block-icon { color: #f472b6; background: rgba(244, 114, 182, 0.08); }
218
+ [data-block="video"] .cpub-bl-block-icon { color: #fb923c; background: rgba(251, 146, 60, 0.08); }
219
+ [data-block="divider"] .cpub-bl-block-icon,
220
+ [data-block="horizontal_rule"] .cpub-bl-block-icon,
221
+ [data-block="horizontalRule"] .cpub-bl-block-icon { color: var(--text-faint); background: var(--surface2); }
222
+ [data-block="gallery"] .cpub-bl-block-icon { color: #2dd4bf; background: rgba(45, 212, 191, 0.08); }
223
+ [data-block="quiz"] .cpub-bl-block-icon { color: #4ade80; background: rgba(74, 222, 128, 0.08); }
224
+ [data-block="slider"] .cpub-bl-block-icon,
225
+ [data-block="interactiveSlider"] .cpub-bl-block-icon { color: #818cf8; background: rgba(129, 140, 248, 0.08); }
226
+ [data-block="math"] .cpub-bl-block-icon,
227
+ [data-block="mathNotation"] .cpub-bl-block-icon { color: #e879f9; background: rgba(232, 121, 249, 0.08); }
228
+ [data-block="markdown"] .cpub-bl-block-icon { color: var(--text-dim); background: var(--surface2); }
229
+ [data-block="buildStep"] .cpub-bl-block-icon { color: var(--accent); background: var(--accent-bg); }
230
+ [data-block="partsList"] .cpub-bl-block-icon { color: #fb7185; background: rgba(251, 113, 133, 0.08); }
231
+ [data-block="toolList"] .cpub-bl-block-icon { color: #a78bfa; background: rgba(167, 139, 250, 0.08); }
232
+ [data-block="downloads"] .cpub-bl-block-icon { color: #22d3ee; background: rgba(34, 211, 238, 0.08); }
233
+ [data-block="sectionHeader"] .cpub-bl-block-icon { color: var(--accent); background: var(--accent-bg); }
234
+ [data-block="checkpoint"] .cpub-bl-block-icon { color: #34d399; background: rgba(52, 211, 153, 0.08); }
235
+ </style>
@@ -0,0 +1,81 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Collapsible section for editor property panels.
4
+ * Reused across all editor types.
5
+ */
6
+ defineProps<{
7
+ title: string;
8
+ icon: string;
9
+ open?: boolean;
10
+ }>();
11
+
12
+ const emit = defineEmits<{
13
+ toggle: [];
14
+ }>();
15
+ </script>
16
+
17
+ <template>
18
+ <div class="cpub-ep-section" :class="{ collapsed: !open }">
19
+ <button class="cpub-ep-section-header" @click="emit('toggle')">
20
+ <i :class="['fa', icon, 'cpub-ep-sec-icon']"></i>
21
+ <span class="cpub-ep-sec-label">{{ title }}</span>
22
+ <i class="fa-solid fa-chevron-down cpub-ep-sec-arrow"></i>
23
+ </button>
24
+ <div v-if="open" class="cpub-ep-section-body">
25
+ <slot />
26
+ </div>
27
+ </div>
28
+ </template>
29
+
30
+ <style scoped>
31
+ .cpub-ep-section {
32
+ border-bottom: var(--border-width-default) solid var(--border);
33
+ }
34
+
35
+ .cpub-ep-section-header {
36
+ display: flex;
37
+ align-items: center;
38
+ gap: 8px;
39
+ width: 100%;
40
+ padding: 10px 12px;
41
+ background: none;
42
+ border: none;
43
+ cursor: pointer;
44
+ color: var(--text);
45
+ font-size: 11px;
46
+ font-family: var(--font-mono);
47
+ font-weight: 600;
48
+ letter-spacing: 0.08em;
49
+ text-transform: uppercase;
50
+ text-align: left;
51
+ }
52
+
53
+ .cpub-ep-section-header:hover {
54
+ background: var(--surface2);
55
+ }
56
+
57
+ .cpub-ep-sec-icon {
58
+ font-size: 10px;
59
+ color: var(--text-faint);
60
+ width: 14px;
61
+ text-align: center;
62
+ }
63
+
64
+ .cpub-ep-sec-label {
65
+ flex: 1;
66
+ }
67
+
68
+ .cpub-ep-sec-arrow {
69
+ font-size: 9px;
70
+ color: var(--text-faint);
71
+ transition: transform 0.15s;
72
+ }
73
+
74
+ .collapsed .cpub-ep-sec-arrow {
75
+ transform: rotate(-90deg);
76
+ }
77
+
78
+ .cpub-ep-section-body {
79
+ padding: 8px 12px 14px;
80
+ }
81
+ </style>
@@ -0,0 +1,198 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue';
3
+
4
+ defineProps<{
5
+ showLeftSidebar?: boolean;
6
+ showRightSidebar?: boolean;
7
+ }>();
8
+
9
+ const leftOpen = ref(false);
10
+ const rightOpen = ref(false);
11
+
12
+ function toggleLeft(): void {
13
+ leftOpen.value = !leftOpen.value;
14
+ if (leftOpen.value) rightOpen.value = false;
15
+ }
16
+
17
+ function toggleRight(): void {
18
+ rightOpen.value = !rightOpen.value;
19
+ if (rightOpen.value) leftOpen.value = false;
20
+ }
21
+ </script>
22
+
23
+ <template>
24
+ <div class="cpub-editor-shell-wrapper">
25
+ <div class="cpub-editor-shell-inner">
26
+ <!-- Mobile sidebar toggles -->
27
+ <div class="cpub-editor-mobile-toggles">
28
+ <button v-if="showLeftSidebar" class="cpub-editor-toggle-btn" aria-label="Toggle blocks panel" @click="toggleLeft">
29
+ <i class="fa-solid fa-layer-group"></i>
30
+ </button>
31
+ <button v-if="showRightSidebar" class="cpub-editor-toggle-btn" aria-label="Toggle properties panel" @click="toggleRight">
32
+ <i class="fa-solid fa-sliders"></i>
33
+ </button>
34
+ </div>
35
+
36
+ <!-- Left sidebar -->
37
+ <aside
38
+ v-if="showLeftSidebar"
39
+ class="cpub-editor-left"
40
+ :class="{ 'cpub-editor-sidebar-open': leftOpen }"
41
+ aria-label="Editor sidebar"
42
+ >
43
+ <slot name="left" />
44
+ </aside>
45
+
46
+ <!-- Overlay for mobile sidebars -->
47
+ <div v-if="leftOpen || rightOpen" class="cpub-editor-overlay" @click="leftOpen = false; rightOpen = false" />
48
+
49
+ <div class="cpub-editor-center">
50
+ <slot />
51
+ </div>
52
+
53
+ <!-- Right sidebar -->
54
+ <aside
55
+ v-if="showRightSidebar"
56
+ class="cpub-editor-right"
57
+ :class="{ 'cpub-editor-sidebar-open': rightOpen }"
58
+ aria-label="Properties"
59
+ >
60
+ <slot name="right" />
61
+ </aside>
62
+ </div>
63
+
64
+ <!-- Status bar -->
65
+ <div v-if="$slots.status" class="cpub-editor-status-bar">
66
+ <slot name="status" />
67
+ </div>
68
+ </div>
69
+ </template>
70
+
71
+ <style scoped>
72
+ .cpub-editor-shell-wrapper {
73
+ display: flex;
74
+ flex-direction: column;
75
+ flex: 1;
76
+ overflow: hidden;
77
+ }
78
+
79
+ .cpub-editor-shell-inner {
80
+ display: flex;
81
+ flex: 1;
82
+ overflow: hidden;
83
+ position: relative;
84
+ }
85
+
86
+ .cpub-editor-status-bar {
87
+ height: 28px;
88
+ flex-shrink: 0;
89
+ background: var(--surface);
90
+ border-top: var(--border-width-default) solid var(--border);
91
+ display: flex;
92
+ align-items: center;
93
+ padding: 0 12px;
94
+ gap: 14px;
95
+ font-family: var(--font-mono);
96
+ font-size: 10px;
97
+ color: var(--text-faint);
98
+ }
99
+
100
+ .cpub-editor-left {
101
+ width: 220px;
102
+ flex-shrink: 0;
103
+ background: var(--surface);
104
+ border-right: var(--border-width-default) solid var(--border);
105
+ overflow-y: auto;
106
+ padding: var(--space-4);
107
+ }
108
+
109
+ .cpub-editor-center {
110
+ flex: 1;
111
+ overflow-y: auto;
112
+ padding: var(--space-6);
113
+ background: var(--bg);
114
+ }
115
+
116
+ .cpub-editor-right {
117
+ width: 280px;
118
+ flex-shrink: 0;
119
+ background: var(--surface);
120
+ border-left: var(--border-width-default) solid var(--border);
121
+ overflow-y: auto;
122
+ padding: var(--space-4);
123
+ }
124
+
125
+ .cpub-editor-mobile-toggles {
126
+ display: none;
127
+ }
128
+
129
+ .cpub-editor-overlay {
130
+ display: none;
131
+ }
132
+
133
+ @media (max-width: 1024px) {
134
+ .cpub-editor-left,
135
+ .cpub-editor-right {
136
+ position: fixed;
137
+ top: 0;
138
+ bottom: 0;
139
+ z-index: var(--z-modal, 200);
140
+ transform: translateX(-100%);
141
+ transition: transform 0.2s ease;
142
+ }
143
+
144
+ .cpub-editor-left {
145
+ left: 0;
146
+ }
147
+
148
+ .cpub-editor-right {
149
+ right: 0;
150
+ left: auto;
151
+ transform: translateX(100%);
152
+ }
153
+
154
+ .cpub-editor-left.cpub-editor-sidebar-open {
155
+ transform: translateX(0);
156
+ }
157
+
158
+ .cpub-editor-right.cpub-editor-sidebar-open {
159
+ transform: translateX(0);
160
+ }
161
+
162
+ .cpub-editor-overlay {
163
+ display: block;
164
+ position: fixed;
165
+ inset: 0;
166
+ background: var(--color-surface-overlay-light);
167
+ z-index: calc(var(--z-modal, 200) - 1);
168
+ }
169
+
170
+ .cpub-editor-mobile-toggles {
171
+ display: flex;
172
+ position: fixed;
173
+ bottom: var(--space-4);
174
+ right: var(--space-4);
175
+ gap: var(--space-2);
176
+ z-index: var(--z-fixed, 100);
177
+ }
178
+
179
+ .cpub-editor-toggle-btn {
180
+ width: 44px;
181
+ height: 44px;
182
+ border: var(--border-width-default) solid var(--border);
183
+ background: var(--surface);
184
+ color: var(--text-dim);
185
+ font-size: 16px;
186
+ cursor: pointer;
187
+ display: flex;
188
+ align-items: center;
189
+ justify-content: center;
190
+ box-shadow: var(--shadow-md, var(--shadow-md));
191
+ }
192
+
193
+ .cpub-editor-toggle-btn:hover {
194
+ background: var(--surface2);
195
+ color: var(--text);
196
+ }
197
+ }
198
+ </style>
@@ -0,0 +1,116 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Reusable tag input for editor panels.
4
+ * Used by Article, Blog, Project editors.
5
+ */
6
+ import { ref } from 'vue';
7
+
8
+ const props = defineProps<{
9
+ tags: string[];
10
+ }>();
11
+
12
+ const emit = defineEmits<{
13
+ 'update:tags': [tags: string[]];
14
+ }>();
15
+
16
+ const tagInput = ref('');
17
+
18
+ function addTag(e: KeyboardEvent): void {
19
+ if (e.key === 'Enter' || e.key === ',') {
20
+ e.preventDefault();
21
+ const val = tagInput.value.trim().replace(/,$/, '');
22
+ if (val && !props.tags.includes(val)) {
23
+ emit('update:tags', [...props.tags, val]);
24
+ }
25
+ tagInput.value = '';
26
+ }
27
+ }
28
+
29
+ function removeTag(idx: number): void {
30
+ emit('update:tags', props.tags.filter((_: string, i: number) => i !== idx));
31
+ }
32
+ </script>
33
+
34
+ <template>
35
+ <div class="cpub-tag-input-wrap">
36
+ <div class="cpub-tag-chips">
37
+ <span v-for="(tag, i) in tags" :key="i" class="cpub-tag-chip">
38
+ {{ tag }}
39
+ <button class="cpub-tag-remove" aria-label="Remove tag" @click="removeTag(i)">
40
+ <i class="fa-solid fa-xmark"></i>
41
+ </button>
42
+ </span>
43
+ </div>
44
+ <input
45
+ v-model="tagInput"
46
+ type="text"
47
+ class="cpub-tag-input"
48
+ placeholder="Add tag..."
49
+ aria-label="Add tag"
50
+ @keydown="addTag"
51
+ />
52
+ </div>
53
+ </template>
54
+
55
+ <style scoped>
56
+ .cpub-tag-input-wrap {
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: 6px;
60
+ }
61
+
62
+ .cpub-tag-chips {
63
+ display: flex;
64
+ flex-wrap: wrap;
65
+ gap: 4px;
66
+ }
67
+
68
+ .cpub-tag-chip {
69
+ display: inline-flex;
70
+ align-items: center;
71
+ gap: 4px;
72
+ font-size: 11px;
73
+ font-family: var(--font-mono);
74
+ padding: 2px 8px;
75
+ background: var(--surface2);
76
+ border: var(--border-width-default) solid var(--border2);
77
+ color: var(--text-dim);
78
+ }
79
+
80
+ .cpub-tag-chip:hover {
81
+ border-color: var(--accent);
82
+ color: var(--accent);
83
+ }
84
+
85
+ .cpub-tag-remove {
86
+ background: none;
87
+ border: none;
88
+ color: var(--text-faint);
89
+ cursor: pointer;
90
+ font-size: 9px;
91
+ padding: 0;
92
+ line-height: 1;
93
+ }
94
+
95
+ .cpub-tag-remove:hover {
96
+ color: var(--red);
97
+ }
98
+
99
+ .cpub-tag-input {
100
+ width: 100%;
101
+ background: var(--surface);
102
+ border: var(--border-width-default) solid var(--border);
103
+ padding: 5px 8px;
104
+ font-size: 11px;
105
+ color: var(--text);
106
+ outline: none;
107
+ }
108
+
109
+ .cpub-tag-input:focus {
110
+ border-color: var(--accent);
111
+ }
112
+
113
+ .cpub-tag-input::placeholder {
114
+ color: var(--text-faint);
115
+ }
116
+ </style>