@ctrl-ui/react 0.8.12 → 0.8.13

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.
@@ -115,14 +115,15 @@
115
115
  padding-inline: 0;
116
116
  }
117
117
 
118
- :where([data-control-family="button"][data-layout="stacked"]) {
118
+ :where([data-control-family="button"][data-layout="tile"]) {
119
+ --cui-button-radius: var(--nest-radius, min(var(--radius-control), var(--radius-field)));
119
120
  flex-direction: column;
120
121
  block-size: auto;
121
122
  padding-block: calc(var(--spacing) * 1.5);
122
123
  padding-inline: var(--cui-button-padding-inline);
123
124
  }
124
125
 
125
- :where([data-control-family="button"][data-layout="stacked"] > [data-slot="content"]) {
126
+ :where([data-control-family="button"][data-layout="tile"] > [data-slot="content"]) {
126
127
  flex-direction: column;
127
128
  }
128
129
 
@@ -37,32 +37,6 @@
37
37
  cursor: row-resize;
38
38
  }
39
39
 
40
- /* biome-ignore-start lint/complexity/noImportantStyles: Resize drag cursor must beat every cursor under the pointer. */
41
- :where(
42
- :root:has(
43
- [data-control-family="resizable"][data-slot="handle"][data-axis="vertical"]:not([data-cursor="none"]):is(
44
- [data-separator="hover"],
45
- [data-separator="active"]
46
- )
47
- )
48
- *
49
- ) {
50
- cursor: col-resize !important;
51
- }
52
-
53
- :where(
54
- :root:has(
55
- [data-control-family="resizable"][data-slot="handle"][data-axis="horizontal"]:not([data-cursor="none"]):is(
56
- [data-separator="hover"],
57
- [data-separator="active"]
58
- )
59
- )
60
- *
61
- ) {
62
- cursor: row-resize !important;
63
- }
64
- /* biome-ignore-end lint/complexity/noImportantStyles: Resize drag cursor must beat every cursor under the pointer. */
65
-
66
40
  :where([data-control-family="resizable"][data-slot="handle"][data-variant="solid"]) {
67
41
  background-color: var(--border);
68
42
  }
@@ -170,7 +170,7 @@
170
170
  :where([data-control-family="scroll-area"][data-slot="scrollbar"][data-visibility="always"]),
171
171
  :where([data-control-family="scroll-area"][data-slot="scrollbar"][data-visibility="hover"]:is([data-hovering], [data-scrolling])),
172
172
  :where([data-control-family="scroll-area"][data-slot="scrollbar"][data-visibility="scroll"][data-scrolling]),
173
- :where([data-control-family="scroll-area"][data-slot="root"]:has(> [data-scroll-area-viewport]:focus-visible) > [data-slot="scrollbar"]) {
173
+ :where([data-control-family="scroll-area"][data-slot="root"] > [data-scroll-area-viewport]:focus-visible ~ [data-slot="scrollbar"]) {
174
174
  opacity: 1;
175
175
  }
176
176
 
@@ -58,8 +58,7 @@
58
58
  }
59
59
 
60
60
  :where(
61
- [data-control-family="sidebar"]:is([data-indicator="slide"], [data-indicator="hover"])
62
- [data-control-family="sidebar"][data-slot="menu-button"][data-active]
61
+ [data-control-family="sidebar"][data-slot="menu-button"]:is([data-indicator="slide"], [data-indicator="hover"])[data-active]
63
62
  ~ [data-slot="menu-action"]
64
63
  ) {
65
64
  color: var(--cui-sidebar-menu-button-track-active-foreground);
@@ -134,21 +133,36 @@
134
133
  }
135
134
 
136
135
  :where(
137
- [data-control-family="sidebar"][data-indicator="slide"]
138
- [data-control-family="sidebar"][data-slot="menu-button"]:is(:hover, :active, [data-active], [data-popup-open])
136
+ [data-control-family="sidebar"][data-slot="menu-button"][data-indicator="slide"]:is(:hover, :active, [data-active], [data-popup-open])
139
137
  ) {
140
138
  background-color: transparent;
141
139
  }
142
140
 
141
+ :where([data-control-family="sidebar"][data-slot="menu-button"]:is([data-indicator="slide"], [data-indicator="hover"])[data-active]) {
142
+ color: var(--cui-sidebar-menu-button-track-active-foreground);
143
+ }
144
+
143
145
  :where(
144
- [data-control-family="sidebar"]:is([data-indicator="slide"], [data-indicator="hover"])
145
- [data-control-family="sidebar"][data-slot="menu-button"][data-active]
146
+ [data-control-family="sidebar"][data-slot="menu-button"][data-indicator="hover"]:focus-visible:not(
147
+ [data-active],
148
+ :disabled,
149
+ [data-disabled],
150
+ [aria-disabled="true"]
151
+ )
146
152
  ) {
147
- color: var(--cui-sidebar-menu-button-track-active-foreground);
153
+ background-color: var(--cui-sidebar-menu-button-hover-background);
154
+ color: var(--cui-sidebar-menu-button-hover-foreground);
155
+ box-shadow: var(--cui-sidebar-menu-button-hover-shadow);
148
156
  }
149
157
 
150
158
  @media (hover: hover) {
151
- :where([data-control-family="sidebar"][data-indicator="hover"] [data-slot="menu-button"]:hover:not([data-active])) {
159
+ :where(
160
+ [data-control-family="sidebar"][data-slot="menu"][data-indicator="hover"]:has(
161
+ > [data-slot="menu-item"] > [data-slot="menu-button"]:focus-visible
162
+ )
163
+ > [data-slot="menu-item"]
164
+ > [data-slot="menu-button"][data-indicator="hover"]:hover:not([data-active], :focus-visible)
165
+ ) {
152
166
  background-color: transparent;
153
167
  box-shadow: none;
154
168
  }
@@ -1,13 +1,21 @@
1
1
  @layer components {
2
+ :where([data-control-family="sidebar"][data-slot="root"]) {
3
+ --_sidebar-transition-duration: var(--duration-base);
4
+ }
5
+
6
+ :where([data-control-family="sidebar"][data-slot="root"]:has(> [data-slot="container"] > div > [data-slot="rail"]:focus-visible)) {
7
+ --_sidebar-transition-duration: 0s;
8
+ }
9
+
2
10
  :where([data-control-family="sidebar"][data-slot="container"]) {
3
11
  translate: 0;
4
12
  transition:
5
- width var(--duration-base) var(--ease-standard),
6
- translate var(--duration-base) var(--ease-standard);
13
+ width var(--_sidebar-transition-duration) var(--ease-standard),
14
+ translate var(--_sidebar-transition-duration) var(--ease-standard);
7
15
  }
8
16
 
9
17
  :where([data-control-family="sidebar"][data-slot="inner"]) {
10
- transition: padding var(--duration-base) var(--ease-standard);
18
+ transition: padding var(--_sidebar-transition-duration) var(--ease-standard);
11
19
  }
12
20
 
13
21
  :where([data-control-family="sidebar"][data-collapsible="offcanvas"][data-side="left"]:dir(ltr) [data-slot="container"]),
@@ -23,10 +31,6 @@
23
31
  :where(
24
32
  [data-control-family="sidebar"][data-slot="wrapper"][data-resizing]
25
33
  [data-control-family="sidebar"]:is([data-slot="container"], [data-slot="gap"], [data-slot="inner"])
26
- ),
27
- :where(
28
- [data-control-family="sidebar"][data-slot="wrapper"]:has([data-control-family="sidebar"][data-slot="rail"]:focus-visible)
29
- [data-control-family="sidebar"]:is([data-slot="container"], [data-slot="gap"], [data-slot="inner"])
30
34
  ) {
31
35
  transition: none;
32
36
  }
@@ -96,7 +96,7 @@
96
96
 
97
97
  :where([data-control-family="sidebar"][data-slot="gap"]) {
98
98
  background-color: transparent;
99
- transition: width var(--duration-base) var(--ease-standard);
99
+ transition: width var(--_sidebar-transition-duration) var(--ease-standard);
100
100
  }
101
101
 
102
102
  :where([data-control-family="sidebar"][data-slot="root"][data-variant="sidebar"][data-side="left"] [data-slot="container"]) {
@@ -93,6 +93,10 @@
93
93
  font-weight: var(--font-weight-medium);
94
94
  }
95
95
 
96
+ :where([data-control-family="tabs"][data-slot="list"]:not([data-orientation="vertical"], [data-variant="browser"]) [data-slot="tab"]) {
97
+ flex-grow: 1;
98
+ }
99
+
96
100
  @media (hover: hover) {
97
101
  :where([data-control-family="tabs"][data-slot="tab"]:hover:not([data-disabled])) {
98
102
  color: var(--cui-tabs-hover-foreground);
@@ -9,7 +9,7 @@ export type ButtonTone = ControlTone;
9
9
  export declare const ButtonTrackContext: import("react").Context<boolean>;
10
10
  export declare const buttonShapes: readonly ["default", "circle"];
11
11
  export type ButtonShape = (typeof buttonShapes)[number];
12
- export declare const buttonLayouts: readonly ["inline", "stacked"];
12
+ export declare const buttonLayouts: readonly ["inline", "tile"];
13
13
  export type ButtonLayout = (typeof buttonLayouts)[number];
14
14
  export type ButtonAppearanceProps = {
15
15
  variant?: ButtonVariant;
package/dist/ui/button.js CHANGED
@@ -9,7 +9,7 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
9
  //#region src/ui/button.tsx
10
10
  const ButtonTrackContext = createContext(false);
11
11
  const buttonShapes = ["default", "circle"];
12
- const buttonLayouts = ["inline", "stacked"];
12
+ const buttonLayouts = ["inline", "tile"];
13
13
  const buttonStructureClasses = "isolate inline-flex shrink-0 items-center justify-center overflow-visible whitespace-nowrap";
14
14
  const buttonTrackStructureClasses = "data-[track=hover]:relative data-[track=hover]:isolate [&_[data-track-item]]:[isolation:auto]";
15
15
  const buttonContentClasses = "relative z-[1] inline-flex min-w-0 items-center justify-center gap-[inherit]";
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
- import { createContext, useContext, useRef, useState } from "react";
3
+ import { createContext, useContext, useEffect, useRef, useState } from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { Group, Panel, Separator } from "react-resizable-panels";
6
6
  //#region src/ui/resizable.tsx
@@ -13,6 +13,32 @@ const FLOATING_ARROW_DIRECTIONS = {
13
13
  ArrowLeft: -1,
14
14
  ArrowRight: 1
15
15
  };
16
+ function useResizeCursor(handleRef, disableCursor) {
17
+ useEffect(() => {
18
+ const handle = handleRef.current;
19
+ if (!handle || disableCursor) return;
20
+ const ownerDocument = handle.ownerDocument;
21
+ const cursorStyle = ownerDocument.createElement("style");
22
+ function updateCursor() {
23
+ const state = handle?.getAttribute("data-separator");
24
+ if (state === "hover" || state === "active") {
25
+ const cursor = handle?.getAttribute("data-axis") === "vertical" ? "col-resize" : "row-resize";
26
+ cursorStyle.textContent = `*, *:hover { cursor: ${cursor} !important; }`;
27
+ if (!cursorStyle.isConnected) ownerDocument.head.append(cursorStyle);
28
+ } else cursorStyle.remove();
29
+ }
30
+ const observer = new MutationObserver(updateCursor);
31
+ observer.observe(handle, {
32
+ attributes: true,
33
+ attributeFilter: ["data-separator", "data-axis"]
34
+ });
35
+ updateCursor();
36
+ return () => {
37
+ observer.disconnect();
38
+ cursorStyle.remove();
39
+ };
40
+ }, [handleRef, disableCursor]);
41
+ }
16
42
  function ResizablePanelGroup({ className, orientation = "horizontal", variant = "framed", disableCursor = false, children, ...props }) {
17
43
  return /* @__PURE__ */ jsx(ResizableGroupContext.Provider, {
18
44
  value: {
@@ -44,6 +70,8 @@ function ResizablePanel({ className, ...props }) {
44
70
  }
45
71
  function ResizableHandle({ className, variant = "solid", withHandle, children, "aria-label": ariaLabel = "Resize panel", ...props }) {
46
72
  const { orientation, disableCursor } = useContext(ResizableGroupContext);
73
+ const handleRef = useRef(null);
74
+ useResizeCursor(handleRef, disableCursor);
47
75
  const axis = orientation === "horizontal" ? "vertical" : "horizontal";
48
76
  return /* @__PURE__ */ jsxs(Separator, {
49
77
  "data-control-ui": "resizable",
@@ -55,6 +83,7 @@ function ResizableHandle({ className, variant = "solid", withHandle, children, "
55
83
  className: cn("relative flex items-center justify-center", axis === "vertical" ? "w-px touch-pan-y" : "h-px touch-pan-x", className),
56
84
  "aria-label": ariaLabel,
57
85
  ...props,
86
+ elementRef: handleRef,
58
87
  children: [withHandle ? /* @__PURE__ */ jsx("span", {
59
88
  "data-control-ui": "resizable",
60
89
  "data-control-family": "resizable",
@@ -72,6 +101,8 @@ function ResizableFloatingPanel({ side = "right", size: controlledSize, defaultS
72
101
  const [focused, setFocused] = useState(false);
73
102
  const [resizing, setResizing] = useState(false);
74
103
  const panelRef = useRef(null);
104
+ const handleRef = useRef(null);
105
+ useResizeCursor(handleRef, false);
75
106
  const resizeRef = useRef(null);
76
107
  const size = controlledSize ?? uncontrolledSize;
77
108
  const growthPerPointerPx = side === "right" ? -1 : 1;
@@ -144,6 +175,7 @@ function ResizableFloatingPanel({ side = "right", size: controlledSize, defaultS
144
175
  }),
145
176
  children,
146
177
  /* @__PURE__ */ jsx("div", {
178
+ ref: handleRef,
147
179
  role: "separator",
148
180
  tabIndex: 0,
149
181
  "aria-label": handleLabel,
@@ -163,6 +195,7 @@ function ResizableFloatingPanel({ side = "right", size: controlledSize, defaultS
163
195
  onPointerMove: updateResize,
164
196
  onPointerUp: endResize,
165
197
  onPointerCancel: endResize,
198
+ onLostPointerCapture: endResize,
166
199
  onPointerEnter: () => setHovered(true),
167
200
  onPointerLeave: () => setHovered(false),
168
201
  onFocus: () => setFocused(true),
@@ -66,7 +66,7 @@ const SidebarMenuContext = createContext("none");
66
66
  function SidebarMenu({ className, indicator, style, children, ...props }) {
67
67
  const skin = useSkin();
68
68
  const resolvedIndicator = indicator ?? skin.indicators?.sidebar ?? "none";
69
- const hasHighlight = resolvedIndicator !== "none";
69
+ const hasHighlight = resolvedIndicator === "slide";
70
70
  const list = /* @__PURE__ */ jsx("ul", {
71
71
  "data-control-ui": "sidebar",
72
72
  "data-control-family": "sidebar",
@@ -131,7 +131,8 @@ function SidebarMenuButton({ render, isActive = false, variant = "default", size
131
131
  "data-size": size,
132
132
  "data-variant": variant,
133
133
  "data-active": isActive || void 0,
134
- "data-track-item": indicator !== "none" ? "" : void 0,
134
+ "data-indicator": indicator,
135
+ "data-track-item": indicator === "slide" ? "" : void 0,
135
136
  className: cn("peer/menu-button flex items-center overflow-hidden text-start disabled:pointer-events-none aria-disabled:pointer-events-none [&>span]:truncate [&>svg]:size-4 [&>svg]:shrink-0", className),
136
137
  children
137
138
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctrl-ui/react",
3
- "version": "0.8.12",
3
+ "version": "0.8.13",
4
4
  "description": "Control UI components, blocks, and primitives with neutral defaults and consumer-owned themes.",
5
5
  "license": "MIT",
6
6
  "repository": {