picocrank 1.18.0 → 1.18.2

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,6 +1,6 @@
1
1
  {
2
2
  "name": "picocrank",
3
- "version": "1.18.0",
3
+ "version": "1.18.2",
4
4
  "description": "A set of zero dependency Vue components built on the Femtocrank global CSS theme.",
5
5
  "main": "vite.config.js",
6
6
  "scripts": {
@@ -18,17 +18,17 @@
18
18
  "url": "https://github.com/jamesread/picocrank/issues"
19
19
  },
20
20
  "dependencies": {
21
- "@hugeicons/core-free-icons": "^4.1.1",
22
- "@hugeicons/vue": "^1.0.5",
23
- "@vitejs/plugin-vue": "^6.0.6",
21
+ "@hugeicons/core-free-icons": "^4.2.2",
22
+ "@hugeicons/vue": "^1.0.7",
23
+ "@vitejs/plugin-vue": "^6.0.7",
24
24
  "femtocrank": "^2.5.0",
25
- "unplugin-vue-components": "^32.0.0",
26
- "vite": "^8.0.10",
27
- "vue": "^3.5.33",
28
- "vue-router": "^5.0.6"
25
+ "unplugin-vue-components": "^32.1.0",
26
+ "vite": "^8.1.2",
27
+ "vue": "^3.5.39",
28
+ "vue-router": "^5.1.0"
29
29
  },
30
30
  "devDependencies": {
31
- "semantic-release": "^25.0.3"
31
+ "semantic-release": "^25.0.5"
32
32
  },
33
33
  "homepage": "https://github.com/jamesread/picocrank#readme"
34
34
  }
@@ -47,7 +47,10 @@ function addRouterLink(link, altTitle = null, options = {}) {
47
47
  icon: foundRoute.meta?.icon || PinIcon,
48
48
  type: 'route',
49
49
  props: options.props || {},
50
- description: options.description || foundRoute.meta?.description || null
50
+ description: options.description || foundRoute.meta?.description || null,
51
+ disabled: options.disabled || false,
52
+ iconColor: options.iconColor || null,
53
+ indicator: options.indicator || false
51
54
  }
52
55
 
53
56
  addNavigationLink(routeLink)
@@ -69,7 +72,10 @@ function addCallback(title, callback, options = {}) {
69
72
  icon: options.icon || PinIcon,
70
73
  callback: callback || (() => {}),
71
74
  title: title,
72
- description: options.description || null
75
+ description: options.description || null,
76
+ disabled: options.disabled || false,
77
+ iconColor: options.iconColor || null,
78
+ indicator: options.indicator || false
73
79
  }
74
80
 
75
81
  addNavigationLink(callbackLink)
@@ -4,11 +4,17 @@
4
4
  <button
5
5
  v-for="link in filteredLinks"
6
6
  :key="link.name"
7
- :class="['nav-button', { active: isLinkActive(link) }]"
7
+ :class="['nav-button', { active: isLinkActive(link), disabled: link.disabled }]"
8
+ :disabled="link.disabled"
8
9
  :title="link.title"
9
10
  @click="handleLinkClick(link)"
10
11
  >
11
- <div class="nav-button-icon">
12
+ <span
13
+ v-if="showIndicator(link)"
14
+ class="nav-button-indicator"
15
+ aria-label="Requires attention"
16
+ />
17
+ <div class="nav-button-icon" :style="getIconStyle(link)">
12
18
  <HugeiconsIcon
13
19
  :icon="link.icon"
14
20
  :width="iconSize"
@@ -102,10 +108,30 @@ const filteredLinks = computed(() => {
102
108
  });
103
109
 
104
110
  function isLinkActive(link) {
111
+ if (link.disabled) {
112
+ return false;
113
+ }
105
114
  return isActive(link);
106
115
  }
107
116
 
117
+ function getIconStyle(link) {
118
+ if (link.disabled || isLinkActive(link)) {
119
+ return undefined;
120
+ }
121
+ if (link.iconColor) {
122
+ return { color: link.iconColor };
123
+ }
124
+ return undefined;
125
+ }
126
+
127
+ function showIndicator(link) {
128
+ return link.indicator && !link.disabled;
129
+ }
130
+
108
131
  function handleLinkClick(link) {
132
+ if (link.disabled) {
133
+ return;
134
+ }
109
135
  if (link.type === 'route') {
110
136
  const to = link.to || link.path;
111
137
  if (to) {
@@ -147,6 +173,7 @@ function handleLinkClick(link) {
147
173
  cursor: pointer;
148
174
  transition: all 0.2s ease;
149
175
  text-align: left;
176
+ position: relative;
150
177
  }
151
178
 
152
179
  .navigation-grid.compact .nav-button {
@@ -188,6 +215,46 @@ function handleLinkClick(link) {
188
215
  color: #fff;
189
216
  }
190
217
 
218
+ .nav-button:disabled,
219
+ .nav-button.disabled {
220
+ cursor: not-allowed;
221
+ opacity: 0.6;
222
+ }
223
+
224
+ .nav-button:disabled:hover,
225
+ .nav-button.disabled:hover {
226
+ border-color: var(--border-color, #e1e5e9);
227
+ transform: none;
228
+ box-shadow: none;
229
+ background: transparent;
230
+ }
231
+
232
+ .nav-button:disabled .nav-button-icon,
233
+ .nav-button.disabled .nav-button-icon {
234
+ color: var(--text-muted, #999);
235
+ }
236
+
237
+ .nav-button-indicator {
238
+ position: absolute;
239
+ top: 0.75rem;
240
+ right: 0.75rem;
241
+ width: 0.625rem;
242
+ height: 0.625rem;
243
+ border-radius: 50%;
244
+ background: var(--indicator-color, #dc3545);
245
+ box-shadow: 0 0 0 2px var(--background-color, #fff);
246
+ flex-shrink: 0;
247
+ }
248
+
249
+ .navigation-grid.compact .nav-button-indicator {
250
+ top: 0.5rem;
251
+ right: 0.5rem;
252
+ }
253
+
254
+ .nav-button.active .nav-button-indicator {
255
+ box-shadow: 0 0 0 2px var(--primary-color, #007bff);
256
+ }
257
+
191
258
  .nav-button-label {
192
259
  font-weight: bold;
193
260
  font-size: 1.125rem;
@@ -28,7 +28,7 @@
28
28
  </template>
29
29
 
30
30
  <script setup>
31
- import { ref, computed, useId } from 'vue'
31
+ import { ref, computed, useId, watch } from 'vue'
32
32
  import { HugeiconsIcon } from '@hugeicons/vue'
33
33
  import { Copy01Icon, CopyCheckIcon } from '@hugeicons/core-free-icons'
34
34
 
@@ -83,25 +83,58 @@ const props = defineProps({
83
83
  },
84
84
  })
85
85
 
86
- const emit = defineEmits(['copy', 'copy-error'])
86
+ const emit = defineEmits(['copy', 'copy-error', 'update:modelValue'])
87
87
 
88
88
  const generatedId = useId()
89
89
  const textareaId = props.id || `readonly-textarea-${generatedId}`
90
90
  const textareaRef = ref(null)
91
91
  const copied = ref(false)
92
+ const content = ref(props.modelValue)
92
93
  let copiedTimer = null
93
94
 
95
+ watch(() => props.modelValue, (value) => {
96
+ content.value = value
97
+ })
98
+
94
99
  const displayValue = computed(() => {
95
- if (!props.markdownTicks || !props.modelValue) {
96
- return props.modelValue
100
+ if (!props.markdownTicks || !content.value) {
101
+ return content.value
97
102
  }
98
103
 
99
104
  const openingFence = props.markdownLang ? `\`\`\`${props.markdownLang}` : '```'
100
- return `${openingFence}\n${props.modelValue}\n\`\`\``
105
+ return `${openingFence}\n${content.value}\n\`\`\``
101
106
  })
102
107
 
108
+ function append(text) {
109
+ content.value += text
110
+ emit('update:modelValue', content.value)
111
+ }
112
+
113
+ function appendYamlProperty(k, v) {
114
+ const line = `${k}: ${v}`
115
+ if (content.value && !content.value.endsWith('\n')) {
116
+ content.value += '\n'
117
+ }
118
+ content.value += line
119
+ emit('update:modelValue', content.value)
120
+ }
121
+
122
+ function appendSection(sectionName) {
123
+ content.value += `\n# ${sectionName} ####\n`
124
+ emit('update:modelValue', content.value)
125
+ }
126
+
127
+ function clear() {
128
+ content.value = ''
129
+ emit('update:modelValue', content.value)
130
+ }
131
+
132
+ function getContentAsString() {
133
+ return content.value
134
+ }
135
+
103
136
  async function copy() {
104
- if (!props.modelValue) {
137
+ if (!content.value) {
105
138
  return
106
139
  }
107
140
 
@@ -133,6 +166,11 @@ function markCopied() {
133
166
 
134
167
  defineExpose({
135
168
  copy,
169
+ append,
170
+ appendYamlProperty,
171
+ appendSection,
172
+ clear,
173
+ getContentAsString,
136
174
  })
137
175
  </script>
138
176
 
@@ -143,6 +143,7 @@ defineExpose({
143
143
  background: transparent;
144
144
  border: none;
145
145
  border-bottom: 2px solid transparent;
146
+ border-radius: 0;
146
147
  cursor: pointer;
147
148
  font-size: 1em;
148
149
  color: var(--text-muted, #666);
@@ -165,7 +166,7 @@ defineExpose({
165
166
  .tab-button:focus {
166
167
  outline: 2px solid var(--primary-color, #007bff);
167
168
  outline-offset: -2px;
168
- border-radius: 4px 4px 0 0;
169
+ border-radius: 0;
169
170
  }
170
171
 
171
172
  .tabs-content {
@@ -174,7 +175,8 @@ defineExpose({
174
175
  }
175
176
 
176
177
  .tab-panel {
177
- padding: 1.5rem;
178
+ padding-block: 1.5rem;
179
+ padding-inline: 0.75rem;
178
180
  display: none;
179
181
  }
180
182
 
@@ -227,7 +229,8 @@ defineExpose({
227
229
  }
228
230
 
229
231
  .tab-panel {
230
- padding: 1rem;
232
+ padding-block: 1rem;
233
+ padding-inline: 0.5rem;
231
234
  }
232
235
  }
233
236
  </style>
@@ -29,7 +29,15 @@
29
29
  />
30
30
 
31
31
  <ReadOnlyTextArea
32
- label="Empty output"
32
+ ref="yamlConfigRef"
33
+ v-model="yamlConfig"
34
+ label="Configuration (YAML)"
35
+ :rows="12"
36
+ markdown-ticks
37
+ markdown-lang="yaml"
38
+ />
39
+
40
+ <ReadOnlyTextArea
33
41
  :rows="4"
34
42
  placeholder="No diagnostics available."
35
43
  />
@@ -42,13 +50,15 @@
42
50
  </template>
43
51
 
44
52
  <script setup>
45
- import { ref } from 'vue'
53
+ import { ref, nextTick } from 'vue'
46
54
  import Section from '../components/Section.vue'
47
55
  import ReadOnlyTextArea from '../components/ReadOnlyTextArea.vue'
48
56
 
49
57
  const diagnosticLog = ref('')
50
58
  const environmentInfo = ref('')
59
+ const yamlConfig = ref('')
51
60
  const lastCopied = ref('')
61
+ const yamlConfigRef = ref(null)
52
62
 
53
63
  function buildDiagnosticLog() {
54
64
  const now = new Date().toISOString()
@@ -81,10 +91,27 @@ function buildEnvironmentInfo() {
81
91
  }, null, 2)
82
92
  }
83
93
 
94
+ function buildYamlConfig() {
95
+ yamlConfig.value = ''
96
+ const area = yamlConfigRef.value
97
+ if (!area) {
98
+ return
99
+ }
100
+
101
+ area.appendSection('Application')
102
+ area.appendYamlProperty('name', 'picocrank')
103
+ area.appendYamlProperty('version', '1.0.0')
104
+ area.appendYamlProperty('environment', 'development')
105
+ area.appendSection('Runtime')
106
+ area.appendYamlProperty('language', navigator.language)
107
+ area.appendYamlProperty('online', navigator.onLine)
108
+ }
109
+
84
110
  function refreshDiagnostics() {
85
111
  diagnosticLog.value = buildDiagnosticLog()
86
112
  environmentInfo.value = buildEnvironmentInfo()
87
113
  lastCopied.value = ''
114
+ nextTick(() => buildYamlConfig())
88
115
  }
89
116
 
90
117
  function onCopy(value) {
@@ -60,7 +60,9 @@ onMounted(() => {
60
60
  }, {
61
61
  icon: ViewIcon,
62
62
  name: 'notifications',
63
- description: 'Manage notification settings'
63
+ description: 'Manage notification settings',
64
+ iconColor: '#e67e22',
65
+ indicator: true
64
66
  });
65
67
 
66
68
  localNavigation.value.addCallback('Billing', () => {
@@ -68,7 +70,8 @@ onMounted(() => {
68
70
  }, {
69
71
  icon: EditIcon,
70
72
  name: 'billing',
71
- description: 'View billing and subscription details'
73
+ description: 'View billing and subscription details',
74
+ disabled: true
72
75
  });
73
76
  }
74
77
  });