picocrank 1.21.4 → 1.22.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.
- package/package.json +8 -8
- package/public/themes/README.md +29 -0
- package/public/themes/catppuccin/theme.css +195 -0
- package/public/themes/dracula/theme.css +181 -0
- package/public/themes/index.json +7 -0
- package/public/themes/waffles/theme.css +143 -0
- package/router.js +20 -0
- package/styles/dark-theme.css +15 -1
- package/styles.css +18 -2
- package/vite.config.js +57 -0
- package/vue/components/CheckGroup.vue +156 -0
- package/vue/components/FormField.vue +14 -2
- package/vue/components/Header.vue +164 -35
- package/vue/components/QuickSearch.vue +325 -348
- package/vue/components/RadioGroup.vue +191 -0
- package/vue/components/Sidebar.vue +226 -262
- package/vue/components/SidebarNavLink.vue +161 -0
- package/vue/composables/useCustomTheme.js +162 -0
- package/vue/composables/useTheme.js +43 -10
- package/vue/data/examplePeople.js +45 -0
- package/vue/views/App.vue +85 -15
- package/vue/views/FormExample.vue +64 -29
- package/vue/views/PageStructure.vue +168 -95
- package/vue/views/TableExample.vue +2 -76
- package/vue/views/ThemePreview.vue +172 -0
- package/vue/views/UserDetails.vue +39 -0
- package/vue/views/Welcome.vue +9 -6
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="rootClasses"
|
|
4
|
+
role="radiogroup"
|
|
5
|
+
:aria-labelledby="ariaLabelledby || undefined"
|
|
6
|
+
:aria-label="ariaLabel || undefined"
|
|
7
|
+
:aria-disabled="disabled ? 'true' : undefined"
|
|
8
|
+
>
|
|
9
|
+
<label
|
|
10
|
+
v-for="option in normalizedOptions"
|
|
11
|
+
:key="option.key"
|
|
12
|
+
:class="{ disabled: disabled || option.disabled }"
|
|
13
|
+
>
|
|
14
|
+
<input
|
|
15
|
+
type="radio"
|
|
16
|
+
:name="groupName"
|
|
17
|
+
:value="option.key"
|
|
18
|
+
:checked="isSelected(option)"
|
|
19
|
+
:disabled="disabled || option.disabled"
|
|
20
|
+
@change="select(option)"
|
|
21
|
+
/>
|
|
22
|
+
<span>{{ option.label }}</span>
|
|
23
|
+
</label>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup>
|
|
28
|
+
import { computed, useId } from 'vue'
|
|
29
|
+
|
|
30
|
+
const props = defineProps({
|
|
31
|
+
modelValue: {
|
|
32
|
+
type: [String, Number, Boolean, null],
|
|
33
|
+
default: null,
|
|
34
|
+
},
|
|
35
|
+
options: {
|
|
36
|
+
type: Array,
|
|
37
|
+
required: true,
|
|
38
|
+
validator: (options) => Array.isArray(options) && options.every((option) => {
|
|
39
|
+
if (typeof option === 'string' || typeof option === 'number') {
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
return option
|
|
43
|
+
&& typeof option === 'object'
|
|
44
|
+
&& 'value' in option
|
|
45
|
+
&& typeof option.label === 'string'
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
name: {
|
|
49
|
+
type: String,
|
|
50
|
+
default: '',
|
|
51
|
+
},
|
|
52
|
+
variant: {
|
|
53
|
+
type: String,
|
|
54
|
+
default: 'default',
|
|
55
|
+
validator: (value) => ['default', 'boolean', 'list'].includes(value),
|
|
56
|
+
},
|
|
57
|
+
disabled: {
|
|
58
|
+
type: Boolean,
|
|
59
|
+
default: false,
|
|
60
|
+
},
|
|
61
|
+
ariaLabelledby: {
|
|
62
|
+
type: String,
|
|
63
|
+
default: '',
|
|
64
|
+
},
|
|
65
|
+
ariaLabel: {
|
|
66
|
+
type: String,
|
|
67
|
+
default: '',
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const emit = defineEmits(['update:modelValue', 'change'])
|
|
72
|
+
|
|
73
|
+
const generatedName = useId()
|
|
74
|
+
|
|
75
|
+
const groupName = computed(() => props.name || generatedName)
|
|
76
|
+
|
|
77
|
+
const rootClasses = computed(() => ({
|
|
78
|
+
'radio-group': true,
|
|
79
|
+
'radio-boolean': props.variant === 'boolean',
|
|
80
|
+
'radio-list': props.variant === 'list',
|
|
81
|
+
disabled: props.disabled,
|
|
82
|
+
}))
|
|
83
|
+
|
|
84
|
+
const normalizedOptions = computed(() => props.options.map((option, index) => {
|
|
85
|
+
if (typeof option === 'string' || typeof option === 'number') {
|
|
86
|
+
return {
|
|
87
|
+
key: String(index),
|
|
88
|
+
label: String(option),
|
|
89
|
+
value: option,
|
|
90
|
+
disabled: false,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
key: String(index),
|
|
96
|
+
label: option.label,
|
|
97
|
+
value: option.value,
|
|
98
|
+
disabled: Boolean(option.disabled),
|
|
99
|
+
}
|
|
100
|
+
}))
|
|
101
|
+
|
|
102
|
+
function isSelected(option) {
|
|
103
|
+
return props.modelValue === option.value
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function select(option) {
|
|
107
|
+
if (props.disabled || option.disabled) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
emit('update:modelValue', option.value)
|
|
111
|
+
emit('change', option.value)
|
|
112
|
+
}
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
<style scoped>
|
|
116
|
+
/*
|
|
117
|
+
* Default and boolean variants: hide the native radio chrome; keep the input
|
|
118
|
+
* for semantics/keyboard. List (stacked) keeps the visible radio icon.
|
|
119
|
+
*/
|
|
120
|
+
.radio-group label {
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
box-sizing: border-box;
|
|
124
|
+
padding-inline: 0.75em;
|
|
125
|
+
gap: 0.5em;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.radio-group:not(.radio-list) label {
|
|
129
|
+
position: relative;
|
|
130
|
+
justify-content: center;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.radio-group input[type="radio"] {
|
|
134
|
+
margin: 0;
|
|
135
|
+
flex-shrink: 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.radio-group:not(.radio-list) input[type="radio"] {
|
|
139
|
+
position: absolute;
|
|
140
|
+
inset: 0;
|
|
141
|
+
width: 100%;
|
|
142
|
+
height: 100%;
|
|
143
|
+
padding: 0;
|
|
144
|
+
opacity: 0;
|
|
145
|
+
cursor: inherit;
|
|
146
|
+
z-index: 1;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.radio-group label:has(input:focus-visible) {
|
|
150
|
+
outline: 2px solid black;
|
|
151
|
+
outline-offset: 2px;
|
|
152
|
+
z-index: 2;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* Beat Femtocrank's gray .radio-group focus/hover when the option is selected. */
|
|
156
|
+
.radio-group label:has(input:checked):hover,
|
|
157
|
+
.radio-group label:has(input:checked:focus-visible) {
|
|
158
|
+
background-color: #3a6f3a;
|
|
159
|
+
color: white;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.radio-group.disabled {
|
|
163
|
+
opacity: 0.7;
|
|
164
|
+
cursor: not-allowed;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.radio-group label.disabled,
|
|
168
|
+
.radio-group.disabled label {
|
|
169
|
+
background-color: var(--disabled-background-color);
|
|
170
|
+
color: var(--disabled-text-color);
|
|
171
|
+
cursor: not-allowed;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.radio-group label.disabled:hover,
|
|
175
|
+
.radio-group label.disabled:has(input:focus-visible),
|
|
176
|
+
.radio-group label.disabled:has(input:checked),
|
|
177
|
+
.radio-group label.disabled:has(input:checked):hover,
|
|
178
|
+
.radio-group.disabled label:hover,
|
|
179
|
+
.radio-group.disabled label:has(input:focus-visible),
|
|
180
|
+
.radio-group.disabled label:has(input:checked),
|
|
181
|
+
.radio-group.disabled label:has(input:checked):hover {
|
|
182
|
+
background-color: var(--disabled-background-color);
|
|
183
|
+
color: var(--disabled-text-color);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.radio-group label.disabled:has(input:focus-visible),
|
|
187
|
+
.radio-group.disabled label:has(input:focus-visible) {
|
|
188
|
+
outline: 2px solid black;
|
|
189
|
+
outline-offset: 2px;
|
|
190
|
+
}
|
|
191
|
+
</style>
|