@kushagradhawan/kookie-ui 0.1.41 → 0.1.42

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 (142) hide show
  1. package/README.md +257 -60
  2. package/components.css +386 -79
  3. package/dist/cjs/components/schemas/base-button.schema.d.ts +319 -0
  4. package/dist/cjs/components/schemas/base-button.schema.d.ts.map +1 -0
  5. package/dist/cjs/components/schemas/base-button.schema.js +2 -0
  6. package/dist/cjs/components/schemas/base-button.schema.js.map +7 -0
  7. package/dist/cjs/components/schemas/button.schema.d.ts +686 -0
  8. package/dist/cjs/components/schemas/button.schema.d.ts.map +1 -0
  9. package/dist/cjs/components/schemas/button.schema.js +2 -0
  10. package/dist/cjs/components/schemas/button.schema.js.map +7 -0
  11. package/dist/cjs/components/schemas/icon-button.schema.d.ts +329 -0
  12. package/dist/cjs/components/schemas/icon-button.schema.d.ts.map +1 -0
  13. package/dist/cjs/components/schemas/icon-button.schema.js +2 -0
  14. package/dist/cjs/components/schemas/icon-button.schema.js.map +7 -0
  15. package/dist/cjs/components/schemas/index.d.ts +52 -0
  16. package/dist/cjs/components/schemas/index.d.ts.map +1 -0
  17. package/dist/cjs/components/schemas/index.js +2 -0
  18. package/dist/cjs/components/schemas/index.js.map +7 -0
  19. package/dist/cjs/components/schemas/toggle-button.schema.d.ts +1172 -0
  20. package/dist/cjs/components/schemas/toggle-button.schema.d.ts.map +1 -0
  21. package/dist/cjs/components/schemas/toggle-button.schema.js +2 -0
  22. package/dist/cjs/components/schemas/toggle-button.schema.js.map +7 -0
  23. package/dist/cjs/components/schemas/toggle-icon-button.schema.d.ts +563 -0
  24. package/dist/cjs/components/schemas/toggle-icon-button.schema.d.ts.map +1 -0
  25. package/dist/cjs/components/schemas/toggle-icon-button.schema.js +2 -0
  26. package/dist/cjs/components/schemas/toggle-icon-button.schema.js.map +7 -0
  27. package/dist/cjs/components/sheet.d.ts +1 -1
  28. package/dist/cjs/components/sheet.d.ts.map +1 -1
  29. package/dist/cjs/components/sheet.js +1 -1
  30. package/dist/cjs/components/sheet.js.map +3 -3
  31. package/dist/cjs/components/shell.d.ts +125 -164
  32. package/dist/cjs/components/shell.d.ts.map +1 -1
  33. package/dist/cjs/components/shell.js +1 -1
  34. package/dist/cjs/components/shell.js.map +3 -3
  35. package/dist/cjs/components/sidebar.d.ts +1 -7
  36. package/dist/cjs/components/sidebar.d.ts.map +1 -1
  37. package/dist/cjs/components/sidebar.js +1 -1
  38. package/dist/cjs/components/sidebar.js.map +3 -3
  39. package/dist/cjs/components/theme.d.ts +3 -0
  40. package/dist/cjs/components/theme.d.ts.map +1 -1
  41. package/dist/cjs/components/theme.js +1 -1
  42. package/dist/cjs/components/theme.js.map +3 -3
  43. package/dist/cjs/components/theme.props.d.ts +10 -0
  44. package/dist/cjs/components/theme.props.d.ts.map +1 -1
  45. package/dist/cjs/components/theme.props.js +1 -1
  46. package/dist/cjs/components/theme.props.js.map +3 -3
  47. package/dist/cjs/helpers/font-config.d.ts +96 -0
  48. package/dist/cjs/helpers/font-config.d.ts.map +1 -0
  49. package/dist/cjs/helpers/font-config.js +3 -0
  50. package/dist/cjs/helpers/font-config.js.map +7 -0
  51. package/dist/cjs/helpers/index.d.ts +1 -0
  52. package/dist/cjs/helpers/index.d.ts.map +1 -1
  53. package/dist/cjs/helpers/index.js +1 -1
  54. package/dist/cjs/helpers/index.js.map +2 -2
  55. package/dist/esm/components/schemas/base-button.schema.d.ts +319 -0
  56. package/dist/esm/components/schemas/base-button.schema.d.ts.map +1 -0
  57. package/dist/esm/components/schemas/base-button.schema.js +2 -0
  58. package/dist/esm/components/schemas/base-button.schema.js.map +7 -0
  59. package/dist/esm/components/schemas/button.schema.d.ts +686 -0
  60. package/dist/esm/components/schemas/button.schema.d.ts.map +1 -0
  61. package/dist/esm/components/schemas/button.schema.js +2 -0
  62. package/dist/esm/components/schemas/button.schema.js.map +7 -0
  63. package/dist/esm/components/schemas/icon-button.schema.d.ts +329 -0
  64. package/dist/esm/components/schemas/icon-button.schema.d.ts.map +1 -0
  65. package/dist/esm/components/schemas/icon-button.schema.js +2 -0
  66. package/dist/esm/components/schemas/icon-button.schema.js.map +7 -0
  67. package/dist/esm/components/schemas/index.d.ts +52 -0
  68. package/dist/esm/components/schemas/index.d.ts.map +1 -0
  69. package/dist/esm/components/schemas/index.js +2 -0
  70. package/dist/esm/components/schemas/index.js.map +7 -0
  71. package/dist/esm/components/schemas/toggle-button.schema.d.ts +1172 -0
  72. package/dist/esm/components/schemas/toggle-button.schema.d.ts.map +1 -0
  73. package/dist/esm/components/schemas/toggle-button.schema.js +2 -0
  74. package/dist/esm/components/schemas/toggle-button.schema.js.map +7 -0
  75. package/dist/esm/components/schemas/toggle-icon-button.schema.d.ts +563 -0
  76. package/dist/esm/components/schemas/toggle-icon-button.schema.d.ts.map +1 -0
  77. package/dist/esm/components/schemas/toggle-icon-button.schema.js +2 -0
  78. package/dist/esm/components/schemas/toggle-icon-button.schema.js.map +7 -0
  79. package/dist/esm/components/sheet.d.ts +1 -1
  80. package/dist/esm/components/sheet.d.ts.map +1 -1
  81. package/dist/esm/components/sheet.js +1 -1
  82. package/dist/esm/components/sheet.js.map +3 -3
  83. package/dist/esm/components/shell.d.ts +125 -164
  84. package/dist/esm/components/shell.d.ts.map +1 -1
  85. package/dist/esm/components/shell.js +1 -1
  86. package/dist/esm/components/shell.js.map +3 -3
  87. package/dist/esm/components/sidebar.d.ts +1 -7
  88. package/dist/esm/components/sidebar.d.ts.map +1 -1
  89. package/dist/esm/components/sidebar.js +1 -1
  90. package/dist/esm/components/sidebar.js.map +3 -3
  91. package/dist/esm/components/theme.d.ts +3 -0
  92. package/dist/esm/components/theme.d.ts.map +1 -1
  93. package/dist/esm/components/theme.js +1 -1
  94. package/dist/esm/components/theme.js.map +3 -3
  95. package/dist/esm/components/theme.props.d.ts +10 -0
  96. package/dist/esm/components/theme.props.d.ts.map +1 -1
  97. package/dist/esm/components/theme.props.js +1 -1
  98. package/dist/esm/components/theme.props.js.map +3 -3
  99. package/dist/esm/helpers/font-config.d.ts +96 -0
  100. package/dist/esm/helpers/font-config.d.ts.map +1 -0
  101. package/dist/esm/helpers/font-config.js +3 -0
  102. package/dist/esm/helpers/font-config.js.map +7 -0
  103. package/dist/esm/helpers/index.d.ts +1 -0
  104. package/dist/esm/helpers/index.d.ts.map +1 -1
  105. package/dist/esm/helpers/index.js +1 -1
  106. package/dist/esm/helpers/index.js.map +2 -2
  107. package/package.json +23 -3
  108. package/schemas/base-button.d.ts +2 -0
  109. package/schemas/base-button.json +284 -0
  110. package/schemas/button.d.ts +2 -0
  111. package/schemas/button.json +535 -0
  112. package/schemas/icon-button.d.ts +2 -0
  113. package/schemas/icon-button.json +318 -0
  114. package/schemas/index.d.ts +2 -0
  115. package/schemas/index.json +2016 -0
  116. package/schemas/schemas.d.ts +29 -0
  117. package/schemas/toggle-button.d.ts +2 -0
  118. package/schemas/toggle-button.json +543 -0
  119. package/schemas/toggle-icon-button.d.ts +2 -0
  120. package/schemas/toggle-icon-button.json +326 -0
  121. package/schemas-json.d.ts +12 -0
  122. package/src/components/_internal/base-sidebar.css +1 -2
  123. package/src/components/schemas/base-button.schema.ts +339 -0
  124. package/src/components/schemas/button.schema.ts +198 -0
  125. package/src/components/schemas/icon-button.schema.ts +142 -0
  126. package/src/components/schemas/index.ts +68 -0
  127. package/src/components/schemas/toggle-button.schema.ts +122 -0
  128. package/src/components/schemas/toggle-icon-button.schema.ts +195 -0
  129. package/src/components/sheet.css +39 -19
  130. package/src/components/sheet.tsx +62 -3
  131. package/src/components/shell.css +510 -89
  132. package/src/components/shell.tsx +2055 -928
  133. package/src/components/sidebar.tsx +3 -22
  134. package/src/components/theme.props.tsx +8 -0
  135. package/src/components/theme.tsx +16 -0
  136. package/src/helpers/font-config.ts +167 -0
  137. package/src/helpers/index.ts +1 -0
  138. package/src/styles/fonts.css +16 -13
  139. package/src/styles/tokens/typography.css +27 -4
  140. package/styles.css +398 -79
  141. package/tokens/base.css +12 -0
  142. package/tokens.css +12 -0
@@ -0,0 +1,195 @@
1
+ import { z } from 'zod';
2
+ import { BaseButtonSchema } from './base-button.schema.js';
3
+
4
+ /**
5
+ * ToggleIconButton Zod schema - Single source of truth for ToggleIconButton component props
6
+ *
7
+ * ToggleIconButton extends IconButton with toggle functionality using Radix UI's Toggle primitive.
8
+ * It provides proper accessibility announcements, controlled/uncontrolled state management,
9
+ * and seamless integration with the existing IconButton component.
10
+ *
11
+ * Key features:
12
+ * - Required accessibility attributes (aria-label, aria-labelledby, or children)
13
+ * - Controlled and uncontrolled state management
14
+ * - Live accessibility announcements for screen readers
15
+ * - Tooltip support for better UX
16
+ * - Proper ARIA attributes for toggle functionality
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * // Uncontrolled toggle icon button
21
+ * const props = ToggleIconButtonSchema.parse({
22
+ * 'aria-label': 'Toggle sidebar',
23
+ * defaultPressed: false,
24
+ * variant: 'ghost'
25
+ * });
26
+ *
27
+ * // Controlled toggle icon button
28
+ * const controlledProps = ToggleIconButtonSchema.parse({
29
+ * 'aria-label': 'Toggle dark mode',
30
+ * pressed: true,
31
+ * onPressedChange: (pressed) => setDarkMode(pressed),
32
+ * variant: 'soft'
33
+ * });
34
+ *
35
+ * // Toggle icon button with tooltip
36
+ * const tooltipProps = ToggleIconButtonSchema.parse({
37
+ * 'aria-label': 'Toggle notifications',
38
+ * defaultPressed: false,
39
+ * tooltip: 'Toggle notification settings',
40
+ * tooltipSide: 'right'
41
+ * });
42
+ * ```
43
+ */
44
+ export const ToggleIconButtonSchema = BaseButtonSchema.extend({
45
+ /**
46
+ * Content to display in the tooltip on hover/focus
47
+ */
48
+ tooltip: z.string().optional().describe('Content to display in the tooltip on hover/focus'),
49
+
50
+ /**
51
+ * Side of the button where the tooltip should appear
52
+ * @default 'top'
53
+ */
54
+ tooltipSide: z
55
+ .enum(['top', 'right', 'bottom', 'left'])
56
+ .optional()
57
+ .default('top')
58
+ .describe('Side of the button where the tooltip should appear'),
59
+
60
+ /**
61
+ * Alignment of the tooltip relative to the button
62
+ * @default 'center'
63
+ */
64
+ tooltipAlign: z
65
+ .enum(['start', 'center', 'end'])
66
+ .optional()
67
+ .default('center')
68
+ .describe('Alignment of the tooltip relative to the button'),
69
+
70
+ /**
71
+ * Delay before showing the tooltip (in milliseconds)
72
+ */
73
+ tooltipDelayDuration: z
74
+ .number()
75
+ .optional()
76
+ .describe('Delay before showing the tooltip (in milliseconds)'),
77
+
78
+ /**
79
+ * Whether to disable hoverable content behavior
80
+ * @default false
81
+ */
82
+ tooltipDisableHoverableContent: z
83
+ .boolean()
84
+ .optional()
85
+ .default(false)
86
+ .describe('Whether to disable hoverable content behavior'),
87
+
88
+ /**
89
+ * ARIA label for accessibility (required if no aria-labelledby or children)
90
+ * Icon buttons must have an accessible name to meet WCAG guidelines
91
+ */
92
+ 'aria-label': z.string().optional().describe('ARIA label for accessibility'),
93
+
94
+ /**
95
+ * ARIA labelled by reference (required if no aria-label or children)
96
+ * Icon buttons must have an accessible name to meet WCAG guidelines
97
+ */
98
+ 'aria-labelledby': z.string().optional().describe('ARIA labelled by reference'),
99
+
100
+ /**
101
+ * Children elements (required if no aria-label or aria-labelledby)
102
+ * Icon buttons must have an accessible name to meet WCAG guidelines
103
+ */
104
+ children: z
105
+ .any()
106
+ .optional()
107
+ .describe('Children elements (required for accessibility if no aria-label)'),
108
+
109
+ /**
110
+ * Controlled pressed state
111
+ * When provided, the component is controlled and the pressed state is managed externally
112
+ */
113
+ pressed: z.boolean().optional().describe('Controlled pressed state'),
114
+
115
+ /**
116
+ * Callback when pressed state changes
117
+ * Called with the new pressed state when the toggle is activated
118
+ */
119
+ onPressedChange: z.function().optional().describe('Callback when pressed state changes'),
120
+
121
+ /**
122
+ * Default pressed state for uncontrolled usage
123
+ * Used as the initial pressed state when the component is uncontrolled
124
+ */
125
+ defaultPressed: z.boolean().optional().describe('Default pressed state for uncontrolled usage'),
126
+ })
127
+ .refine((data: any) => data['aria-label'] || data['aria-labelledby'] || data.children, {
128
+ message:
129
+ "ToggleIconButton must have either 'aria-label', 'aria-labelledby', or 'children' for accessibility",
130
+ path: ['aria-label', 'aria-labelledby', 'children'],
131
+ })
132
+ .refine(
133
+ (data: any) => {
134
+ // If pressed is provided, onPressedChange should also be provided for controlled usage
135
+ if (data.pressed !== undefined && data.onPressedChange === undefined) {
136
+ return false;
137
+ }
138
+ return true;
139
+ },
140
+ {
141
+ message: 'When using controlled mode (pressed prop), onPressedChange must also be provided',
142
+ path: ['onPressedChange'],
143
+ },
144
+ )
145
+ .refine(
146
+ (data: any) => {
147
+ // Cannot have both controlled and uncontrolled props
148
+ if (data.pressed !== undefined && data.defaultPressed !== undefined) {
149
+ return false;
150
+ }
151
+ return true;
152
+ },
153
+ {
154
+ message:
155
+ 'Cannot use both controlled (pressed) and uncontrolled (defaultPressed) props together',
156
+ path: ['pressed', 'defaultPressed'],
157
+ },
158
+ );
159
+
160
+ /**
161
+ * Type derived from ToggleIconButton Zod schema
162
+ * This ensures type safety and consistency with the schema
163
+ */
164
+ export type ToggleIconButtonProps = z.infer<typeof ToggleIconButtonSchema>;
165
+
166
+ /**
167
+ * Development-only helper to validate and normalize ToggleIconButton props
168
+ * This function should only be used in development mode
169
+ *
170
+ * @param props - Props to validate and normalize
171
+ * @returns Validated and normalized props
172
+ *
173
+ * @example
174
+ * ```tsx
175
+ * // In development, this will validate props and show helpful errors
176
+ * const invalidProps = parseToggleIconButtonProps({
177
+ * pressed: true,
178
+ * defaultPressed: false
179
+ * // Missing onPressedChange, conflicting controlled/uncontrolled, and missing accessibility
180
+ * });
181
+ * // Throws multiple validation errors
182
+ *
183
+ * const validProps = parseToggleIconButtonProps({
184
+ * 'aria-label': 'Toggle menu',
185
+ * pressed: true,
186
+ * onPressedChange: (pressed) => setMenuOpen(pressed)
187
+ * });
188
+ * ```
189
+ */
190
+ export function parseToggleIconButtonProps(props: unknown): ToggleIconButtonProps {
191
+ if (process.env.NODE_ENV === 'development') {
192
+ return ToggleIconButtonSchema.parse(props);
193
+ }
194
+ return props as ToggleIconButtonProps;
195
+ }
@@ -1,10 +1,10 @@
1
1
  :where(.rt-SheetContent) {
2
2
  position: fixed !important;
3
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;
4
+ width: var(--width, 90vw);
5
+ max-width: var(--max-width, 100vw);
6
+ height: 100vh;
7
+ max-height: 100vh;
8
8
  margin: 0 !important;
9
9
  border-radius: 0 !important;
10
10
  will-change: transform;
@@ -34,9 +34,9 @@
34
34
  left: 0 !important;
35
35
  right: 0 !important;
36
36
  bottom: auto !important;
37
- width: auto !important;
38
- max-width: none !important;
39
- height: var(--height, 75vh) !important;
37
+ width: auto;
38
+ max-width: none;
39
+ height: var(--height, 75vh);
40
40
  margin: 0 !important;
41
41
  border-start-start-radius: 0;
42
42
  border-start-end-radius: 0;
@@ -46,9 +46,9 @@
46
46
  left: 0 !important;
47
47
  right: 0 !important;
48
48
  top: auto !important;
49
- width: auto !important;
50
- max-width: none !important;
51
- height: var(--height, 75vh) !important;
49
+ width: auto;
50
+ max-width: none;
51
+ height: var(--height, 75vh);
52
52
  margin: 0 !important;
53
53
  border-end-start-radius: 0;
54
54
  border-end-end-radius: 0;
@@ -56,7 +56,6 @@
56
56
 
57
57
  /* Overlay adjustments: avoid double-fade jank from base dialog */
58
58
  .rt-SheetOverlay::before {
59
- opacity: 1 !important;
60
59
  backdrop-filter: var(--backdrop-filter-components) !important;
61
60
  }
62
61
 
@@ -83,31 +82,49 @@
83
82
  }
84
83
 
85
84
  .rt-SheetContent:where([data-state='open'][data-side='start']) {
86
- animation-name: rt-sheet-open-from-start, rt-fade-in !important;
85
+ /* OPEN: slide first (no fade yet) */
86
+ animation-name: rt-sheet-open-from-start !important;
87
87
  }
88
88
  .rt-SheetContent:where([data-state='closed'][data-side='start']) {
89
- animation-name: rt-sheet-close-to-start, rt-fade-out !important;
89
+ /* CLOSED: delay slide until content fade-out finishes */
90
+ animation-name: rt-sheet-close-to-start !important;
91
+ animation-delay: var(--motion-duration-small);
90
92
  }
91
93
 
92
94
  .rt-SheetContent:where([data-state='open'][data-side='end']) {
93
- animation-name: rt-sheet-open-from-end, rt-fade-in !important;
95
+ animation-name: rt-sheet-open-from-end !important;
94
96
  }
95
97
  .rt-SheetContent:where([data-state='closed'][data-side='end']) {
96
- animation-name: rt-sheet-close-to-end, rt-fade-out !important;
98
+ animation-name: rt-sheet-close-to-end !important;
99
+ animation-delay: var(--motion-duration-small);
97
100
  }
98
101
 
99
102
  .rt-SheetContent:where([data-state='open'][data-side='top']) {
100
- animation-name: rt-sheet-open-from-top, rt-fade-in !important;
103
+ animation-name: rt-sheet-open-from-top !important;
101
104
  }
102
105
  .rt-SheetContent:where([data-state='closed'][data-side='top']) {
103
- animation-name: rt-sheet-close-to-top, rt-fade-out !important;
106
+ animation-name: rt-sheet-close-to-top !important;
107
+ animation-delay: var(--motion-duration-small);
104
108
  }
105
109
 
106
110
  .rt-SheetContent:where([data-state='open'][data-side='bottom']) {
107
- animation-name: rt-sheet-open-from-bottom, rt-fade-in !important;
111
+ animation-name: rt-sheet-open-from-bottom !important;
108
112
  }
109
113
  .rt-SheetContent:where([data-state='closed'][data-side='bottom']) {
110
- animation-name: rt-sheet-close-to-bottom, rt-fade-out !important;
114
+ animation-name: rt-sheet-close-to-bottom !important;
115
+ animation-delay: var(--motion-duration-small);
116
+ }
117
+
118
+ /* OPEN: fade in inner content after slide completes */
119
+ .rt-SheetContent:where([data-state='open']) > * {
120
+ opacity: 0;
121
+ animation: rt-fade-in var(--motion-duration-small) var(--motion-spring-snappy) both;
122
+ animation-delay: var(--motion-duration-medium);
123
+ }
124
+
125
+ /* CLOSED: fade out inner content immediately, then container slides */
126
+ .rt-SheetContent:where([data-state='closed']) > * {
127
+ animation: rt-fade-out var(--motion-duration-small) var(--motion-spring-snappy) both;
111
128
  }
112
129
  }
113
130
 
@@ -115,4 +132,7 @@
115
132
  .rt-SheetContent {
116
133
  animation: none !important;
117
134
  }
135
+ .rt-SheetContent > * {
136
+ animation: none !important;
137
+ }
118
138
  }
@@ -42,6 +42,7 @@ import type { DialogContentOwnProps } from './dialog.props.js';
42
42
  import { Theme } from './theme.js';
43
43
  import { extractProps } from '../helpers/extract-props.js';
44
44
  import { requireReactElement } from '../helpers/require-react-element.js';
45
+ import { useBodyPointerEventsCleanup } from '../hooks/use-body-pointer-events-cleanup.js';
45
46
 
46
47
  import type { ComponentPropsWithout, RemovedProps } from '../helpers/component-props.js';
47
48
 
@@ -132,10 +133,10 @@ const Content = React.forwardRef<SheetContentElement, SheetContentProps>(
132
133
  );
133
134
 
134
135
  const materialValue = React.useMemo(() => {
135
- if (resolvedMaterial !== undefined) {
136
+ if (resolvedPanelBackground !== undefined) {
136
137
  if (process.env.NODE_ENV !== 'production') {
137
138
  console.warn(
138
- 'Warning: The `panelBackground` prop is deprecated and will be removed in a future version. Use `material` prop instead.',
139
+ 'Warning: The `panelBackground` prop is deprecated and will be removed in a future version. Use the `material` prop instead.',
139
140
  );
140
141
  }
141
142
  }
@@ -175,13 +176,65 @@ const Content = React.forwardRef<SheetContentElement, SheetContentProps>(
175
176
  }
176
177
  }
177
178
 
179
+ // Focus management and stuck pointer-events cleanup like Dialog
180
+ const contentRef = React.useRef<HTMLDivElement>(null);
181
+ const combinedRef = React.useMemo(
182
+ () => (node: HTMLDivElement | null) => {
183
+ contentRef.current = node;
184
+ if (typeof forwardedRef === 'function') {
185
+ forwardedRef(node);
186
+ } else if (forwardedRef) {
187
+ (forwardedRef as React.MutableRefObject<HTMLDivElement | null>).current = node;
188
+ }
189
+ },
190
+ [forwardedRef],
191
+ );
192
+
193
+ useBodyPointerEventsCleanup();
194
+
195
+ React.useEffect(() => {
196
+ if (typeof window === 'undefined') return;
197
+ const content = contentRef.current;
198
+ if (!content) return;
199
+
200
+ const focusableElements = content.querySelectorAll(
201
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
202
+ );
203
+
204
+ if (focusableElements.length === 0) return;
205
+
206
+ const firstElement = focusableElements[0] as HTMLElement;
207
+ const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;
208
+
209
+ const handleKeyDown = (event: KeyboardEvent) => {
210
+ if (event.key === 'Tab') {
211
+ if (event.shiftKey) {
212
+ if (document.activeElement === firstElement) {
213
+ event.preventDefault();
214
+ lastElement.focus();
215
+ }
216
+ } else if (document.activeElement === lastElement) {
217
+ event.preventDefault();
218
+ firstElement.focus();
219
+ }
220
+ }
221
+ };
222
+
223
+ content.addEventListener('keydown', handleKeyDown);
224
+ firstElement.focus();
225
+
226
+ return () => {
227
+ content.removeEventListener('keydown', handleKeyDown);
228
+ };
229
+ }, []);
230
+
178
231
  return (
179
232
  <DialogPrimitive.Portal container={container} forceMount={forceMount}>
180
233
  <Theme asChild>
181
234
  <DialogPrimitive.Overlay className="rt-BaseDialogOverlay rt-DialogOverlay rt-SheetOverlay">
182
235
  <DialogPrimitive.Content
183
236
  {...contentProps}
184
- ref={forwardedRef}
237
+ ref={combinedRef}
185
238
  className={classNames(
186
239
  'rt-BaseDialogContent',
187
240
  'rt-SheetContent',
@@ -191,6 +244,9 @@ const Content = React.forwardRef<SheetContentElement, SheetContentProps>(
191
244
  data-side={normalizedSide}
192
245
  data-material={materialValue}
193
246
  data-panel-background={materialValue}
247
+ tabIndex={-1}
248
+ role="dialog"
249
+ aria-modal="true"
194
250
  />
195
251
  </DialogPrimitive.Overlay>
196
252
  </Theme>
@@ -244,4 +300,7 @@ export type {
244
300
  SheetRootProps as RootProps,
245
301
  SheetTriggerProps as TriggerProps,
246
302
  SheetContentProps as ContentProps,
303
+ SheetTitleProps as TitleProps,
304
+ SheetDescriptionProps as DescriptionProps,
305
+ SheetCloseProps as CloseProps,
247
306
  };