@alankrit2/ui 0.1.0

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.
package/dist/index.mjs ADDED
@@ -0,0 +1,373 @@
1
+ import * as React8 from 'react';
2
+ import * as ReactDOM from 'react-dom';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ // src/components/dialog/dialog.tsx
6
+ function useControlled({
7
+ value: valueProp,
8
+ defaultValue,
9
+ onChange
10
+ }) {
11
+ const [valueState, setValueState] = React8.useState(defaultValue);
12
+ const isControlled = valueProp !== void 0;
13
+ const value = isControlled ? valueProp : valueState;
14
+ const setValue = React8.useCallback(
15
+ (newValue) => {
16
+ if (!isControlled) {
17
+ setValueState(newValue);
18
+ }
19
+ onChange?.(newValue);
20
+ },
21
+ [isControlled, onChange]
22
+ );
23
+ return [value, setValue];
24
+ }
25
+ var FOCUSABLE_SELECTOR = [
26
+ "a[href]",
27
+ "button:not([disabled])",
28
+ "textarea:not([disabled])",
29
+ "input:not([disabled])",
30
+ "select:not([disabled])",
31
+ '[tabindex]:not([tabindex="-1"])'
32
+ ].join(",");
33
+ function useFocusTrap(containerRef, enabled = true, autoFocus = true) {
34
+ const previouslyFocusedElement = React8.useRef(null);
35
+ React8.useEffect(() => {
36
+ if (!enabled || !containerRef.current) return;
37
+ const container = containerRef.current;
38
+ previouslyFocusedElement.current = document.activeElement;
39
+ const getFocusableElements = () => {
40
+ return Array.from(
41
+ container.querySelectorAll(FOCUSABLE_SELECTOR)
42
+ );
43
+ };
44
+ if (autoFocus) {
45
+ const focusableElements = getFocusableElements();
46
+ if (focusableElements.length > 0) {
47
+ focusableElements[0].focus();
48
+ }
49
+ }
50
+ const handleKeyDown = (event) => {
51
+ if (event.key !== "Tab") return;
52
+ const focusableElements = getFocusableElements();
53
+ if (focusableElements.length === 0) return;
54
+ const firstElement = focusableElements[0];
55
+ const lastElement = focusableElements[focusableElements.length - 1];
56
+ if (event.shiftKey) {
57
+ if (document.activeElement === firstElement) {
58
+ event.preventDefault();
59
+ lastElement.focus();
60
+ }
61
+ } else {
62
+ if (document.activeElement === lastElement) {
63
+ event.preventDefault();
64
+ firstElement.focus();
65
+ }
66
+ }
67
+ };
68
+ container.addEventListener("keydown", handleKeyDown);
69
+ return () => {
70
+ container.removeEventListener("keydown", handleKeyDown);
71
+ if (previouslyFocusedElement.current) {
72
+ previouslyFocusedElement.current.focus();
73
+ }
74
+ };
75
+ }, [enabled, autoFocus, containerRef]);
76
+ }
77
+ function useEscapeKeydown(handler, enabled = true) {
78
+ React8.useEffect(() => {
79
+ if (!enabled) return;
80
+ const handleKeyDown = (event) => {
81
+ if (event.key === "Escape") {
82
+ handler(event);
83
+ }
84
+ };
85
+ document.addEventListener("keydown", handleKeyDown);
86
+ return () => document.removeEventListener("keydown", handleKeyDown);
87
+ }, [handler, enabled]);
88
+ }
89
+ function useBodyScrollLock(enabled = true) {
90
+ React8.useEffect(() => {
91
+ if (!enabled) return;
92
+ const originalOverflow = document.body.style.overflow;
93
+ const originalPaddingRight = document.body.style.paddingRight;
94
+ document.body.style.overflow = "hidden";
95
+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
96
+ if (scrollbarWidth > 0) {
97
+ document.body.style.paddingRight = `${scrollbarWidth}px`;
98
+ }
99
+ return () => {
100
+ document.body.style.overflow = originalOverflow;
101
+ document.body.style.paddingRight = originalPaddingRight;
102
+ };
103
+ }, [enabled]);
104
+ }
105
+ var count = 0;
106
+ function generateId() {
107
+ return `headless-ui-${++count}`;
108
+ }
109
+ function useStableId(idProp) {
110
+ const [id] = React8.useState(() => idProp || generateId());
111
+ return idProp || id;
112
+ }
113
+
114
+ // src/utils/compose-refs.ts
115
+ function composeRefs(...refs) {
116
+ return (node) => {
117
+ refs.forEach((ref) => {
118
+ if (!ref) return;
119
+ if (typeof ref === "function") {
120
+ ref(node);
121
+ } else {
122
+ ref.current = node;
123
+ }
124
+ });
125
+ };
126
+ }
127
+
128
+ // src/primitives/slot.tsx
129
+ var Slot = React8.forwardRef(
130
+ (props, forwardedRef) => {
131
+ const { children, ...slotProps } = props;
132
+ if (!React8.isValidElement(children)) {
133
+ return null;
134
+ }
135
+ return React8.cloneElement(children, {
136
+ ...mergeProps(slotProps, children.props),
137
+ ref: forwardedRef ? composeRefs(forwardedRef, children.ref) : children.ref
138
+ });
139
+ }
140
+ );
141
+ Slot.displayName = "Slot";
142
+ function mergeProps(slotProps, childProps) {
143
+ const merged = { ...childProps };
144
+ for (const key in childProps) {
145
+ const slotValue = slotProps[key];
146
+ const childValue = childProps[key];
147
+ if (/^on[A-Z]/.test(key)) {
148
+ if (slotValue && childValue) {
149
+ merged[key] = (...args) => {
150
+ childValue(...args);
151
+ slotValue(...args);
152
+ };
153
+ } else if (slotValue) {
154
+ merged[key] = slotValue;
155
+ }
156
+ } else if (key === "style") {
157
+ merged[key] = { ...slotValue, ...childValue };
158
+ } else if (key === "className") {
159
+ merged[key] = [slotValue, childValue].filter(Boolean).join(" ");
160
+ }
161
+ }
162
+ for (const key in slotProps) {
163
+ if (!(key in childProps)) {
164
+ merged[key] = slotProps[key];
165
+ }
166
+ }
167
+ return merged;
168
+ }
169
+ var DialogContext = React8.createContext(
170
+ null
171
+ );
172
+ function useDialogContext(componentName) {
173
+ const context = React8.useContext(DialogContext);
174
+ if (!context) {
175
+ throw new Error(
176
+ `<${componentName}> must be used within a <Dialog> component.`
177
+ );
178
+ }
179
+ return context;
180
+ }
181
+ function Dialog({
182
+ open: openProp,
183
+ defaultOpen = false,
184
+ onOpenChange,
185
+ children
186
+ }) {
187
+ const [open, setOpen] = useControlled({
188
+ value: openProp,
189
+ defaultValue: defaultOpen,
190
+ onChange: onOpenChange
191
+ });
192
+ const triggerRef = React8.useRef(null);
193
+ const contentRef = React8.useRef(null);
194
+ const titleId = useStableId();
195
+ const descriptionId = useStableId();
196
+ const contextValue = React8.useMemo(
197
+ () => ({
198
+ open,
199
+ onOpenChange: setOpen,
200
+ triggerRef,
201
+ contentRef,
202
+ titleId,
203
+ descriptionId
204
+ }),
205
+ [open, setOpen, titleId, descriptionId]
206
+ );
207
+ return /* @__PURE__ */ jsx(DialogContext.Provider, { value: contextValue, children });
208
+ }
209
+ var DialogTrigger = React8.forwardRef(
210
+ ({ asChild = false, children, onClick, ...props }, forwardedRef) => {
211
+ const { onOpenChange, triggerRef } = useDialogContext("Dialog.Trigger");
212
+ const handleClick = (event) => {
213
+ onClick?.(event);
214
+ onOpenChange(true);
215
+ };
216
+ const Comp = asChild ? Slot : "button";
217
+ return /* @__PURE__ */ jsx(
218
+ Comp,
219
+ {
220
+ ref: (node) => {
221
+ if (forwardedRef) {
222
+ if (typeof forwardedRef === "function") {
223
+ forwardedRef(node);
224
+ } else {
225
+ forwardedRef.current = node;
226
+ }
227
+ }
228
+ triggerRef.current = node;
229
+ },
230
+ type: asChild ? void 0 : "button",
231
+ onClick: handleClick,
232
+ ...props,
233
+ children
234
+ }
235
+ );
236
+ }
237
+ );
238
+ DialogTrigger.displayName = "Dialog.Trigger";
239
+ function DialogPortal({ container, children }) {
240
+ const { open } = useDialogContext("Dialog.Portal");
241
+ const [mounted, setMounted] = React8.useState(false);
242
+ React8.useEffect(() => {
243
+ setMounted(true);
244
+ }, []);
245
+ if (!open || !mounted) return null;
246
+ return ReactDOM.createPortal(
247
+ children,
248
+ container || document.body
249
+ );
250
+ }
251
+ var DialogOverlay = React8.forwardRef(
252
+ ({ onClick, ...props }, forwardedRef) => {
253
+ const { onOpenChange } = useDialogContext("Dialog.Overlay");
254
+ const handleClick = (event) => {
255
+ onClick?.(event);
256
+ onOpenChange(false);
257
+ };
258
+ return /* @__PURE__ */ jsx(
259
+ "div",
260
+ {
261
+ ref: forwardedRef,
262
+ onClick: handleClick,
263
+ style: {
264
+ position: "fixed",
265
+ inset: 0,
266
+ zIndex: 9998
267
+ },
268
+ ...props
269
+ }
270
+ );
271
+ }
272
+ );
273
+ DialogOverlay.displayName = "Dialog.Overlay";
274
+ var DialogContent = React8.forwardRef(
275
+ ({ onEscapeKeyDown, onOverlayClick, children, ...props }, forwardedRef) => {
276
+ const { open, onOpenChange, contentRef, titleId, descriptionId } = useDialogContext("Dialog.Content");
277
+ useFocusTrap(contentRef, open, true);
278
+ useBodyScrollLock(open);
279
+ useEscapeKeydown(
280
+ (event) => {
281
+ onEscapeKeyDown?.(event);
282
+ onOpenChange(false);
283
+ },
284
+ open
285
+ );
286
+ const handleContentClick = (event) => {
287
+ event.stopPropagation();
288
+ };
289
+ return /* @__PURE__ */ jsx(
290
+ "div",
291
+ {
292
+ ref: (node) => {
293
+ if (forwardedRef) {
294
+ if (typeof forwardedRef === "function") {
295
+ forwardedRef(node);
296
+ } else {
297
+ forwardedRef.current = node;
298
+ }
299
+ }
300
+ contentRef.current = node;
301
+ },
302
+ role: "dialog",
303
+ "aria-modal": "true",
304
+ "aria-labelledby": titleId,
305
+ "aria-describedby": descriptionId,
306
+ onClick: handleContentClick,
307
+ style: {
308
+ position: "fixed",
309
+ top: "50%",
310
+ left: "50%",
311
+ transform: "translate(-50%, -50%)",
312
+ zIndex: 9999
313
+ },
314
+ ...props,
315
+ children
316
+ }
317
+ );
318
+ }
319
+ );
320
+ DialogContent.displayName = "Dialog.Content";
321
+ var DialogTitle = React8.forwardRef(
322
+ ({ children, ...props }, forwardedRef) => {
323
+ const { titleId } = useDialogContext("Dialog.Title");
324
+ return /* @__PURE__ */ jsx("h2", { ref: forwardedRef, id: titleId, ...props, children });
325
+ }
326
+ );
327
+ DialogTitle.displayName = "Dialog.Title";
328
+ var DialogDescription = React8.forwardRef(({ children, ...props }, forwardedRef) => {
329
+ const { descriptionId } = useDialogContext("Dialog.Description");
330
+ return /* @__PURE__ */ jsx("p", { ref: forwardedRef, id: descriptionId, ...props, children });
331
+ });
332
+ DialogDescription.displayName = "Dialog.Description";
333
+ var DialogClose = React8.forwardRef(
334
+ ({ asChild = false, children, onClick, ...props }, forwardedRef) => {
335
+ const { onOpenChange } = useDialogContext("Dialog.Close");
336
+ const handleClick = (event) => {
337
+ onClick?.(event);
338
+ onOpenChange(false);
339
+ };
340
+ const Comp = asChild ? Slot : "button";
341
+ return /* @__PURE__ */ jsx(
342
+ Comp,
343
+ {
344
+ ref: forwardedRef,
345
+ type: asChild ? void 0 : "button",
346
+ onClick: handleClick,
347
+ ...props,
348
+ children
349
+ }
350
+ );
351
+ }
352
+ );
353
+ DialogClose.displayName = "Dialog.Close";
354
+ Dialog.Trigger = DialogTrigger;
355
+ Dialog.Portal = DialogPortal;
356
+ Dialog.Overlay = DialogOverlay;
357
+ Dialog.Content = DialogContent;
358
+ Dialog.Title = DialogTitle;
359
+ Dialog.Description = DialogDescription;
360
+ Dialog.Close = DialogClose;
361
+ var dialog_default = Dialog;
362
+ var Button = React8.forwardRef(
363
+ ({ asChild = false, ...props }, ref) => {
364
+ const Comp = asChild ? Slot : "button";
365
+ return /* @__PURE__ */ jsx(Comp, { ref, ...props });
366
+ }
367
+ );
368
+ Button.displayName = "Button";
369
+ var button_default = Button;
370
+
371
+ export { button_default as Button, dialog_default as Dialog, Slot, composeRefs, useBodyScrollLock, useControlled, useEscapeKeydown, useFocusTrap, useStableId };
372
+ //# sourceMappingURL=index.mjs.map
373
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/use-controlled.ts","../src/hooks/use-focus-trap.ts","../src/hooks/use-escape-keydown.ts","../src/hooks/use-body-scroll-lock.ts","../src/hooks/use-id.ts","../src/utils/compose-refs.ts","../src/primitives/slot.tsx","../src/components/dialog/dialog-context.ts","../src/components/dialog/dialog.tsx","../src/components/button/button.tsx"],"names":["React","React2","React3","React4","React5","React6","React7","React9","jsx"],"mappings":"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","file":"index.mjs","sourcesContent":["import * as React from 'react';\n\ninterface UseControlledOptions<T> {\n /** Controlled value */\n value?: T;\n /** Default value for uncontrolled mode */\n defaultValue?: T;\n /** Change handler */\n onChange?: (value: T) => void;\n}\n\n/**\n * Hook for managing controlled/uncontrolled state pattern.\n * Supports both controlled (value + onChange) and uncontrolled (defaultValue) modes.\n * \n * @example\n * // Controlled\n * const [value, setValue] = useControlled({ value: externalValue, onChange: setExternalValue });\n * \n * // Uncontrolled\n * const [value, setValue] = useControlled({ defaultValue: false });\n */\nexport function useControlled<T>({\n value: valueProp,\n defaultValue,\n onChange,\n}: UseControlledOptions<T>): [T, (newValue: T) => void] {\n const [valueState, setValueState] = React.useState<T>(defaultValue as T);\n const isControlled = valueProp !== undefined;\n\n const value = isControlled ? valueProp : valueState;\n\n const setValue = React.useCallback(\n (newValue: T) => {\n if (!isControlled) {\n setValueState(newValue);\n }\n onChange?.(newValue);\n },\n [isControlled, onChange]\n );\n\n return [value as T, setValue];\n}\n","import * as React from 'react';\n\nconst FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'textarea:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\n/**\n * Hook for trapping focus within a container.\n * Handles Tab/Shift+Tab cycling and prevents focus escape.\n * \n * @param containerRef - Ref to the container element\n * @param enabled - Whether the focus trap is active\n * @param autoFocus - Whether to auto-focus the first element on mount\n */\nexport function useFocusTrap(\n containerRef: React.RefObject<HTMLElement | null>,\n enabled = true,\n autoFocus = true\n) {\n const previouslyFocusedElement = React.useRef<HTMLElement | null>(null);\n\n React.useEffect(() => {\n if (!enabled || !containerRef.current) return;\n\n const container = containerRef.current;\n\n // Store the previously focused element\n previouslyFocusedElement.current = document.activeElement as HTMLElement;\n\n // Get all focusable elements\n const getFocusableElements = (): HTMLElement[] => {\n return Array.from(\n container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)\n );\n };\n\n // Auto-focus first element if enabled\n if (autoFocus) {\n const focusableElements = getFocusableElements();\n if (focusableElements.length > 0) {\n focusableElements[0].focus();\n }\n }\n\n // Handle Tab key to cycle focus\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Tab') return;\n\n const focusableElements = getFocusableElements();\n if (focusableElements.length === 0) return;\n\n const firstElement = focusableElements[0];\n const lastElement = focusableElements[focusableElements.length - 1];\n\n if (event.shiftKey) {\n // Shift + Tab: move focus to last element if on first\n if (document.activeElement === firstElement) {\n event.preventDefault();\n lastElement.focus();\n }\n } else {\n // Tab: move focus to first element if on last\n if (document.activeElement === lastElement) {\n event.preventDefault();\n firstElement.focus();\n }\n }\n };\n\n container.addEventListener('keydown', handleKeyDown);\n\n return () => {\n container.removeEventListener('keydown', handleKeyDown);\n\n // Restore focus to previously focused element\n if (previouslyFocusedElement.current) {\n previouslyFocusedElement.current.focus();\n }\n };\n }, [enabled, autoFocus, containerRef]);\n}\n","import * as React from 'react';\n\n/**\n * Hook for handling Escape key press events.\n * Automatically cleans up event listener on unmount.\n */\nexport function useEscapeKeydown(\n handler: (event: KeyboardEvent) => void,\n enabled = true\n) {\n React.useEffect(() => {\n if (!enabled) return;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n handler(event);\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [handler, enabled]);\n}\n","import * as React from 'react';\n\n/**\n * Hook for preventing body scroll when a modal/dialog is open.\n * This is an accessibility requirement to prevent background scrolling.\n */\nexport function useBodyScrollLock(enabled = true) {\n React.useEffect(() => {\n if (!enabled) return;\n\n const originalOverflow = document.body.style.overflow;\n const originalPaddingRight = document.body.style.paddingRight;\n\n // Prevent scroll\n document.body.style.overflow = 'hidden';\n\n // Prevent layout shift by adding padding to compensate for scrollbar\n const scrollbarWidth =\n window.innerWidth - document.documentElement.clientWidth;\n if (scrollbarWidth > 0) {\n document.body.style.paddingRight = `${scrollbarWidth}px`;\n }\n\n return () => {\n document.body.style.overflow = originalOverflow;\n document.body.style.paddingRight = originalPaddingRight;\n };\n }, [enabled]);\n}\n","import * as React from 'react';\n\nlet count = 0;\nfunction generateId() {\n return `headless-ui-${++count}`;\n}\n\n/**\n * Hook for generating stable, unique IDs.\n * Uses React's useId when available, with a fallback for older versions.\n */\nexport function useStableId(idProp?: string): string {\n const [id] = React.useState(() => idProp || generateId());\n return idProp || id;\n}\n","/**\n * Composes multiple refs into a single ref callback.\n * Useful when you need to forward a ref while also using it internally.\n */\nexport function composeRefs<T>(...refs: Array<React.Ref<T> | undefined>) {\n return (node: T | null) => {\n refs.forEach((ref) => {\n if (!ref) return;\n\n if (typeof ref === 'function') {\n ref(node);\n } else {\n (ref as React.MutableRefObject<T | null>).current = node;\n }\n });\n };\n}\n","import * as React from 'react';\nimport { composeRefs } from '../utils/compose-refs';\n\ninterface SlotProps extends React.HTMLAttributes<HTMLElement> {\n children?: React.ReactNode;\n}\n\n/**\n * Slot component for the asChild pattern.\n * When asChild is true, merges props with the child element instead of rendering a wrapper.\n */\nexport const Slot = React.forwardRef<HTMLElement, SlotProps>(\n (props, forwardedRef) => {\n const { children, ...slotProps } = props;\n\n if (!React.isValidElement(children)) {\n return null;\n }\n\n return React.cloneElement(children, {\n ...mergeProps(slotProps, children.props),\n ref: forwardedRef\n ? composeRefs(forwardedRef, (children as any).ref)\n : (children as any).ref,\n });\n }\n);\n\nSlot.displayName = 'Slot';\n\n/**\n * Merges props from the Slot with props from the child element.\n * Event handlers are composed, other props from child take precedence.\n */\nfunction mergeProps(slotProps: any, childProps: any) {\n const merged = { ...childProps };\n\n for (const key in childProps) {\n const slotValue = slotProps[key];\n const childValue = childProps[key];\n\n // Compose event handlers\n if (/^on[A-Z]/.test(key)) {\n if (slotValue && childValue) {\n merged[key] = (...args: any[]) => {\n childValue(...args);\n slotValue(...args);\n };\n } else if (slotValue) {\n merged[key] = slotValue;\n }\n }\n // Child props take precedence for non-event handlers\n else if (key === 'style') {\n merged[key] = { ...slotValue, ...childValue };\n } else if (key === 'className') {\n merged[key] = [slotValue, childValue].filter(Boolean).join(' ');\n }\n }\n\n // Add slot props that don't exist in child\n for (const key in slotProps) {\n if (!(key in childProps)) {\n merged[key] = slotProps[key];\n }\n }\n\n return merged;\n}\n","import * as React from 'react';\n\nexport interface DialogContextValue {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n triggerRef: React.RefObject<HTMLElement | null>;\n contentRef: React.RefObject<HTMLElement | null>;\n titleId: string;\n descriptionId: string;\n}\n\nexport const DialogContext = React.createContext<DialogContextValue | null>(\n null\n);\n\n/**\n * Hook to access Dialog context.\n * Throws an error if used outside of a Dialog component.\n */\nexport function useDialogContext(componentName: string): DialogContextValue {\n const context = React.useContext(DialogContext);\n\n if (!context) {\n throw new Error(\n `<${componentName}> must be used within a <Dialog> component.`\n );\n }\n\n return context;\n}\n","import * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { useControlled } from '../../hooks/use-controlled';\nimport { useFocusTrap } from '../../hooks/use-focus-trap';\nimport { useEscapeKeydown } from '../../hooks/use-escape-keydown';\nimport { useBodyScrollLock } from '../../hooks/use-body-scroll-lock';\nimport { useStableId } from '../../hooks/use-id';\nimport { Slot } from '../../primitives/slot';\nimport { DialogContext, useDialogContext } from './dialog-context';\nimport type {\n DialogProps,\n DialogTriggerProps,\n DialogPortalProps,\n DialogOverlayProps,\n DialogContentProps,\n DialogTitleProps,\n DialogDescriptionProps,\n DialogCloseProps,\n} from './types';\n\n/**\n * Dialog Root Component\n * \n * Provides context for all Dialog sub-components.\n * Supports both controlled and uncontrolled state.\n * \n * @example\n * // Controlled\n * <Dialog open={open} onOpenChange={setOpen}>\n * ...\n * </Dialog>\n * \n * // Uncontrolled\n * <Dialog defaultOpen={false}>\n * ...\n * </Dialog>\n */\nexport function Dialog({\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n children,\n}: DialogProps) {\n const [open, setOpen] = useControlled({\n value: openProp,\n defaultValue: defaultOpen,\n onChange: onOpenChange,\n });\n\n const triggerRef = React.useRef<HTMLElement>(null);\n const contentRef = React.useRef<HTMLElement>(null);\n const titleId = useStableId();\n const descriptionId = useStableId();\n\n const contextValue = React.useMemo(\n () => ({\n open,\n onOpenChange: setOpen,\n triggerRef,\n contentRef,\n titleId,\n descriptionId,\n }),\n [open, setOpen, titleId, descriptionId]\n );\n\n return (\n <DialogContext.Provider value={contextValue}>\n {children}\n </DialogContext.Provider>\n );\n}\n\n/**\n * Dialog Trigger Component\n * \n * Button that opens the dialog when clicked.\n * Supports asChild pattern for custom elements.\n * \n * @example\n * <Dialog.Trigger>Open Dialog</Dialog.Trigger>\n * \n * // With asChild\n * <Dialog.Trigger asChild>\n * <button className=\"custom-button\">Open</button>\n * </Dialog.Trigger>\n */\nconst DialogTrigger = React.forwardRef<HTMLButtonElement, DialogTriggerProps>(\n ({ asChild = false, children, onClick, ...props }, forwardedRef) => {\n const { onOpenChange, triggerRef } = useDialogContext('Dialog.Trigger');\n\n const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(event);\n onOpenChange(true);\n };\n\n const Comp = asChild ? Slot : 'button';\n\n return (\n <Comp\n ref={(node: any) => {\n if (forwardedRef) {\n if (typeof forwardedRef === 'function') {\n forwardedRef(node);\n } else {\n forwardedRef.current = node;\n }\n }\n (triggerRef as React.MutableRefObject<HTMLElement | null>).current =\n node;\n }}\n type={asChild ? undefined : 'button'}\n onClick={handleClick}\n {...props}\n >\n {children}\n </Comp>\n );\n }\n);\n\nDialogTrigger.displayName = 'Dialog.Trigger';\n\n/**\n * Dialog Portal Component\n * \n * Renders children in a portal outside the DOM hierarchy.\n * Only renders when dialog is open.\n * \n * @example\n * <Dialog.Portal>\n * <Dialog.Overlay />\n * <Dialog.Content>...</Dialog.Content>\n * </Dialog.Portal>\n */\nfunction DialogPortal({ container, children }: DialogPortalProps) {\n const { open } = useDialogContext('Dialog.Portal');\n const [mounted, setMounted] = React.useState(false);\n\n React.useEffect(() => {\n setMounted(true);\n }, []);\n\n if (!open || !mounted) return null;\n\n return ReactDOM.createPortal(\n children,\n container || document.body\n );\n}\n\n/**\n * Dialog Overlay Component\n * \n * Semi-transparent backdrop behind the dialog.\n * Clicking it closes the dialog.\n * \n * @example\n * <Dialog.Overlay style={{ backgroundColor: 'rgba(0, 0, 0, 0.5)' }} />\n */\nconst DialogOverlay = React.forwardRef<HTMLDivElement, DialogOverlayProps>(\n ({ onClick, ...props }, forwardedRef) => {\n const { onOpenChange } = useDialogContext('Dialog.Overlay');\n\n const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {\n onClick?.(event);\n onOpenChange(false);\n };\n\n return (\n <div\n ref={forwardedRef}\n onClick={handleClick}\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 9998,\n }}\n {...props}\n />\n );\n }\n);\n\nDialogOverlay.displayName = 'Dialog.Overlay';\n\n/**\n * Dialog Content Component\n * \n * Main container for dialog content.\n * Implements focus trap, keyboard handling, and ARIA attributes.\n * \n * @example\n * <Dialog.Content>\n * <Dialog.Title>Title</Dialog.Title>\n * <Dialog.Description>Description</Dialog.Description>\n * <Dialog.Close>Close</Dialog.Close>\n * </Dialog.Content>\n */\nconst DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(\n (\n { onEscapeKeyDown, onOverlayClick, children, ...props },\n forwardedRef\n ) => {\n const { open, onOpenChange, contentRef, titleId, descriptionId } =\n useDialogContext('Dialog.Content');\n\n // Focus trap\n useFocusTrap(contentRef, open, true);\n\n // Body scroll lock\n useBodyScrollLock(open);\n\n // Escape key handling\n useEscapeKeydown(\n (event) => {\n onEscapeKeyDown?.(event);\n onOpenChange(false);\n },\n open\n );\n\n // Prevent clicks inside content from closing dialog\n const handleContentClick = (event: React.MouseEvent<HTMLDivElement>) => {\n event.stopPropagation();\n };\n\n return (\n <div\n ref={(node) => {\n if (forwardedRef) {\n if (typeof forwardedRef === 'function') {\n forwardedRef(node);\n } else {\n forwardedRef.current = node;\n }\n }\n (contentRef as React.MutableRefObject<HTMLDivElement | null>).current =\n node;\n }}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n aria-describedby={descriptionId}\n onClick={handleContentClick}\n style={{\n position: 'fixed',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n zIndex: 9999,\n }}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nDialogContent.displayName = 'Dialog.Content';\n\n/**\n * Dialog Title Component\n * \n * Title for the dialog, linked via aria-labelledby.\n * \n * @example\n * <Dialog.Title>Confirm Action</Dialog.Title>\n */\nconst DialogTitle = React.forwardRef<HTMLHeadingElement, DialogTitleProps>(\n ({ children, ...props }, forwardedRef) => {\n const { titleId } = useDialogContext('Dialog.Title');\n\n return (\n <h2 ref={forwardedRef} id={titleId} {...props}>\n {children}\n </h2>\n );\n }\n);\n\nDialogTitle.displayName = 'Dialog.Title';\n\n/**\n * Dialog Description Component\n * \n * Description for the dialog, linked via aria-describedby.\n * \n * @example\n * <Dialog.Description>This action cannot be undone.</Dialog.Description>\n */\nconst DialogDescription = React.forwardRef<\n HTMLParagraphElement,\n DialogDescriptionProps\n>(({ children, ...props }, forwardedRef) => {\n const { descriptionId } = useDialogContext('Dialog.Description');\n\n return (\n <p ref={forwardedRef} id={descriptionId} {...props}>\n {children}\n </p>\n );\n});\n\nDialogDescription.displayName = 'Dialog.Description';\n\n/**\n * Dialog Close Component\n * \n * Button that closes the dialog when clicked.\n * Supports asChild pattern for custom elements.\n * \n * @example\n * <Dialog.Close>Cancel</Dialog.Close>\n * \n * // With asChild\n * <Dialog.Close asChild>\n * <button className=\"custom-button\">Close</button>\n * </Dialog.Close>\n */\nconst DialogClose = React.forwardRef<HTMLButtonElement, DialogCloseProps>(\n ({ asChild = false, children, onClick, ...props }, forwardedRef) => {\n const { onOpenChange } = useDialogContext('Dialog.Close');\n\n const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(event);\n onOpenChange(false);\n };\n\n const Comp = asChild ? Slot : 'button';\n\n return (\n <Comp\n ref={forwardedRef}\n type={asChild ? undefined : 'button'}\n onClick={handleClick}\n {...props}\n >\n {children}\n </Comp>\n );\n }\n);\n\nDialogClose.displayName = 'Dialog.Close';\n\n// Attach sub-components to Dialog\nDialog.Trigger = DialogTrigger;\nDialog.Portal = DialogPortal;\nDialog.Overlay = DialogOverlay;\nDialog.Content = DialogContent;\nDialog.Title = DialogTitle;\nDialog.Description = DialogDescription;\nDialog.Close = DialogClose;\n\n// Type augmentation for compound components\nexport interface DialogComponent extends React.FC<DialogProps> {\n Trigger: typeof DialogTrigger;\n Portal: typeof DialogPortal;\n Overlay: typeof DialogOverlay;\n Content: typeof DialogContent;\n Title: typeof DialogTitle;\n Description: typeof DialogDescription;\n Close: typeof DialogClose;\n}\n\nexport default Dialog as DialogComponent;\n","import * as React from 'react';\nimport { Slot } from '../../primitives/slot';\nimport { ButtonProps } from './types';\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : 'button';\n return <Comp ref={ref} {...props} />;\n }\n);\n\nButton.displayName = 'Button';\n\nexport default Button;\n"]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@alankrit2/ui",
3
+ "version": "0.1.0",
4
+ "description": "Production-quality, headless, accessible React UI components",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "react",
26
+ "ui",
27
+ "components",
28
+ "headless",
29
+ "accessible",
30
+ "dialog",
31
+ "modal",
32
+ "primitives"
33
+ ],
34
+ "author": "alankrit",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/alankrit/ui"
39
+ },
40
+ "peerDependencies": {
41
+ "react": "^18.0.0 || ^19.0.0",
42
+ "react-dom": "^18.0.0 || ^19.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/react": "^19.0.0",
46
+ "@types/react-dom": "^19.0.0",
47
+ "react": "^19.0.0",
48
+ "react-dom": "^19.0.0",
49
+ "tsup": "^8.0.0",
50
+ "typescript": "^5.0.0"
51
+ }
52
+ }