File size: 1,616 Bytes
830f2f5
45fb44a
 
 
 
 
 
 
830f2f5
 
 
 
 
 
 
 
 
 
 
45fb44a
 
 
830f2f5
 
45fb44a
 
830f2f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45fb44a
 
 
 
 
 
 
830f2f5
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<script setup lang="ts" generic="T extends string">
import Icon, { type IconName } from "./Icon.vue";

/**
 * Segmented control — a styled radio group used in place of a native <select>.
 * Options with an `icon` show only the icon; `label` becomes its accessible name.
 */
defineProps<{ options: { value: T; label: string; icon?: IconName }[]; label: string }>();
const model = defineModel<T>({ required: true });
</script>

<template>
	<div class="seg" role="radiogroup" :aria-label="label">
		<button
			v-for="o in options"
			:key="o.value"
			type="button"
			role="radio"
			:aria-checked="model === o.value"
			:class="{ on: model === o.value, icon: o.icon }"
			:aria-label="o.icon ? o.label : undefined"
			:title="o.icon ? o.label : undefined"
			@click="model = o.value"
		>
			<Icon v-if="o.icon" :name="o.icon" />
			<template v-else>{{ o.label }}</template>
		</button>
	</div>
</template>

<style scoped>
.seg {
	display: inline-flex;
	padding: 3px;
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: var(--surface);
}
button {
	height: 32px;
	padding: 0 12px;
	border: 0;
	border-radius: 2px;
	background: transparent;
	color: var(--text-3);
	font-family: var(--font-mono);
	font-size: 11px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	cursor: pointer;
	transition: color 0.15s, background 0.15s;
}
button.icon {
	display: grid;
	place-items: center;
	width: 32px;
	padding: 0;
	font-size: 15px;
}
button:hover {
	color: var(--text);
}
button.on {
	background: var(--surface-2);
	color: var(--accent);
	box-shadow: inset 0 0 0 1px var(--line-strong);
}
</style>