@kushagradhawan/kookie-ui 0.1.32 → 0.1.34

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.
Files changed (120) hide show
  1. package/components.css +937 -458
  2. package/dist/cjs/components/_internal/base-button.d.ts.map +1 -1
  3. package/dist/cjs/components/_internal/base-button.js +1 -1
  4. package/dist/cjs/components/_internal/base-button.js.map +3 -3
  5. package/dist/cjs/components/chatbar.d.ts +202 -0
  6. package/dist/cjs/components/chatbar.d.ts.map +1 -0
  7. package/dist/cjs/components/chatbar.js +2 -0
  8. package/dist/cjs/components/chatbar.js.map +7 -0
  9. package/dist/cjs/components/icon-button.d.ts.map +1 -1
  10. package/dist/cjs/components/icon-button.js +2 -2
  11. package/dist/cjs/components/icon-button.js.map +3 -3
  12. package/dist/cjs/components/icons.d.ts +6 -1
  13. package/dist/cjs/components/icons.d.ts.map +1 -1
  14. package/dist/cjs/components/icons.js +1 -1
  15. package/dist/cjs/components/icons.js.map +3 -3
  16. package/dist/cjs/components/index.d.ts +3 -0
  17. package/dist/cjs/components/index.d.ts.map +1 -1
  18. package/dist/cjs/components/index.js +1 -1
  19. package/dist/cjs/components/index.js.map +3 -3
  20. package/dist/cjs/components/popover.d.ts +13 -1
  21. package/dist/cjs/components/popover.d.ts.map +1 -1
  22. package/dist/cjs/components/popover.js +1 -1
  23. package/dist/cjs/components/popover.js.map +3 -3
  24. package/dist/cjs/components/sheet.d.ts +82 -0
  25. package/dist/cjs/components/sheet.d.ts.map +1 -0
  26. package/dist/cjs/components/sheet.js +2 -0
  27. package/dist/cjs/components/sheet.js.map +7 -0
  28. package/dist/cjs/components/shell.d.ts +180 -0
  29. package/dist/cjs/components/shell.d.ts.map +1 -0
  30. package/dist/cjs/components/shell.js +2 -0
  31. package/dist/cjs/components/shell.js.map +7 -0
  32. package/dist/cjs/components/sidebar.d.ts +4 -33
  33. package/dist/cjs/components/sidebar.d.ts.map +1 -1
  34. package/dist/cjs/components/sidebar.js +1 -1
  35. package/dist/cjs/components/sidebar.js.map +3 -3
  36. package/dist/cjs/components/skeleton.d.ts.map +1 -1
  37. package/dist/cjs/components/skeleton.js +1 -1
  38. package/dist/cjs/components/skeleton.js.map +2 -2
  39. package/dist/cjs/helpers/inert.d.ts +1 -1
  40. package/dist/cjs/helpers/inert.d.ts.map +1 -1
  41. package/dist/cjs/helpers/inert.js +1 -1
  42. package/dist/cjs/helpers/inert.js.map +2 -2
  43. package/dist/esm/components/_internal/base-button.d.ts.map +1 -1
  44. package/dist/esm/components/_internal/base-button.js +1 -1
  45. package/dist/esm/components/_internal/base-button.js.map +3 -3
  46. package/dist/esm/components/chatbar.d.ts +202 -0
  47. package/dist/esm/components/chatbar.d.ts.map +1 -0
  48. package/dist/esm/components/chatbar.js +2 -0
  49. package/dist/esm/components/chatbar.js.map +7 -0
  50. package/dist/esm/components/icon-button.d.ts.map +1 -1
  51. package/dist/esm/components/icon-button.js +2 -2
  52. package/dist/esm/components/icon-button.js.map +3 -3
  53. package/dist/esm/components/icons.d.ts +6 -1
  54. package/dist/esm/components/icons.d.ts.map +1 -1
  55. package/dist/esm/components/icons.js +1 -1
  56. package/dist/esm/components/icons.js.map +3 -3
  57. package/dist/esm/components/index.d.ts +3 -0
  58. package/dist/esm/components/index.d.ts.map +1 -1
  59. package/dist/esm/components/index.js +1 -1
  60. package/dist/esm/components/index.js.map +3 -3
  61. package/dist/esm/components/popover.d.ts +13 -1
  62. package/dist/esm/components/popover.d.ts.map +1 -1
  63. package/dist/esm/components/popover.js +1 -1
  64. package/dist/esm/components/popover.js.map +3 -3
  65. package/dist/esm/components/sheet.d.ts +82 -0
  66. package/dist/esm/components/sheet.d.ts.map +1 -0
  67. package/dist/esm/components/sheet.js +2 -0
  68. package/dist/esm/components/sheet.js.map +7 -0
  69. package/dist/esm/components/shell.d.ts +180 -0
  70. package/dist/esm/components/shell.d.ts.map +1 -0
  71. package/dist/esm/components/shell.js +2 -0
  72. package/dist/esm/components/shell.js.map +7 -0
  73. package/dist/esm/components/sidebar.d.ts +4 -33
  74. package/dist/esm/components/sidebar.d.ts.map +1 -1
  75. package/dist/esm/components/sidebar.js +1 -1
  76. package/dist/esm/components/sidebar.js.map +3 -3
  77. package/dist/esm/components/skeleton.d.ts.map +1 -1
  78. package/dist/esm/components/skeleton.js.map +2 -2
  79. package/dist/esm/helpers/inert.d.ts +1 -1
  80. package/dist/esm/helpers/inert.d.ts.map +1 -1
  81. package/dist/esm/helpers/inert.js +1 -1
  82. package/dist/esm/helpers/inert.js.map +2 -2
  83. package/package.json +2 -1
  84. package/src/components/_internal/base-button.tsx +8 -0
  85. package/src/components/_internal/base-card.css +18 -18
  86. package/src/components/_internal/base-dialog.css +11 -49
  87. package/src/components/_internal/base-menu.css +2 -2
  88. package/src/components/_internal/base-sidebar-menu.css +3 -3
  89. package/src/components/accordion.css +6 -6
  90. package/src/components/animations.css +65 -81
  91. package/src/components/callout.css +3 -3
  92. package/src/components/chatbar.css +214 -0
  93. package/src/components/chatbar.tsx +1181 -0
  94. package/src/components/icon-button.tsx +11 -0
  95. package/src/components/icons.tsx +97 -2
  96. package/src/components/image.css +3 -3
  97. package/src/components/index.css +3 -0
  98. package/src/components/index.tsx +3 -0
  99. package/src/components/popover.css +53 -8
  100. package/src/components/popover.tsx +180 -2
  101. package/src/components/scroll-area.css +3 -3
  102. package/src/components/segmented-control.css +3 -3
  103. package/src/components/sheet.css +90 -0
  104. package/src/components/sheet.tsx +247 -0
  105. package/src/components/shell.css +137 -0
  106. package/src/components/shell.tsx +1032 -0
  107. package/src/components/sidebar.css +55 -268
  108. package/src/components/sidebar.tsx +40 -262
  109. package/src/components/skeleton.tsx +1 -2
  110. package/src/components/text-area.css +6 -5
  111. package/src/components/tooltip.css +2 -2
  112. package/src/helpers/inert.ts +3 -3
  113. package/src/styles/tokens/constants.css +6 -3
  114. package/src/styles/tokens/index.css +1 -0
  115. package/src/styles/tokens/radius.css +7 -2
  116. package/src/styles/tokens/space.css +6 -0
  117. package/src/styles/tokens/transition.css +91 -46
  118. package/styles.css +998 -496
  119. package/tokens/base.css +57 -35
  120. package/tokens.css +61 -38
@@ -114,6 +114,8 @@ const IconButton = React.forwardRef(
114
114
  }: IconButtonProps,
115
115
  forwardedRef: React.ForwardedRef<IconButtonElement>,
116
116
  ) => {
117
+ // Generate unique ID for tooltip accessibility
118
+ const tooltipId = React.useId();
117
119
  // Runtime accessibility validation to ensure WCAG compliance
118
120
  // This helps catch accessibility issues during development
119
121
  const hasAriaLabel = 'aria-label' in props && props['aria-label'];
@@ -130,10 +132,18 @@ const IconButton = React.forwardRef(
130
132
  );
131
133
  }
132
134
 
135
+ // Prepare accessibility props for tooltip integration
136
+ const hasTooltip = Boolean(tooltip);
137
+ const tooltipAccessibilityProps = React.useMemo(
138
+ () => (hasTooltip ? { 'aria-describedby': tooltipId } : {}),
139
+ [hasTooltip, tooltipId],
140
+ );
141
+
133
142
  // Create the base icon button element with accessibility props
134
143
  const iconButton = (
135
144
  <BaseButton
136
145
  {...props}
146
+ {...tooltipAccessibilityProps}
137
147
  ref={forwardedRef}
138
148
  className={classNames('rt-IconButton', className)}
139
149
  />
@@ -153,6 +163,7 @@ const IconButton = React.forwardRef(
153
163
  align={tooltipAlign}
154
164
  delayDuration={tooltipDelayDuration}
155
165
  disableHoverableContent={tooltipDisableHoverableContent}
166
+ id={tooltipId}
156
167
  >
157
168
  {iconButton}
158
169
  </Tooltip>
@@ -24,7 +24,7 @@ const ThickDividerHorizontalIcon = React.forwardRef<IconElement, IconProps>(
24
24
  />
25
25
  </svg>
26
26
  );
27
- }
27
+ },
28
28
  );
29
29
 
30
30
  ThickDividerHorizontalIcon.displayName = 'ThickDividerHorizontalIcon';
@@ -105,5 +105,100 @@ const ThickDotIcon = React.forwardRef<IconElement, IconProps>((props, forwardedR
105
105
  });
106
106
  ThickDotIcon.displayName = 'ThickDotIcon';
107
107
 
108
- export { ChevronDownIcon, ThickCheckIcon, ThickChevronRightIcon, ThickDividerHorizontalIcon, ThickDotIcon };
108
+ export {
109
+ ChevronDownIcon,
110
+ ThickCheckIcon,
111
+ ThickChevronRightIcon,
112
+ ThickDividerHorizontalIcon,
113
+ ThickDotIcon,
114
+ };
109
115
  export type { IconProps };
116
+
117
+ // Additional minimal icons
118
+ const CloseIcon = React.forwardRef<IconElement, IconProps>((_props, _forwardedRef) => {
119
+ return (
120
+ <svg
121
+ {..._props}
122
+ ref={_forwardedRef}
123
+ xmlns="http://www.w3.org/2000/svg"
124
+ width="24"
125
+ height="24"
126
+ viewBox="0 0 24 24"
127
+ fill="none"
128
+ stroke="currentColor"
129
+ strokeWidth="2"
130
+ strokeLinecap="round"
131
+ strokeLinejoin="round"
132
+ >
133
+ <path d="M18 6 6 18" />
134
+ <path d="m6 6 12 12" />
135
+ </svg>
136
+ );
137
+ });
138
+ CloseIcon.displayName = 'CloseIcon';
139
+
140
+ const PaperclipIcon = React.forwardRef<IconElement, IconProps>((props, forwardedRef) => {
141
+ return (
142
+ <svg
143
+ {...props}
144
+ ref={forwardedRef}
145
+ xmlns="http://www.w3.org/2000/svg"
146
+ width="24"
147
+ height="24"
148
+ viewBox="0 0 24 24"
149
+ fill="none"
150
+ stroke="currentColor"
151
+ strokeWidth="2"
152
+ strokeLinecap="round"
153
+ strokeLinejoin="round"
154
+ >
155
+ <path d="m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551" />
156
+ </svg>
157
+ );
158
+ });
159
+ PaperclipIcon.displayName = 'PaperclipIcon';
160
+
161
+ const FileIcon = React.forwardRef<IconElement, IconProps>((props, forwardedRef) => {
162
+ return (
163
+ <svg
164
+ xmlns="http://www.w3.org/2000/svg"
165
+ width="24"
166
+ height="24"
167
+ viewBox="0 0 24 24"
168
+ fill="none"
169
+ stroke="currentColor"
170
+ strokeWidth="2"
171
+ strokeLinecap="round"
172
+ strokeLinejoin="round"
173
+ >
174
+ <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
175
+ <path d="M14 2v4a2 2 0 0 0 2 2h4" />
176
+ </svg>
177
+ );
178
+ });
179
+ FileIcon.displayName = 'FileIcon';
180
+
181
+ const FileTextIcon = React.forwardRef<IconElement, IconProps>((_props, _forwardedRef) => {
182
+ return (
183
+ <svg
184
+ xmlns="http://www.w3.org/2000/svg"
185
+ width="24"
186
+ height="24"
187
+ viewBox="0 0 24 24"
188
+ fill="none"
189
+ stroke="currentColor"
190
+ strokeWidth="2"
191
+ strokeLinecap="round"
192
+ strokeLinejoin="round"
193
+ >
194
+ <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
195
+ <path d="M14 2v4a2 2 0 0 0 2 2h4" />
196
+ <path d="M10 9H8" />
197
+ <path d="M16 13H8" />
198
+ <path d="M16 17H8" />
199
+ </svg>
200
+ );
201
+ });
202
+ FileTextIcon.displayName = 'FileTextIcon';
203
+
204
+ export { CloseIcon, PaperclipIcon, FileIcon, FileTextIcon };
@@ -25,7 +25,7 @@
25
25
  .rt-Image {
26
26
  display: block;
27
27
  object-fit: var(--object-fit); /* Controlled by fit prop */
28
- border-radius: var(--radius-4); /* Default radius, can be overridden */
28
+ border-radius: var(--radius-2); /* Default radius, can be overridden */
29
29
  }
30
30
 
31
31
  /*
@@ -78,7 +78,7 @@
78
78
  */
79
79
  .rt-Image:where(:any-link, button, label) {
80
80
  cursor: pointer;
81
- transition: var(--transition-card), filter var(--duration-2) var(--ease-1); /* Smooth transitions for interactive states */
81
+ transition: var(--transition-card), filter var(--motion-duration-small) var(--motion-ease-standard); /* Smooth transitions for interactive states */
82
82
 
83
83
  /*
84
84
  * Hover effects with progressive enhancement
@@ -116,7 +116,7 @@
116
116
  */
117
117
  :where(:any-link, button, label) {
118
118
  cursor: pointer;
119
- transition: var(--transition-card), filter var(--duration-2) var(--ease-1); /* Smooth transitions for interactive states */
119
+ transition: var(--transition-card), filter var(--motion-duration-small) var(--motion-ease-standard); /* Smooth transitions for interactive states */
120
120
 
121
121
  /* Hover effects for wrapper elements */
122
122
  @media (hover: hover) {
@@ -38,11 +38,13 @@
38
38
  @import './radio-cards.css';
39
39
  @import './radio-group.css';
40
40
  @import './radio.css';
41
+ @import './sheet.css';
41
42
  @import './scroll-area.css';
42
43
  @import './segmented-control.css';
43
44
  @import './select.css';
44
45
  @import './separator.css';
45
46
  @import './sidebar.css';
47
+ @import './shell.css';
46
48
  @import './slider.css';
47
49
  @import './spinner.css';
48
50
  @import './strong.css';
@@ -55,6 +57,7 @@
55
57
  @import './theme-panel.css';
56
58
  @import './tooltip.css';
57
59
  @import './user-card.css';
60
+ @import './chatbar.css';
58
61
 
59
62
  .radix-themes:where([data-is-root-theme='true']) {
60
63
  /* Create a new stacking context on the root `Theme` so layered components work out of the box */
@@ -68,3 +68,6 @@ export { Tooltip, type TooltipProps } from './tooltip.js';
68
68
  export { UserCard, type UserCardProps } from './user-card.js';
69
69
  export { VisuallyHidden, type VisuallyHiddenProps } from './visually-hidden.js';
70
70
  export * as Sidebar from './sidebar.js';
71
+ export * as Shell from './shell.js';
72
+ export * as Sheet from './sheet.js';
73
+ export * as Chatbar from './chatbar.js';
@@ -13,6 +13,8 @@
13
13
  --inset-padding-right: var(--popover-content-padding);
14
14
  --inset-padding-bottom: var(--popover-content-padding);
15
15
  --inset-padding-left: var(--popover-content-padding);
16
+ /* Default toolbar padding maps to content padding; overridden per size below */
17
+ --popover-toolbar-padding: var(--popover-content-padding);
16
18
  padding: var(--popover-content-padding);
17
19
  box-sizing: border-box;
18
20
 
@@ -33,6 +35,45 @@
33
35
  background-color: var(--color-panel-translucent);
34
36
  backdrop-filter: var(--backdrop-filter-panel);
35
37
  }
38
+
39
+ /* Toolbar integration */
40
+ &:where([data-has-toolbar]) {
41
+ /* When toolbar is present, top padding equals toolbar height */
42
+ padding-top: var(--popover-toolbar-offset, var(--popover-content-padding));
43
+ }
44
+ }
45
+
46
+ .rt-PopoverToolbar {
47
+ position: absolute;
48
+ top: 0;
49
+ left: 0;
50
+ right: 0;
51
+ z-index: 1;
52
+ display: grid;
53
+ grid-template-columns: 1fr auto 1fr;
54
+ align-items: center;
55
+ column-gap: var(--space-1);
56
+ padding: var(--popover-toolbar-padding);
57
+ background-color: var(--color-panel);
58
+ }
59
+
60
+ .rt-PopoverToolbarSection {
61
+ display: flex;
62
+ align-items: center;
63
+ gap: var(--space-2);
64
+ }
65
+
66
+ .rt-PopoverToolbarLeft { justify-self: start; }
67
+
68
+ .rt-PopoverToolbarCenter { grid-column: 2; justify-self: center; min-width: 0; text-align: center; }
69
+
70
+ .rt-PopoverToolbarRight { justify-self: end; }
71
+
72
+ .rt-PopoverToolbarTitle {
73
+ display: inline-block;
74
+ white-space: nowrap;
75
+ overflow: hidden;
76
+ text-overflow: ellipsis;
36
77
  }
37
78
 
38
79
  /***************************************************************************************************
@@ -44,20 +85,24 @@
44
85
  @breakpoints {
45
86
  .rt-PopoverContent {
46
87
  &:where(.rt-r-size-1) {
47
- --popover-content-padding: var(--space-3);
48
- border-radius: var(--radius-3);
88
+ --popover-content-padding: var(--space-5);
89
+ --popover-toolbar-padding: var(--space-2);
90
+ border-radius: min(var(--radius-5), calc(var(--radius-1) + var(--space-2)));
49
91
  }
50
92
  &:where(.rt-r-size-2) {
51
- --popover-content-padding: var(--space-4);
52
- border-radius: var(--radius-4);
93
+ --popover-content-padding: var(--space-6);
94
+ --popover-toolbar-padding: var(--space-2);
95
+ border-radius: min(var(--radius-6), calc(var(--radius-2) + var(--space-3)));
53
96
  }
54
97
  &:where(.rt-r-size-3) {
55
- --popover-content-padding: var(--space-5);
56
- border-radius: var(--radius-5);
98
+ --popover-content-padding: var(--space-7);
99
+ --popover-toolbar-padding: var(--space-3);
100
+ border-radius: min(var(--radius-7), calc(var(--radius-3) + var(--space-4)));
57
101
  }
58
102
  &:where(.rt-r-size-4) {
59
- --popover-content-padding: var(--space-6);
60
- border-radius: var(--radius-6);
103
+ --popover-content-padding: var(--space-8);
104
+ --popover-toolbar-padding: var(--space-4);
105
+ border-radius: min(var(--radius-8), calc(var(--radius-4) + var(--space-5)));
61
106
  }
62
107
  }
63
108
  }
@@ -11,6 +11,29 @@ import { useThemeContext } from './theme.js';
11
11
  import type { PopoverContentOwnProps } from './popover.props.js';
12
12
  import type { ComponentPropsWithout, RemovedProps } from '../helpers/component-props.js';
13
13
 
14
+ // ---------------------------------------------
15
+ // Popover Content Context
16
+ // ---------------------------------------------
17
+ type PopoverContentContextValue = {
18
+ /** Ref to the Popover.Content DOM node */
19
+ contentRef: React.MutableRefObject<HTMLDivElement | null>;
20
+ /** Update content's toolbar offset variable and presence flag */
21
+ setToolbarOffset: (offsetPx: number) => void;
22
+ /** Wire up aria-labelledby for toolbar title */
23
+ setLabelId: (id: string | undefined) => void;
24
+ };
25
+
26
+ const PopoverContentContext = React.createContext<PopoverContentContextValue | null>(null);
27
+ const usePopoverContentContext = (caller: string) => {
28
+ const ctx = React.useContext(PopoverContentContext);
29
+ if (!ctx) {
30
+ if (process.env.NODE_ENV !== 'production') {
31
+ console.warn(`${caller} must be used within Popover.Content`);
32
+ }
33
+ }
34
+ return ctx;
35
+ };
36
+
14
37
  interface PopoverRootProps extends React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Root> {}
15
38
  const PopoverRoot: React.FC<PopoverRootProps> = (props: PopoverRootProps) => (
16
39
  <PopoverPrimitive.Root {...props} />
@@ -46,6 +69,35 @@ const PopoverContent = React.forwardRef<PopoverContentElement, PopoverContentPro
46
69
  panelBackground: _,
47
70
  ...contentProps
48
71
  } = extractProps(props, popoverContentPropDefs);
72
+
73
+ // Manage refs (we need the DOM node to apply CSS variables)
74
+ const contentRef = React.useRef<HTMLDivElement>(null);
75
+ const combinedRef = React.useCallback(
76
+ (node: HTMLDivElement | null) => {
77
+ contentRef.current = node;
78
+ if (typeof forwardedRef === 'function') {
79
+ forwardedRef(node);
80
+ } else if (forwardedRef) {
81
+ (forwardedRef as React.MutableRefObject<HTMLDivElement | null>).current = node;
82
+ }
83
+ },
84
+ [forwardedRef],
85
+ );
86
+
87
+ const [labelId, setLabelId] = React.useState<string | undefined>(undefined);
88
+
89
+ const setToolbarOffset = React.useCallback((offsetPx: number) => {
90
+ const el = contentRef.current;
91
+ if (!el) return;
92
+ if (offsetPx > 0) {
93
+ el.style.setProperty('--popover-toolbar-offset', `${offsetPx}px`);
94
+ el.setAttribute('data-has-toolbar', 'true');
95
+ } else {
96
+ el.style.removeProperty('--popover-toolbar-offset');
97
+ el.removeAttribute('data-has-toolbar');
98
+ }
99
+ }, []);
100
+
49
101
  return (
50
102
  <PopoverPrimitive.Portal container={container} forceMount={forceMount}>
51
103
  <Theme asChild>
@@ -54,10 +106,15 @@ const PopoverContent = React.forwardRef<PopoverContentElement, PopoverContentPro
54
106
  sideOffset={8}
55
107
  collisionPadding={10}
56
108
  {...contentProps}
57
- ref={forwardedRef}
109
+ ref={combinedRef}
110
+ aria-labelledby={labelId}
58
111
  data-panel-background={panelBackground}
59
112
  className={classNames('rt-PopperContent', 'rt-PopoverContent', className)}
60
- />
113
+ >
114
+ <PopoverContentContext.Provider value={{ contentRef, setToolbarOffset, setLabelId }}>
115
+ {props.children}
116
+ </PopoverContentContext.Provider>
117
+ </PopoverPrimitive.Content>
61
118
  </Theme>
62
119
  </PopoverPrimitive.Portal>
63
120
  );
@@ -88,12 +145,133 @@ const PopoverAnchor = React.forwardRef<PopoverAnchorElement, PopoverAnchorProps>
88
145
 
89
146
  PopoverAnchor.displayName = 'Popover.Anchor';
90
147
 
148
+ // ---------------------------------------------
149
+ // Toolbar (Popover-only)
150
+ // ---------------------------------------------
151
+
152
+ type PopoverToolbarElement = HTMLDivElement;
153
+ type PopoverToolbarProps = React.ComponentPropsWithoutRef<'div'>;
154
+
155
+ const PopoverToolbarBase = React.forwardRef<PopoverToolbarElement, PopoverToolbarProps>(
156
+ ({ className, children, ...props }, forwardedRef) => {
157
+ const ctx = usePopoverContentContext('Popover.Toolbar');
158
+
159
+ const localRef = React.useRef<HTMLDivElement | null>(null);
160
+ const setRefs = React.useCallback(
161
+ (node: HTMLDivElement | null) => {
162
+ localRef.current = node;
163
+ if (typeof forwardedRef === 'function') forwardedRef(node);
164
+ else if (forwardedRef)
165
+ (forwardedRef as React.MutableRefObject<HTMLDivElement | null>).current = node;
166
+ },
167
+ [forwardedRef],
168
+ );
169
+
170
+ React.useEffect(() => {
171
+ if (!ctx || !localRef.current) return;
172
+ const el = localRef.current;
173
+ const update = () => {
174
+ const height = el.getBoundingClientRect().height;
175
+ ctx.setToolbarOffset(height);
176
+ };
177
+ update();
178
+ const ro = new ResizeObserver(update);
179
+ ro.observe(el);
180
+ return () => {
181
+ ro.disconnect();
182
+ ctx.setToolbarOffset(0);
183
+ };
184
+ }, [ctx]);
185
+
186
+ return (
187
+ <div {...props} ref={setRefs} className={classNames('rt-PopoverToolbar', className)}>
188
+ {children}
189
+ </div>
190
+ );
191
+ },
192
+ );
193
+ (PopoverToolbarBase as any).displayName = 'Popover.Toolbar';
194
+
195
+ type SectionProps = React.ComponentPropsWithoutRef<'div'>;
196
+
197
+ const PopoverToolbarLeft = React.forwardRef<HTMLDivElement, SectionProps>(
198
+ ({ className, ...props }, ref) => (
199
+ <div
200
+ {...props}
201
+ ref={ref}
202
+ className={classNames('rt-PopoverToolbarSection', 'rt-PopoverToolbarLeft', className)}
203
+ />
204
+ ),
205
+ );
206
+ PopoverToolbarLeft.displayName = 'Popover.Toolbar.Left';
207
+
208
+ const PopoverToolbarCenter = React.forwardRef<HTMLDivElement, SectionProps>(
209
+ ({ className, ...props }, ref) => (
210
+ <div
211
+ {...props}
212
+ ref={ref}
213
+ className={classNames('rt-PopoverToolbarSection', 'rt-PopoverToolbarCenter', className)}
214
+ />
215
+ ),
216
+ );
217
+ PopoverToolbarCenter.displayName = 'Popover.Toolbar.Center';
218
+
219
+ const PopoverToolbarRight = React.forwardRef<HTMLDivElement, SectionProps>(
220
+ ({ className, ...props }, ref) => (
221
+ <div
222
+ {...props}
223
+ ref={ref}
224
+ className={classNames('rt-PopoverToolbarSection', 'rt-PopoverToolbarRight', className)}
225
+ />
226
+ ),
227
+ );
228
+ PopoverToolbarRight.displayName = 'Popover.Toolbar.Right';
229
+
230
+ type TitleProps = React.ComponentPropsWithoutRef<'span'>;
231
+ const PopoverToolbarTitle = React.forwardRef<HTMLSpanElement, TitleProps>(
232
+ ({ className, id: idProp, ...props }, ref) => {
233
+ const ctx = usePopoverContentContext('Popover.Toolbar.Title');
234
+ const reactId = React.useId();
235
+ const id = idProp ?? `rt-popover-toolbar-title-${reactId}`;
236
+ React.useEffect(() => {
237
+ ctx?.setLabelId(id);
238
+ return () => ctx?.setLabelId(undefined);
239
+ }, [ctx, id]);
240
+ return (
241
+ <span
242
+ {...props}
243
+ id={id}
244
+ ref={ref}
245
+ className={classNames('rt-PopoverToolbarTitle', className)}
246
+ />
247
+ );
248
+ },
249
+ );
250
+ PopoverToolbarTitle.displayName = 'Popover.Toolbar.Title';
251
+
252
+ type PopoverToolbarComponent = React.ForwardRefExoticComponent<
253
+ React.PropsWithoutRef<PopoverToolbarProps> & React.RefAttributes<HTMLDivElement>
254
+ > & {
255
+ Left: typeof PopoverToolbarLeft;
256
+ Center: typeof PopoverToolbarCenter;
257
+ Right: typeof PopoverToolbarRight;
258
+ Title: typeof PopoverToolbarTitle;
259
+ };
260
+
261
+ const PopoverToolbar = Object.assign(PopoverToolbarBase, {
262
+ Left: PopoverToolbarLeft,
263
+ Center: PopoverToolbarCenter,
264
+ Right: PopoverToolbarRight,
265
+ Title: PopoverToolbarTitle,
266
+ }) as PopoverToolbarComponent;
267
+
91
268
  export {
92
269
  PopoverRoot as Root,
93
270
  PopoverContent as Content,
94
271
  PopoverTrigger as Trigger,
95
272
  PopoverClose as Close,
96
273
  PopoverAnchor as Anchor,
274
+ PopoverToolbar as Toolbar,
97
275
  };
98
276
  export type {
99
277
  PopoverRootProps as RootProps,
@@ -109,8 +109,8 @@
109
109
  background-color: var(--gray-a3);
110
110
  border-radius: var(--scrollarea-scrollbar-border-radius);
111
111
 
112
- animation-duration: 120ms;
113
- animation-timing-function: ease-out;
112
+ animation-duration: var(--motion-duration-micro);
113
+ animation-timing-function: var(--motion-ease-standard);
114
114
 
115
115
  &:where([data-state='visible']) {
116
116
  animation-name: rt-fade-in;
@@ -135,7 +135,7 @@
135
135
  .rt-ScrollAreaThumb {
136
136
  background-color: var(--gray-a8);
137
137
  border-radius: inherit;
138
- transition: background-color 100ms;
138
+ transition: background-color var(--motion-duration-micro) var(--motion-ease-standard);
139
139
 
140
140
  @media (hover: hover) {
141
141
  &:where(:hover) {
@@ -1,6 +1,6 @@
1
1
  .radix-themes {
2
2
  /* Use standard duration token instead of custom duration */
3
- --segmented-control-transition-duration: var(--duration-1); /* 100ms - Fast transitions */
3
+ --segmented-control-transition-duration: var(--motion-duration-micro); /* Micro-interactions */
4
4
  }
5
5
 
6
6
  .rt-SegmentedControlRoot {
@@ -138,7 +138,7 @@
138
138
  margin-right: calc(-1 * var(--border-width-standard) * 0.5); /* -0.5px */
139
139
  width: var(--border-width-standard); /* 1px */
140
140
  background-color: var(--gray-a4);
141
- transition: opacity calc(0.8 * var(--segmented-control-transition-duration)) var(--ease-1);
141
+ transition: opacity calc(0.8 * var(--segmented-control-transition-duration)) var(--motion-ease-standard);
142
142
 
143
143
  /* Make separators slow to disappear and fast to appear, syncing it well with the indicator motion */
144
144
 
@@ -166,7 +166,7 @@
166
166
  left: 0;
167
167
  height: 100%;
168
168
  pointer-events: none;
169
- transition: transform var(--segmented-control-transition-duration) var(--ease-3);
169
+ transition: transform var(--segmented-control-transition-duration) var(--motion-spring-snappy);
170
170
 
171
171
  &::before {
172
172
  inset: var(--border-width-standard); /* 1px */
@@ -0,0 +1,90 @@
1
+ :where(.rt-SheetContent) {
2
+ position: fixed !important;
3
+ /* Use physical properties to integrate with width/height responsive utilities */
4
+ width: var(--width, 90vw) !important;
5
+ max-width: var(--max-width, 100vw) !important;
6
+ height: 100vh !important;
7
+ max-height: 100vh !important;
8
+ margin: 0 !important;
9
+ border-radius: 0 !important;
10
+ will-change: transform;
11
+ }
12
+
13
+ /* Logical side anchoring (RTL-aware via logical inset) */
14
+ :where(.rt-SheetContent[data-side='start']) {
15
+ top: 0 !important;
16
+ bottom: 0 !important;
17
+ left: 0 !important;
18
+ right: auto !important;
19
+ margin: 0 !important;
20
+ border-start-start-radius: 0;
21
+ border-end-start-radius: 0;
22
+ }
23
+ :where(.rt-SheetContent[data-side='end']) {
24
+ top: 0 !important;
25
+ bottom: 0 !important;
26
+ right: 0 !important;
27
+ left: auto !important;
28
+ margin: 0 !important;
29
+ border-start-end-radius: 0;
30
+ border-end-end-radius: 0;
31
+ }
32
+ :where(.rt-SheetContent[data-side='top']) {
33
+ top: 0 !important;
34
+ left: 0 !important;
35
+ right: 0 !important;
36
+ bottom: auto !important;
37
+ width: auto !important;
38
+ max-width: none !important;
39
+ height: var(--height, 75vh) !important;
40
+ margin: 0 !important;
41
+ border-start-start-radius: 0;
42
+ border-start-end-radius: 0;
43
+ }
44
+ :where(.rt-SheetContent[data-side='bottom']) {
45
+ bottom: 0 !important;
46
+ left: 0 !important;
47
+ right: 0 !important;
48
+ top: auto !important;
49
+ width: auto !important;
50
+ max-width: none !important;
51
+ height: var(--height, 75vh) !important;
52
+ margin: 0 !important;
53
+ border-end-start-radius: 0;
54
+ border-end-end-radius: 0;
55
+ }
56
+
57
+ /* Overlay adjustments: avoid double-fade jank from base dialog */
58
+ :where(.rt-SheetOverlay)::before { opacity: 1; }
59
+
60
+ @media (prefers-reduced-motion: no-preference) {
61
+ /* Override base dialog animations specifically for Sheet */
62
+ .rt-SheetContent:where([data-state='open']) {
63
+ animation-duration: var(--motion-duration-medium);
64
+ animation-timing-function: var(--motion-spring-snappy);
65
+ animation-fill-mode: both;
66
+ }
67
+ .rt-SheetContent:where([data-state='closed']) {
68
+ /* Keep visible to allow reverse slide-out */
69
+ opacity: 1;
70
+ animation-duration: var(--motion-duration-small);
71
+ animation-timing-function: var(--motion-spring-snappy);
72
+ animation-fill-mode: both;
73
+ }
74
+
75
+ .rt-SheetContent:where([data-state='open'][data-side='start']) { animation-name: rt-sheet-open-from-start, rt-fade-in !important; }
76
+ .rt-SheetContent:where([data-state='closed'][data-side='start']) { animation-name: rt-sheet-close-to-start, rt-fade-out !important; }
77
+
78
+ .rt-SheetContent:where([data-state='open'][data-side='end']) { animation-name: rt-sheet-open-from-end, rt-fade-in !important; }
79
+ .rt-SheetContent:where([data-state='closed'][data-side='end']) { animation-name: rt-sheet-close-to-end, rt-fade-out !important; }
80
+
81
+ .rt-SheetContent:where([data-state='open'][data-side='top']) { animation-name: rt-sheet-open-from-top, rt-fade-in !important; }
82
+ .rt-SheetContent:where([data-state='closed'][data-side='top']) { animation-name: rt-sheet-close-to-top, rt-fade-out !important; }
83
+
84
+ .rt-SheetContent:where([data-state='open'][data-side='bottom']) { animation-name: rt-sheet-open-from-bottom, rt-fade-in !important; }
85
+ .rt-SheetContent:where([data-state='closed'][data-side='bottom']) { animation-name: rt-sheet-close-to-bottom, rt-fade-out !important; }
86
+ }
87
+
88
+ @media (prefers-reduced-motion: reduce) {
89
+ .rt-SheetContent { animation: none !important; }
90
+ }