Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions packages/raystack/components/copy-button/copy-button.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { CopyIcon } from '@radix-ui/react-icons';
import { ElementRef, forwardRef, useState } from 'react';
import { useState } from 'react';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
import { CheckCircleFilledIcon } from '~/icons';
import { IconButton, IconButtonProps } from '../icon-button/icon-button';
Expand All @@ -12,10 +12,12 @@ export interface CopyButtonProps extends IconButtonProps {
resetIcon?: boolean;
}

export const CopyButton = forwardRef<
ElementRef<typeof IconButton>,
CopyButtonProps
>(({ text, resetTimeout = 1000, resetIcon = true, ...props }, ref) => {
export function CopyButton({
text,
resetTimeout = 1000,
resetIcon = true,
...props
}: CopyButtonProps) {
const { copy } = useCopyToClipboard();
const [isCopied, setIsCopied] = useState(false);

Expand All @@ -32,19 +34,14 @@ export const CopyButton = forwardRef<
}

return (
<IconButton
ref={ref}
{...props}
onClick={onCopy}
data-test-id='copy-button'
>
<IconButton {...props} onClick={onCopy} data-test-id='copy-button'>
{isCopied ? (
<CheckCircleFilledIcon color='var(--rs-color-foreground-success-primary)' />
) : (
<CopyIcon />
)}
</IconButton>
);
});
}

CopyButton.displayName = 'CopyButton';
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,5 @@ export function Content({
</div>
);
}

Content.displayName = 'DataTable.Content';
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { Popover } from '../../popover';
import styles from '../data-table.module.css';
import {
DataTableColumn,
SortOrdersValues,
defaultGroupOption
defaultGroupOption,
SortOrdersValues
} from '../data-table.types';
import { useDataTable } from '../hooks/useDataTable';
import { DisplayProperties } from './display-properties';
Expand Down Expand Up @@ -124,3 +124,5 @@ export function DisplaySettings<TData, TValue>({
</Popover>
);
}

DisplaySettings.displayName = 'DataTable.DisplayControls';
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,5 @@ export function Filters<TData, TValue>({
</Flex>
);
}

Filters.displayName = 'DataTable.Filters';
94 changes: 47 additions & 47 deletions packages/raystack/components/data-table/components/search.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,64 @@
'use client';

import { forwardRef } from 'react';
import { ChangeEvent, type ComponentProps } from 'react';
import { Search } from '../../search';
import { SearchProps } from '../../search/search';
import { useDataTable } from '../hooks/useDataTable';

export interface DataTableSearchProps extends SearchProps {
export interface DataTableSearchProps extends ComponentProps<typeof Search> {
/**
* Automatically disable search in zero state (when no data and no filters/search applied).
* @defaultValue true
*/
autoDisableInZeroState?: boolean;
}

export const TableSearch = forwardRef<HTMLInputElement, DataTableSearchProps>(
({ autoDisableInZeroState = true, disabled, ...props }, ref) => {
const {
updateTableQuery,
tableQuery,
shouldShowFilters = false
} = useDataTable();
export function TableSearch({
autoDisableInZeroState = true,
disabled,
...props
}: DataTableSearchProps) {
const {
updateTableQuery,
tableQuery,
shouldShowFilters = false
} = useDataTable();

const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
updateTableQuery(query => {
return {
...query,
search: value
};
});
};
const handleSearch = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
updateTableQuery(query => {
return {
...query,
search: value
};
});
};

const handleClear = () => {
updateTableQuery(query => {
return {
...query,
search: ''
};
});
};
const handleClear = () => {
updateTableQuery(query => {
return {
...query,
search: ''
};
});
};

// Auto-disable in zero state if enabled, but allow manual override
// Once search is applied, keep it enabled (even if shouldShowFilters is false)
const hasSearch = Boolean(
tableQuery?.search && tableQuery.search.trim() !== ''
);
const isDisabled =
disabled ?? (autoDisableInZeroState && !shouldShowFilters && !hasSearch);
// Auto-disable in zero state if enabled, but allow manual override
// Once search is applied, keep it enabled (even if shouldShowFilters is false)
const hasSearch = Boolean(
tableQuery?.search && tableQuery.search.trim() !== ''
);
const isDisabled =
disabled ?? (autoDisableInZeroState && !shouldShowFilters && !hasSearch);

return (
<Search
{...props}
ref={ref}
onChange={handleSearch}
value={tableQuery?.search}
onClear={handleClear}
disabled={isDisabled}
/>
);
}
);
return (
<Search
{...props}
onChange={handleSearch}
value={tableQuery?.search}
onClear={handleClear}
disabled={isDisabled}
/>
);
}

TableSearch.displayName = 'TableSearch';
TableSearch.displayName = 'DataTable.Search';
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ export function Toolbar<TData, TValue>({ className }: { className?: string }) {
</Flex>
);
}

Toolbar.displayName = 'DataTable.Toolbar';
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,5 @@ export function VirtualizedContent({
</div>
);
}

VirtualizedContent.displayName = 'DataTable.VirtualizedContent';
8 changes: 3 additions & 5 deletions packages/raystack/components/data-table/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,11 @@ function DataTableRoot<TData, TValue>({
stickyGroupHeader
]);

return (
<TableContext.Provider value={contextValue}>
{children}
</TableContext.Provider>
);
return <TableContext value={contextValue}>{children}</TableContext>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the minimum React version required in package.json
fd -t f 'package.json' packages/raystack --exec cat {} | jq -r '.peerDependencies.react // .dependencies.react // "not found"' 2>/dev/null | head -5

# Also check if there are any other context usages in the codebase that still use .Provider
rg -n '\.Provider' --type ts --type tsx -g '*.tsx' -g '*.ts' packages/raystack/

Repository: raystack/apsara

Length of output: 114


🏁 Script executed:

# Search for .Provider usages in the entire packages/raystack directory
rg -n 'Provider' packages/raystack --type ts --type js -A 2 | head -100

# Also check the specific file for any other context-related changes
cat -n packages/raystack/components/data-table/data-table.tsx | sed -n '210,230p'

Repository: raystack/apsara

Length of output: 8661


Remove React 19-only context syntax or drop React 18 support.

The change on line 218 uses <TableContext value={contextValue}> (React 19 syntax) instead of <TableContext.Provider value={...}> (React 18 syntax). Since package.json declares support for ^18 || ^19, this breaks React 18 compatibility. Other contexts in the codebase (e.g., ThemeContext in theme-provider/theme.tsx line 201) still use the .Provider syntax, indicating this is an inconsistent partial migration. Either revert to .Provider syntax for React 18 compatibility or explicitly drop React 18 support from package.json.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/data-table/data-table.tsx` at line 218, The
return uses React 19 context shorthand (<TableContext value={contextValue}>)
which breaks React 18; revert to the React 18-compatible API by returning
<TableContext.Provider value={contextValue}>{children}</TableContext.Provider>
in the component that references TableContext (use the same contextValue and
children identifiers), or if you intentionally want React 19-only, update
package.json to remove ^18 from the supported engines; pick one approach and
make the code and package.json consistent (do not leave mixed syntax like
TableContext here while ThemeContext uses .Provider).

}

DataTableRoot.displayName = 'DataTable';

export const DataTable = Object.assign(DataTableRoot, {
Content: Content,
VirtualizedContent: VirtualizedContent,
Expand Down
80 changes: 35 additions & 45 deletions packages/raystack/components/dialog/dialog-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import { Dialog as DialogPrimitive } from '@base-ui/react';
import { cx } from 'class-variance-authority';
import { type ElementRef, forwardRef } from 'react';
import styles from './dialog.module.css';
import { CloseButton } from './dialog-misc';

Expand All @@ -17,51 +16,42 @@ export interface DialogContentProps extends DialogPrimitive.Popup.Props {
showNestedAnimation?: boolean;
}

export const DialogContent = forwardRef<
ElementRef<typeof DialogPrimitive.Popup>,
DialogContentProps
>(
(
{
className,
children,
showCloseButton = true,
overlay,
width,
style,
showNestedAnimation = true,
...props
},
ref
) => {
return (
<DialogPrimitive.Portal>
<DialogPrimitive.Backdrop
{...overlay}
export function DialogContent({
className,
children,
showCloseButton = true,
overlay,
width,
style,
showNestedAnimation = true,
...props
}: DialogContentProps) {
return (
<DialogPrimitive.Portal>
<DialogPrimitive.Backdrop
{...overlay}
className={cx(
styles.dialogOverlay,
overlay?.blur && styles.overlayBlur,
overlay?.className
)}
Comment on lines +19 to +37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*dialog*" | head -20

Repository: raystack/apsara

Length of output: 730


🏁 Script executed:

cat -n packages/raystack/components/dialog/dialog-content.tsx | head -50

Repository: raystack/apsara

Length of output: 1784


🏁 Script executed:

rg "DialogPrimitive" packages/raystack/components/dialog/ -A 2 -B 2

Repository: raystack/apsara

Length of output: 6169


🏁 Script executed:

rg "DialogContentProps" packages/raystack/components/dialog/ -A 10 -B 2

Repository: raystack/apsara

Length of output: 2174


🌐 Web query:

@base-ui/react Dialog Backdrop className API callback state

💡 Result:

The @base-ui/react Dialog Backdrop component supports a className prop that accepts either a static string or a callback function receiving the component's state. Key details from official Base UI documentation: - className: string | ((state: Dialog.Backdrop.State) => string | undefined) - Description: CSS class applied to the element, or a function that returns a class based on the component’s state. Similar support exists for style: React.CSSProperties | ((state: Dialog.Backdrop.State) => CSSProperties | undefined) The exact shape of Dialog.Backdrop.State is not detailed in the public docs but typically includes properties like open and transitionStatus based on component patterns (e.g., open, mounted, transitioning). Example usage: <Dialog.Backdrop className={(state) => state.open ? 'backdrop-open' : 'backdrop-closed'} /> Data attributes like data-open are also present on the rendered div for additional styling. This API enables dynamic styling of the backdrop based on its open/transition state. Root-level state control uses open and onOpenChange props.

Citations:


🏁 Script executed:

# Check package.json to confirm `@base-ui/react` version
rg "@base-ui/react" packages/raystack/components/ -B 2 -A 2

Repository: raystack/apsara

Length of output: 31037


🏁 Script executed:

# Check if there are any tests or usage examples showing overlay className
rg "overlay.*className" packages/raystack/components/dialog/ -A 3 -B 3

Repository: raystack/apsara

Length of output: 655


🏁 Script executed:

# Look for any test files to understand expected behavior
cat -n packages/raystack/components/dialog/__tests__/dialog.test.tsx | head -100

Repository: raystack/apsara

Length of output: 3919


Fix overlay.className callback handling and prevent blur leak.

DialogPrimitive.Backdrop supports className as a string or a state callback function. Wrapping overlay?.className in cx(...) converts any function to a stringified value, breaking state-aware styling. Additionally, blur is a wrapper-only property and should not be forwarded to the primitive via {...overlay}.

Suggested fix
 export function DialogContent({
   className,
   children,
   showCloseButton = true,
   overlay,
   width,
   style,
   showNestedAnimation = true,
   ...props
 }: DialogContentProps) {
+  const {
+    blur,
+    className: overlayClassName,
+    ...overlayProps
+  } = overlay ?? {};
+
   return (
     <DialogPrimitive.Portal>
       <DialogPrimitive.Backdrop
-        {...overlay}
-        className={cx(
-          styles.dialogOverlay,
-          overlay?.blur && styles.overlayBlur,
-          overlay?.className
-        )}
+        {...overlayProps}
+        className={
+          typeof overlayClassName === 'function'
+            ? state =>
+                cx(
+                  styles.dialogOverlay,
+                  blur && styles.overlayBlur,
+                  overlayClassName(state)
+                )
+            : cx(styles.dialogOverlay, blur && styles.overlayBlur, overlayClassName)
+        }
       />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function DialogContent({
className,
children,
showCloseButton = true,
overlay,
width,
style,
showNestedAnimation = true,
...props
}: DialogContentProps) {
return (
<DialogPrimitive.Portal>
<DialogPrimitive.Backdrop
{...overlay}
className={cx(
styles.dialogOverlay,
overlay?.blur && styles.overlayBlur,
overlay?.className
)}
export function DialogContent({
className,
children,
showCloseButton = true,
overlay,
width,
style,
showNestedAnimation = true,
...props
}: DialogContentProps) {
const {
blur,
className: overlayClassName,
...overlayProps
} = overlay ?? {};
return (
<DialogPrimitive.Portal>
<DialogPrimitive.Backdrop
{...overlayProps}
className={
typeof overlayClassName === 'function'
? state =>
cx(
styles.dialogOverlay,
blur && styles.overlayBlur,
overlayClassName(state)
)
: cx(styles.dialogOverlay, blur && styles.overlayBlur, overlayClassName)
}
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/dialog/dialog-content.tsx` around lines 19 - 37,
DialogContent is spreading the overlay prop directly into
DialogPrimitive.Backdrop which forwards overlay.blur (a wrapper-only prop) and
wraps overlay.className in cx, converting a potential state callback into a
string; update DialogContent so it does NOT spread overlay blindly into
DialogPrimitive.Backdrop (remove overlay.blur before forwarding) and pass
className to Backdrop by merging styles while preserving a function callback:
compute backdropClassName by combining styles.dialogOverlay and
overlay?.blur-derived classes, then if overlay.className is a function call
Backdrop with that function (or pass it through unchanged) so state-aware
className callbacks from overlay.className are preserved when rendering
DialogPrimitive.Backdrop.

/>
<DialogPrimitive.Viewport className={styles.viewport}>
<DialogPrimitive.Popup
className={cx(
styles.dialogOverlay,
overlay?.blur && styles.overlayBlur,
overlay?.className
styles.dialogContent,
showNestedAnimation && styles.showNestedAnimation,
className
)}
/>
<DialogPrimitive.Viewport className={styles.viewport}>
<DialogPrimitive.Popup
ref={ref}
className={cx(
styles.dialogContent,
showNestedAnimation && styles.showNestedAnimation,
className
)}
style={{ width, ...style }}
{...props}
>
{children}
{showCloseButton && <CloseButton className={styles.closeButton} />}
</DialogPrimitive.Popup>
</DialogPrimitive.Viewport>
</DialogPrimitive.Portal>
);
}
);
style={{ width, ...style }}
{...props}
>
{children}
{showCloseButton && <CloseButton className={styles.closeButton} />}
</DialogPrimitive.Popup>
</DialogPrimitive.Viewport>
</DialogPrimitive.Portal>
);
}

DialogContent.displayName = 'Dialog.Content';
Loading
Loading