panelui-web 0.9.0 → 0.9.1

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ButtonGroup — related actions joined into one control.
2
+ * ButtonGroup — related actions held together in one control.
3
3
  *
4
4
  * ```tsx
5
5
  * <ButtonGroup aria-label="History">
@@ -8,21 +8,30 @@
8
8
  * </ButtonGroup>
9
9
  * ```
10
10
  *
11
- * ## Joining
11
+ * ## A track, like the segmented Tabs
12
12
  *
13
- * The group does not draw anything of its own. It squares the corners where
14
- * two children meet and drops the border one of them would otherwise double,
15
- * so outline buttons read as one strip with dividers. It does that with child
16
- * selectors rather than props, so any `Button`, `Input`, `Menu.Trigger` or
17
- * `Select.Trigger` placed directly inside joins without being told to.
13
+ * The group is drawn the way the segmented `Tabs` are: a muted track with
14
+ * rounded corners, and the buttons inside it at the radius one step smaller,
15
+ * so every corner follows the track's. Outline and ghost buttons lie flat in
16
+ * the track with muted labels; the one under the pointer rises out of it the
17
+ * way the chosen tab does, and the short dividers on either side of it fade.
18
+ * A field or a filled button keeps its own surface, so it reads as raised
19
+ * from the start.
18
20
  *
19
- * Filled buttons have no border to share, so put a `ButtonGroup.Separator`
20
- * between them; without one, two primary buttons become one long button.
21
+ * It does this with child selectors rather than props, so any `Button`,
22
+ * `Input`, `Menu.Trigger` or `ButtonGroup.Text` placed directly inside takes
23
+ * part without being told to.
24
+ *
25
+ * ## Split buttons
26
+ *
27
+ * A group with a `ButtonGroup.Separator` in it is a split button instead: no
28
+ * track, and its filled halves joined edge to edge, with the separator
29
+ * carrying their fill across the seam.
21
30
  *
22
31
  * ## Groups of groups
23
32
  *
24
- * A group whose children are groups spaces them apart instead of joining
25
- * them, which is how a toolbar is built: one group per kind of action.
33
+ * A group whose children are groups spaces them apart, which is how a
34
+ * toolbar is built: one track per kind of action.
26
35
  */
27
36
  import type { ComponentProps } from 'react';
28
37
  import { type VariantProps } from 'tailwind-variants';
@@ -51,7 +60,7 @@ export declare const buttonGroupVariants: import("tailwind-variants").TVReturnTy
51
60
  };
52
61
  }, {
53
62
  root: string[];
54
- separator: string;
63
+ separator: string[];
55
64
  text: string[];
56
65
  }, undefined, {
57
66
  orientation: {
@@ -78,7 +87,7 @@ export declare const buttonGroupVariants: import("tailwind-variants").TVReturnTy
78
87
  };
79
88
  }, {
80
89
  root: string[];
81
- separator: string;
90
+ separator: string[];
82
91
  text: string[];
83
92
  }, import("tailwind-variants").TVReturnType<{
84
93
  orientation: {
@@ -105,7 +114,7 @@ export declare const buttonGroupVariants: import("tailwind-variants").TVReturnTy
105
114
  };
106
115
  }, {
107
116
  root: string[];
108
- separator: string;
117
+ separator: string[];
109
118
  text: string[];
110
119
  }, undefined, unknown, unknown, undefined>>;
111
120
  export type ButtonGroupOrientation = NonNullable<VariantProps<typeof buttonGroupVariants>['orientation']>;
@@ -130,8 +139,11 @@ export interface ButtonGroupSeparatorProps extends ComponentProps<'span'> {
130
139
  orientation?: 'vertical' | 'horizontal';
131
140
  }
132
141
  /**
133
- * A line between two children that have no border to share, such as filled
134
- * buttons. It is decorative and hidden from assistive technology.
142
+ * The divider of a split button. A group with one in it drops its track and
143
+ * joins its children edge to edge. Before a primary, secondary or destructive
144
+ * button the separator takes that button's fill, so the two halves read as
145
+ * one, and draws a short line in the button's text colour. It is decorative
146
+ * and hidden from assistive technology.
135
147
  */
136
148
  declare function ButtonGroupSeparator({ orientation, className, ...props }: ButtonGroupSeparatorProps): import("react").JSX.Element;
137
149
  declare namespace ButtonGroupSeparator {
@@ -1,19 +1,53 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { tv } from 'tailwind-variants';
3
+ /*
4
+ * Which of the three a group is depends on what it holds, so the rules below
5
+ * are scoped by selector rather than by prop:
6
+ *
7
+ * track &:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))
8
+ * split &:has(>[data-slot=button-group-separator])
9
+ *
10
+ * Tailwind reads class names whole, so each selector is written out in full
11
+ * in every class that uses it.
12
+ */
3
13
  export const buttonGroupVariants = tv({
4
14
  slots: {
5
15
  root: [
6
16
  'flex w-fit items-stretch',
7
17
  // A focused child draws its ring over its neighbours, not under them.
8
18
  '*:focus-visible:relative *:focus-visible:z-10',
9
- // Groups inside a group are spaced, not joined.
10
- 'has-[>[data-slot=button-group]]:gap-2',
11
19
  // A field takes the room the buttons leave.
12
20
  '[&>[data-slot=input]]:min-w-0 [&>[data-slot=input]]:flex-1',
21
+ // The track, in the segmented Tabs' colour and radius. Its children
22
+ // take the radius one step down, which is the track's less its padding.
23
+ '[&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))]:gap-1 [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))]:rounded-xl [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))]:bg-muted [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))]:p-1',
24
+ '[&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*]:relative [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*]:rounded-lg',
25
+ // Outline and ghost buttons lie flat, with muted labels, and rise out
26
+ // of the track under the pointer like the chosen tab.
27
+ '[&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>:is([data-variant=outline],[data-variant=ghost])]:border-transparent [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>:is([data-variant=outline],[data-variant=ghost])]:bg-transparent [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>:is([data-variant=outline],[data-variant=ghost])]:text-muted-foreground [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>:is([data-variant=outline],[data-variant=ghost])]:shadow-none',
28
+ '[&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>:is([data-variant=outline],[data-variant=ghost]):hover:not(:disabled)]:bg-background [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>:is([data-variant=outline],[data-variant=ghost]):hover:not(:disabled)]:text-foreground [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>:is([data-variant=outline],[data-variant=ghost]):hover:not(:disabled)]:shadow-sm',
29
+ '[&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>:is([data-variant=outline],[data-variant=ghost]):hover:not(:disabled)]:inset-ring [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>:is([data-variant=outline],[data-variant=ghost]):hover:not(:disabled)]:inset-ring-foreground/[0.06]',
30
+ 'dark:[&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>:is([data-variant=outline],[data-variant=ghost]):hover:not(:disabled)]:bg-input dark:[&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>:is([data-variant=outline],[data-variant=ghost]):hover:not(:disabled)]:inset-ring-foreground/10',
31
+ // Short dividers in the gaps, drawn by the second child of each pair so
32
+ // the first never gets one. None beside a field, which has its own
33
+ // edge, and none beside the child under the pointer.
34
+ "[&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*+*]:before:pointer-events-none [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*+*]:before:absolute [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*+*]:before:bg-foreground/10 [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*+*]:before:transition-opacity [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*+*]:before:duration-150 [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*+*]:before:content-['']",
35
+ '[&>[data-slot=input]+*]:before:hidden [&>*+[data-slot=input]]:before:hidden',
36
+ '[&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*+*:hover:not(:disabled)]:before:opacity-0 [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*:hover:not(:disabled)+*]:before:opacity-0',
37
+ // Groups inside a group are spaced, one track each.
38
+ 'has-[>[data-slot=button-group]]:gap-2',
39
+ ],
40
+ separator: [
41
+ 'relative shrink-0 self-stretch bg-border',
42
+ // Between filled buttons the separator carries their fill across the
43
+ // seam, and a short line in their text colour divides it.
44
+ "before:absolute before:content-['']",
45
+ 'has-[+[data-variant=default]]:bg-primary has-[+[data-variant=default]]:before:bg-primary-foreground/25',
46
+ 'has-[+[data-variant=secondary]]:bg-secondary has-[+[data-variant=secondary]]:before:bg-secondary-foreground/15',
47
+ 'has-[+[data-variant=destructive]]:bg-destructive has-[+[data-variant=destructive]]:before:bg-white/30',
13
48
  ],
14
- separator: 'shrink-0 self-stretch bg-border',
15
49
  text: [
16
- 'flex shrink-0 items-center gap-2 whitespace-nowrap border bg-muted font-medium text-muted-foreground shadow-xs dark:bg-input/30',
50
+ 'relative flex shrink-0 items-center gap-2 whitespace-nowrap font-medium text-muted-foreground',
17
51
  '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',
18
52
  ],
19
53
  },
@@ -21,25 +55,26 @@ export const buttonGroupVariants = tv({
21
55
  orientation: {
22
56
  horizontal: {
23
57
  root: [
24
- '[&>*:not(:first-child)]:rounded-l-none [&>*:not(:last-child)]:rounded-r-none',
25
- '[&>*:not(:first-child)]:border-l-0',
58
+ '[&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*+*]:before:inset-y-2 [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*+*]:before:-left-0.5 [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*+*]:before:w-px',
59
+ // A split button's halves meet edge to edge.
60
+ '[&:has(>[data-slot=button-group-separator])>*:not(:first-child)]:rounded-l-none [&:has(>[data-slot=button-group-separator])>*:not(:last-child)]:rounded-r-none',
26
61
  ],
27
- separator: 'w-px',
62
+ separator: 'w-px before:inset-y-2 before:inset-x-0',
28
63
  },
29
64
  vertical: {
30
65
  root: [
31
66
  'flex-col',
32
- '[&>*:not(:first-child)]:rounded-t-none [&>*:not(:last-child)]:rounded-b-none',
33
- '[&>*:not(:first-child)]:border-t-0',
67
+ '[&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*+*]:before:inset-x-2 [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*+*]:before:-top-0.5 [&:not(:has(>[data-slot=button-group-separator],>[data-slot=button-group]))>*+*]:before:h-px',
68
+ '[&:has(>[data-slot=button-group-separator])>*:not(:first-child)]:rounded-t-none [&:has(>[data-slot=button-group-separator])>*:not(:last-child)]:rounded-b-none',
34
69
  ],
35
- separator: 'h-px',
70
+ separator: 'h-px before:inset-x-2 before:inset-y-0',
36
71
  },
37
72
  },
38
73
  /** Matches the heights of `Button` and `Input` at the same size. */
39
74
  size: {
40
- sm: { text: 'h-8 rounded-md px-2.5 text-[0.8125rem]' },
41
- md: { text: 'h-9 rounded-lg px-3 text-sm' },
42
- lg: { text: 'h-11 rounded-xl px-3.5 text-[0.9375rem]' },
75
+ sm: { text: 'h-8 px-2.5 text-[0.8125rem]' },
76
+ md: { text: 'h-9 px-3 text-sm' },
77
+ lg: { text: 'h-11 px-3.5 text-[0.9375rem]' },
43
78
  },
44
79
  },
45
80
  defaultVariants: { orientation: 'horizontal', size: 'md' },
@@ -53,8 +88,11 @@ function ButtonGroupRoot({ orientation = 'horizontal', className, ...props }) {
53
88
  }
54
89
  ButtonGroupRoot.displayName = 'ButtonGroup';
55
90
  /**
56
- * A line between two children that have no border to share, such as filled
57
- * buttons. It is decorative and hidden from assistive technology.
91
+ * The divider of a split button. A group with one in it drops its track and
92
+ * joins its children edge to edge. Before a primary, secondary or destructive
93
+ * button the separator takes that button's fill, so the two halves read as
94
+ * one, and draws a short line in the button's text colour. It is decorative
95
+ * and hidden from assistive technology.
58
96
  */
59
97
  function ButtonGroupSeparator({ orientation = 'vertical', className, ...props }) {
60
98
  const slots = buttonGroupVariants({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panelui-web",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "PanelUI for the web: animated, data-rich React components styled with Tailwind CSS on shadcn tokens.",
5
5
  "type": "module",
6
6
  "sideEffects": false,