This repository has no description
1<script module lang="ts">
2 import { tv, type VariantProps } from "tailwind-variants";
3 import type { ButtonVariants } from "./Button.svelte";
4
5 export const buttonGroup = tv({
6 base: "inline-flex items-center",
7 variants: {
8 // joined groups are the segmented control from Figma: the group draws the outer
9 // border and the recessed surface, and each segment's own 1px border is pulled
10 // onto it (-m-px) so an unselected segment shows the group's surface while a
11 // selected one covers it and reads as a raised card.
12 spaced: {
13 true: "gap-2",
14 false: [
15 "gap-0 rounded border border-border-default bg-background-subtle",
16 "*:relative *:-my-px",
17 "[&>*:first-child]:-ml-px [&>*:last-child]:-mr-px",
18 "[&>*:not(:first-child)]:-ml-px [&>*:not(:first-child)]:[--btn-radius-l:0px]",
19 "[&>*:not(:last-child)]:[--btn-radius-r:0px]",
20 "[&>*:focus-visible]:z-10 [&>*:hover]:z-10"
21 ]
22 }
23 },
24 defaultVariants: {
25 spaced: false
26 }
27 });
28
29 export type ButtonGroupVariants = VariantProps<typeof buttonGroup>;
30
31 /**
32 * Recipe for using ButtonGroup as a tab/segment switcher (e.g. Open/Closed issue filters,
33 * Write/Preview editor tabs). Per Figma, the selected segment reads as a raised, lighter
34 * surface (`default` + its inset shadow) while unselected segments are flat `ghost`
35 * buttons — never the plain `default` variant, which reads as the *hovered* state of an
36 * unselected segment. Figma also gives an unselected segment the same foreground as a
37 * selected one, where `ghost` on its own is muted, so the recipe overrides the colour
38 * here rather than changing `ghost` everywhere it's used.
39 */
40 export function segmentProps(selected: boolean): {
41 variant: ButtonVariants["variant"];
42 insetShadow: boolean;
43 class?: string;
44 } {
45 return selected
46 ? { variant: "default", insetShadow: true }
47 : { variant: "ghost", insetShadow: false, class: "text-foreground-default" };
48 }
49</script>
50
51<script lang="ts">
52 import type { Snippet } from "svelte";
53
54 interface Props {
55 spaced?: ButtonGroupVariants["spaced"];
56 class?: string;
57 children: Snippet;
58 }
59
60 let { spaced = false, class: className, children }: Props = $props();
61
62 const classes = $derived(buttonGroup({ spaced, class: className }));
63</script>
64
65<div class={classes}>
66 {@render children()}
67</div>