Skip to content

fix(#736): updates for doc issues found in apsara audit#756

Open
Shreyag02 wants to merge 1 commit intomainfrom
fix/doc_issues_from_apsara_audit
Open

fix(#736): updates for doc issues found in apsara audit#756
Shreyag02 wants to merge 1 commit intomainfrom
fix/doc_issues_from_apsara_audit

Conversation

@Shreyag02
Copy link
Copy Markdown
Contributor

Description

Resolves multiple documentation issues identified in the Apsara UI audit. Fixes include:

  • Fix demo container overflow clipping indicator dots and slider labels
  • Fix accordion controlled mode losing state on close/reopen
  • Fix breadcrumb demo links navigating to 404 pages
  • Add calendar month/year dropdown demo
  • Split datatable demo into virtualized/non-virtualized tabs with synced code preview
  • Add uncontrolled clear support and click-to-focus in search component
  • Expose inputRef prop in search for external access
  • Add interactive chips demo for input field with enter-to-add behavior
  • Create custom 404 page using apsara theme tokens for light/dark mode support

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) -> might affect usage of accordion and search
  • Documentation update
  • Improvement (Improvements to existing code)

How Has This Been Tested?

  • Verified accordion controlled mode opens/closes/reopens correctly
  • Verified breadcrumb demo links no longer navigate to 404
  • Verified calendar dropdown demo renders month/year selects
  • Verified datatable tabs switch between virtualized/non-virtualized with synced code preview
  • Verified indicator dots and slider labels are no longer clipped in demo previews
  • Verified search clear button works in both controlled and uncontrolled modes
  • Verified search container click focuses the input
  • Verified input field interactive chips demo adds on Enter and removes on dismiss
  • Verified 404 page renders correctly in both light and dark themes

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (.mdx files)
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works

Related Issues

#736

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Apr 23, 2026 11:57am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

📝 Walkthrough

Walkthrough

The pull request introduces several updates across documentation demos and component functionality. A new 404 error page component is added with custom styling. The DataTable demo is refactored to support both standard and virtualized rendering modes with tabbed examples. Multiple documentation demos are enhanced with tabbed variants showing different implementation approaches (uncontrolled/controlled Search patterns, dropdown Calendar layout). The Search and Accordion components are updated to support controlled/uncontrolled usage patterns and improved value normalization. InputField documentation is expanded with an interactive chip input example. Various CSS styling adjustments are made to preview and demo containers.

Suggested reviewers

  • rsbh
  • paanSinghCoder
  • rohanchkrabrty
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically references the main objective: fixing documentation issues found in the Apsara audit (issue #736), which aligns with the changeset's primary focus.
Description check ✅ Passed The description is directly related to the changeset, providing a detailed list of fixes and improvements that matches the file-level changes including accordion, breadcrumb, calendar, datatable, search, input field, and 404 page updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Shreyag02 Shreyag02 requested review from paanSinghCoder and rohanchkrabrty and removed request for rohanchkrabrty April 23, 2026 11:57
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
apps/www/src/content/docs/components/input-field/demo.ts (1)

131-159: Consider deduplicating/guarding chip entries.

Minor UX polish for the demo: pressing Enter on an already-present label will add a duplicate chip (e.g., Tag1 twice), and very long input produces a chip that may push the layout. Not a bug, but since this demo is meant to showcase realistic usage, a small guard would make it a better reference implementation for consumers.

♻️ Suggested tweak
       onKeyDown={(e) => {
-        if (e.key === "Enter" && input.trim()) {
-          setChips([...chips, { label: input.trim() }]);
-          setInput("");
+        if (e.key === "Enter" && input.trim()) {
+          e.preventDefault();
+          const label = input.trim();
+          if (!chips.some((c) => c.label === label)) {
+            setChips([...chips, { label }]);
+          }
+          setInput("");
         }
       }}

The e.preventDefault() also avoids any accidental form submission if this demo is ever embedded inside a <form>.

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

In `@apps/www/src/content/docs/components/input-field/demo.ts` around lines 131 -
159, In ChipInputDemo, prevent adding duplicate or excessively long chips by
enhancing the onKeyDown handler on the InputField: call e.preventDefault(),
compute const value = input.trim(), check that value is non-empty, not already
present in chips (compare against chips.map(c=>c.label)), and optionally limit
length (e.g., value.length <= MAX_LABEL_LENGTH) before calling
setChips([...chips, {label: value}]) and clearing setInput(""); update the chips
onRemove logic remains the same.
apps/www/src/app/not-found.tsx (1)

13-17: Render the 404 copy with semantic elements.

Text defaults to span, so this page lacks heading and paragraph semantics for assistive-tech navigation. Use the render prop to render the status as h1 and message as p while preserving visual styling.

Proposed semantic markup update
-      <Text size={10} weight='bold'>
+      <Text render={<h1 />} size={10} weight='bold' style={{ margin: 0 }}>
         404
       </Text>
       <div className={styles.divider} />
-      <Text size={7}>This page could not be found.</Text>
+      <Text render={<p />} size={7} style={{ margin: 0 }}>
+        This page could not be found.
+      </Text>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/app/not-found.tsx` around lines 13 - 17, The Text component is
currently rendering as a span, so update the two Text usages to use the render
prop to provide semantic elements: render the status Text (the one with
size={10} weight='bold') as an h1 and render the message Text (size={7}) as a p,
preserving their size/weight props and keeping the existing divider
(styles.divider) in place; locate the Text usages in not-found.tsx and add
render="h1" and render="p" respectively.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/www/src/content/docs/components/search/demo.ts`:
- Around line 46-60: The ControlledSearch component is defined but never
rendered; after the ControlledSearch function declaration add an instance of it
(i.e., render <ControlledSearch />) so the controlled demo appears in the live
preview — locate the ControlledSearch function in the diff and append a JSX
mount of ControlledSearch right after its definition.

In `@packages/raystack/components/search/search.tsx`:
- Around line 35-36: The uncontrolled Search initializes internalValue to '' so
a passed defaultValue is ignored and both value and defaultValue get forwarded;
change the uncontrolled initialization in the Search component so internalValue
is initialized from props.defaultValue (e.g., const [internalValue,
setInternalValue] = useState(() => props.defaultValue ?? '')), keep currentValue
= isControlled ? controlledValue : internalValue, and when rendering the
underlying input only pass value when isControlled (so the uncontrolled case
only forwards defaultValue in ...props); update references to internalValue,
setInternalValue, currentValue, isControlled and controlledValue accordingly.

---

Nitpick comments:
In `@apps/www/src/app/not-found.tsx`:
- Around line 13-17: The Text component is currently rendering as a span, so
update the two Text usages to use the render prop to provide semantic elements:
render the status Text (the one with size={10} weight='bold') as an h1 and
render the message Text (size={7}) as a p, preserving their size/weight props
and keeping the existing divider (styles.divider) in place; locate the Text
usages in not-found.tsx and add render="h1" and render="p" respectively.

In `@apps/www/src/content/docs/components/input-field/demo.ts`:
- Around line 131-159: In ChipInputDemo, prevent adding duplicate or excessively
long chips by enhancing the onKeyDown handler on the InputField: call
e.preventDefault(), compute const value = input.trim(), check that value is
non-empty, not already present in chips (compare against chips.map(c=>c.label)),
and optionally limit length (e.g., value.length <= MAX_LABEL_LENGTH) before
calling setChips([...chips, {label: value}]) and clearing setInput(""); update
the chips onRemove logic remains the same.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 64524df2-bf9e-4881-9147-e69994eb7b89

📥 Commits

Reviewing files that changed from the base of the PR and between 08768b4 and 01eeb11.

📒 Files selected for processing (15)
  • apps/www/src/app/not-found.module.css
  • apps/www/src/app/not-found.tsx
  • apps/www/src/components/datatable-demo.tsx
  • apps/www/src/components/demo/demo-preview.tsx
  • apps/www/src/components/demo/demo.tsx
  • apps/www/src/components/demo/styles.module.css
  • apps/www/src/components/preview/preview.module.css
  • apps/www/src/content/docs/components/breadcrumb/demo.ts
  • apps/www/src/content/docs/components/calendar/demo.ts
  • apps/www/src/content/docs/components/datatable/demo.ts
  • apps/www/src/content/docs/components/input-field/demo.ts
  • apps/www/src/content/docs/components/input-field/index.mdx
  • apps/www/src/content/docs/components/search/demo.ts
  • packages/raystack/components/accordion/accordion-root.tsx
  • packages/raystack/components/search/search.tsx

Comment on lines +46 to +60
function ControlledSearch() {
const [value, setValue] = React.useState("Searchable text");

return (
<Flex direction="column" gap="medium" align="center">
<Search
placeholder="Type to search..."
value={value}
onChange={(e) => setValue(e.target.value)}
showClearButton
onClear={() => setValue("")}
/>
</Flex>
);
}`
Copy link
Copy Markdown
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:

#!/bin/bash
# Description: Verify the controlled Search demo defines and renders the component.
python - <<'PY'
from pathlib import Path
p = Path("apps/www/src/content/docs/components/search/demo.ts")
s = p.read_text()
start = s.index("function ControlledSearch")
snippet = s[start:start + 700]
print(snippet)
print("\nRenders ControlledSearch:", "<ControlledSearch" in snippet)
PY

Repository: raystack/apsara

Length of output: 563


Render the controlled demo component.

The ControlledSearch function is defined but never instantiated, so the controlled demo won't display in the live preview. Add <ControlledSearch /> after the function declaration to mount it.

🐛 Proposed fix
       function ControlledSearch() {
         const [value, setValue] = React.useState("Searchable text");

         return (
           <Flex direction="column" gap="medium" align="center">
             <Search
               placeholder="Type to search..."
               value={value}
               onChange={(e) => setValue(e.target.value)}
               showClearButton
               onClear={() => setValue("")}
             />
           </Flex>
         );
-      }`
+      }
+
+      <ControlledSearch />`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/search/demo.ts` around lines 46 - 60,
The ControlledSearch component is defined but never rendered; after the
ControlledSearch function declaration add an instance of it (i.e., render
<ControlledSearch />) so the controlled demo appears in the live preview —
locate the ControlledSearch function in the diff and append a JSX mount of
ControlledSearch right after its definition.

Comment on lines +35 to +36
const [internalValue, setInternalValue] = useState('');
const currentValue = isControlled ? controlledValue : internalValue;
Copy link
Copy Markdown
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:

#!/bin/bash
# Description: Confirm Search currently ignores defaultValue while always passing value to InputField.
rg -n -C4 'defaultValue|value=\{currentValue\}|const \[internalValue' packages/raystack/components/search/search.tsx

Repository: raystack/apsara

Length of output: 825


🏁 Script executed:

cd packages/raystack/components/search && cat -n search.tsx | head -100

Repository: raystack/apsara

Length of output: 3433


🏁 Script executed:

# Also check the SearchProps interface definition
rg -n 'interface SearchProps|type SearchProps' packages/raystack/components/search/

Repository: raystack/apsara

Length of output: 184


🏁 Script executed:

# Check InputFieldProps definition to confirm it includes defaultValue
rg -n 'interface InputFieldProps|type InputFieldProps' -A 15 packages/raystack/components/input-field/

Repository: raystack/apsara

Length of output: 1487


🏁 Script executed:

# Also verify what happens when both value and defaultValue are passed to InputField
cat -n packages/raystack/components/input-field/input-field.tsx | head -50

Repository: raystack/apsara

Length of output: 1655


🏁 Script executed:

# Check what InputPrimitive.Props includes from `@base-ui/react`
# First, let's see if there are any imports or type definitions we can examine
rg -n "InputPrimitive" packages/raystack/components/input-field/input-field.tsx -B 5 -A 5

Repository: raystack/apsara

Length of output: 1115


🏁 Script executed:

# Let's check if there are any test files or examples showing defaultValue usage
fd -e test.tsx -e test.ts -e spec.tsx packages/raystack/components/search/

Repository: raystack/apsara

Length of output: 508


🏁 Script executed:

# Check if defaultValue is used anywhere in the codebase with Search component
rg 'defaultValue' packages/raystack/components/search/ -n

Repository: raystack/apsara

Length of output: 41


🏁 Script executed:

# Also check the InputField implementation to see how it handles value and defaultValue
rg -n 'defaultValue' packages/raystack/components/input-field/input-field.tsx -C 3

Repository: raystack/apsara

Length of output: 41


🏁 Script executed:

# Check the full InputField implementation, especially how it handles props
cat -n packages/raystack/components/input-field/input-field.tsx | sed -n '40,120p'

Repository: raystack/apsara

Length of output: 2701


🏁 Script executed:

# Look for any usage examples of Search component in tests or stories
fd . packages/raystack/components/search/ -type f

Repository: raystack/apsara

Length of output: 229


🏁 Script executed:

# Check if there's a storybook or test file for Search
find packages/raystack/components/search -type f \( -name "*.stories.*" -o -name "*.test.*" -o -name "*.spec.*" \)

Repository: raystack/apsara

Length of output: 120


🏁 Script executed:

# Check the test file for Search to understand expected behavior
cat -n packages/raystack/components/search/__tests__/search.test.tsx

Repository: raystack/apsara

Length of output: 9584


Honor defaultValue in uncontrolled Search.

SearchProps accepts defaultValue through InputFieldProps, but uncontrolled state initializes to an empty string while defaultValue is forwarded via ...props. This causes <Search defaultValue="foo" /> to render with value="" and pass both value and defaultValue to the underlying input, making React treat it as controlled and ignoring the default.

🐛 Proposed fix
 export function Search({
   className,
   disabled,
   placeholder = 'Search',
   size,
   showClearButton,
   onClear,
   value: controlledValue,
   onChange,
   width = '100%',
   variant = 'default',
   inputRef: externalRef,
+  defaultValue = '',
   ...props
 }: SearchProps) {
   const internalRef = useRef<HTMLInputElement>(null);
   const inputRef = externalRef ?? internalRef;
   const isControlled = controlledValue !== undefined;
-  const [internalValue, setInternalValue] = useState('');
+  const [internalValue, setInternalValue] = useState(() =>
+    defaultValue == null ? '' : String(defaultValue)
+  );
   const currentValue = isControlled ? controlledValue : internalValue;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/search/search.tsx` around lines 35 - 36, The
uncontrolled Search initializes internalValue to '' so a passed defaultValue is
ignored and both value and defaultValue get forwarded; change the uncontrolled
initialization in the Search component so internalValue is initialized from
props.defaultValue (e.g., const [internalValue, setInternalValue] = useState(()
=> props.defaultValue ?? '')), keep currentValue = isControlled ?
controlledValue : internalValue, and when rendering the underlying input only
pass value when isControlled (so the uncontrolled case only forwards
defaultValue in ...props); update references to internalValue, setInternalValue,
currentValue, isControlled and controlledValue accordingly.

showClearButton?: boolean;
onClear?: () => void;
variant?: 'default' | 'borderless';
inputRef?: RefObject<HTMLInputElement | null>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we can just name this ref as it will be almost always known that ref will be pointing to the input field.

Moreover, lets add this in the Search docs as well.

(onValueChange as (value: string | undefined) => void)(
newValue[0] || undefined
);
(onValueChange as (value: string) => void)(newValue[0] ?? '');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why did we change newValue[0] || undefined to newValue[0] ?? ''?

@@ -151,9 +151,9 @@ export const iconsDemo = {
<Breadcrumb>
<Breadcrumb.Item href="/" leadingIcon={<BellIcon />}>Home</Breadcrumb.Item>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

href='/' should also be href='#'?

<Text size={10} weight='bold'>
404
</Text>
<div className={styles.divider} />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have a Separator component in Apsara. It can be used here with the vertical orientation.

https://apsara-raystack.vercel.app/docs/components/separator

const internalRef = useRef<HTMLInputElement>(null);
const inputRef = externalRef ?? internalRef;
const isControlled = controlledValue !== undefined;
const [internalValue, setInternalValue] = useState('');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The defaultValue prop is probably getting ignored. It is inherited from InputField component. Please check this once.

Previously if the value prop is undefined it treats Search as uncontrolled and defaultValue is rendered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants