Skip to content
Open
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
1 change: 1 addition & 0 deletions apps/www/src/components/playground/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export * from './label-examples';
export * from './link-examples';
export * from './list-examples';
export * from './menu-examples';
export * from './number-field-examples';
export * from './popover-examples';
export * from './preview-card-examples';
export * from './radio-examples';
Expand Down
34 changes: 34 additions & 0 deletions apps/www/src/components/playground/number-field-examples.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client';

import { Flex, NumberField, Text } from '@raystack/apsara';
import PlaygroundLayout from './playground-layout';

export function NumberFieldExamples() {
return (
<PlaygroundLayout title='Number Field'>
<Flex direction='column' gap='large'>
<Text>Default:</Text>
<NumberField defaultValue={0} />

<Text>With Min/Max (0-10):</Text>
<NumberField defaultValue={5} min={0} max={10} />

<Text>With Step (5):</Text>
<NumberField defaultValue={0} step={5} />

<Text>Disabled:</Text>
<NumberField defaultValue={0} disabled />

<Text>Composed with ScrubArea:</Text>
<NumberField defaultValue={50}>
<NumberField.ScrubArea label='Amount' />
<NumberField.Group>
<NumberField.Decrement />
<NumberField.Input />
<NumberField.Increment />
</NumberField.Group>
</NumberField>
</Flex>
</PlaygroundLayout>
);
}
59 changes: 59 additions & 0 deletions apps/www/src/content/docs/components/number-field/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use client';

import { getPropsString } from '@/lib/utils';

export const getCode = (props: any) => {
return `<NumberField${getPropsString(props)}/>`;
};
Comment on lines +5 to +7
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

getCode signature does not match the required GetCodeType interface.

According to apps/www/src/components/demo/types.ts, GetCodeType requires two parameters: (updatedProps: Record<string, any>, props: Record<string, any>). The current implementation only accepts one parameter, which will cause the playground to malfunction when generating code snippets.

🐛 Proposed fix
-export const getCode = (props: any) => {
-  return `<NumberField${getPropsString(props)}/>`;
+export const getCode = (updatedProps: Record<string, any>, props: Record<string, any>) => {
+  return `<NumberField${getPropsString(updatedProps)}/>`;
 };
📝 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 const getCode = (props: any) => {
return `<NumberField${getPropsString(props)}/>`;
};
export const getCode = (updatedProps: Record<string, any>, props: Record<string, any>) => {
return `<NumberField${getPropsString(updatedProps)}/>`;
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/number-field/demo.ts` around lines 5 -
7, The getCode function signature currently accepts a single parameter but must
match the GetCodeType interface by accepting two parameters (updatedProps:
Record<string, any>, props: Record<string, any>); change export const getCode to
(updatedProps, props) and use updatedProps when building the props string (i.e.,
pass updatedProps to getPropsString) so the playground receives the updated
properties while preserving any use of props elsewhere in the function.


export const playground = {
type: 'playground',
controls: {
defaultValue: { type: 'text', initialValue: '0' },
min: { type: 'text', initialValue: '' },
max: { type: 'text', initialValue: '' },
step: { type: 'text', initialValue: '1' },
disabled: { type: 'checkbox', initialValue: false, defaultValue: false }
},
getCode
};

export const basicDemo = {
type: 'code',
code: `<NumberField defaultValue={0} />`
};

export const minMaxDemo = {
type: 'code',
code: `<NumberField defaultValue={5} min={0} max={10} />`
};

export const stepDemo = {
type: 'code',
code: `<NumberField defaultValue={0} step={5} />`
};

export const disabledDemo = {
type: 'code',
code: `<NumberField defaultValue={0} disabled />`
};

export const composedDemo = {
type: 'code',
code: `<NumberField defaultValue={0}>
<NumberField.ScrubArea label="Amount" />
<NumberField.Group>
<NumberField.Decrement />
<NumberField.Input />
<NumberField.Increment />
</NumberField.Group>
</NumberField>`
};

export const formatDemo = {
type: 'code',
code: `<NumberField
defaultValue={1000}
format={{ style: 'currency', currency: 'USD' }}
/>`
};
123 changes: 123 additions & 0 deletions apps/www/src/content/docs/components/number-field/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
---
title: Number Field
description: A numeric input component with increment and decrement buttons, supporting scrub interaction.
source: packages/raystack/components/number-field
tag: new
---

import {
playground,
basicDemo,
minMaxDemo,
stepDemo,
disabledDemo,
composedDemo,
formatDemo,
} from "./demo.ts";

<Demo data={playground} />

## Anatomy

Import and use the component standalone or composed:

```tsx
import { NumberField } from "@raystack/apsara";

{/* Standalone — renders decrement, input, and increment internally */}
<NumberField defaultValue={0} />

{/* Composed — full control over sub-components */}
<NumberField defaultValue={0}>
<NumberField.ScrubArea label="Amount" />
<NumberField.Group>
<NumberField.Decrement />
<NumberField.Input />
<NumberField.Increment />
</NumberField.Group>
</NumberField>
```

## API Reference

### Root

The root component that manages numeric state. When no children are provided, it renders a default group with decrement, input, and increment controls.

<auto-type-table path="./props.ts" name="NumberFieldProps" />

### Group

Groups the input and button controls together.

<auto-type-table path="./props.ts" name="NumberFieldGroupProps" />

### Input

The numeric input element.

<auto-type-table path="./props.ts" name="NumberFieldInputProps" />

### Decrement

Button to decrease the value.

<auto-type-table path="./props.ts" name="NumberFieldDecrementProps" />

### Increment

Button to increase the value.

<auto-type-table path="./props.ts" name="NumberFieldIncrementProps" />

### ScrubArea

An interactive area that allows adjusting the value by dragging. Renders a Label component internally.

<auto-type-table path="./props.ts" name="NumberFieldScrubAreaProps" />

## Examples

### Basic

A standalone number field with default controls.

<Demo data={basicDemo} />

### Min / Max

Number field constrained to a range.

<Demo data={minMaxDemo} />

### Step

Number field with a custom step amount.

<Demo data={stepDemo} />

### Disabled

Number field in disabled state.

<Demo data={disabledDemo} />

### Composed with ScrubArea

Full control with scrub area for drag-to-adjust interaction.

<Demo data={composedDemo} />

### Formatted

Number field with currency formatting.

<Demo data={formatDemo} />

## Accessibility

- The input has `role="textbox"` with `aria-roledescription="Number field"`
- Increment and decrement buttons are keyboard accessible
- ScrubArea associates its label with the input via `htmlFor`
- Supports `aria-valuemin`, `aria-valuemax`, and `aria-valuenow`
- Keyboard: Arrow Up/Down to increment/decrement, Shift for large step, Meta for small step
Comment on lines +117 to +123
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:

#!/bin/bash
# Check the actual role and ARIA attributes in the component implementation
rg -n "role|aria-" packages/raystack/components/number-field/

# Check tests to see what role is asserted
rg -n "getByRole" packages/raystack/components/number-field/__tests__/

Repository: raystack/apsara

Length of output: 1332


🏁 Script executed:

#!/bin/bash
# Search for aria-value attributes specifically
rg -n "aria-value" packages/raystack/components/number-field/

# Also check the entire number-field component file
wc -l packages/raystack/components/number-field/number-field.tsx

Repository: raystack/apsara

Length of output: 121


🏁 Script executed:

cat -n packages/raystack/components/number-field/number-field.tsx

Repository: raystack/apsara

Length of output: 5531


🏁 Script executed:

# Check the MDX file to see the exact content at lines 117-123
sed -n '117,123p' apps/www/src/content/docs/components/number-field/index.mdx

Repository: raystack/apsara

Length of output: 429


🏁 Script executed:

# Check if aria-roledescription is mentioned anywhere in the component
rg -n "aria-roledescription" packages/raystack/components/number-field/

# Check Base UI documentation or types to see what ARIA attributes are supported
rg -n "aria-roledescription" --type ts --type tsx

Repository: raystack/apsara

Length of output: 86


🏁 Script executed:

# Check if aria-roledescription is mentioned anywhere in the component
rg -n "aria-roledescription"

Repository: raystack/apsara

Length of output: 198


Remove unsupported ARIA attributes from accessibility documentation.

The documentation claims the input supports aria-roledescription="Number field" and aria-valuemin, aria-valuemax, aria-valuenow, but the component does not implement any of these attributes. Only role="textbox" is actually applied. Remove lines referencing the unsupported ARIA attributes from the accessibility section, or update the component implementation to add them if they are intended.

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

In `@apps/www/src/content/docs/components/number-field/index.mdx` around lines 117
- 123, The Accessibility doc incorrectly claims support for
aria-roledescription="Number field" and
aria-valuemin/aria-valuemax/aria-valuenow even though the component only applies
role="textbox"; remove the lines that reference these unsupported ARIA
attributes from the Accessibility section (i.e., delete the bullets mentioning
aria-roledescription and aria-valuemin/aria-valuemax/aria-valuenow) or
alternatively implement those attributes on the input if they are
intended—ensure any change targets the Accessibility section text that currently
lists role="textbox" and the ARIA attributes.

89 changes: 89 additions & 0 deletions apps/www/src/content/docs/components/number-field/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
export interface NumberFieldProps {
/** Controlled numeric value. */
value?: number | null;

/**
* Initial uncontrolled value.
* @defaultValue 0
*/
defaultValue?: number;

/** Event handler called when the value changes. */
onValueChange?: (value: number | null, event: Event) => void;

/** Minimum allowed value. */
min?: number;

/** Maximum allowed value. */
max?: number;

/**
* Step amount for increment/decrement.
* @defaultValue 1
*/
step?: number;

/**
* Whether the field is disabled.
* @defaultValue false
*/
disabled?: boolean;

/**
* Whether the field is read-only.
* @defaultValue false
*/
readOnly?: boolean;

/**
* Whether the field is required.
* @defaultValue false
*/
required?: boolean;

/** Number formatting options (Intl.NumberFormatOptions). */
format?: Intl.NumberFormatOptions;

/** Additional CSS class names. */
className?: string;
}

export interface NumberFieldGroupProps {
/** Additional CSS class names. */
className?: string;
}

export interface NumberFieldInputProps {
/** Additional CSS class names. */
className?: string;
}

export interface NumberFieldDecrementProps {
/** Custom content for the decrement button. Defaults to a minus icon. */
children?: React.ReactNode;

/** Additional CSS class names. */
className?: string;
}

export interface NumberFieldIncrementProps {
/** Custom content for the increment button. Defaults to a plus icon. */
children?: React.ReactNode;

/** Additional CSS class names. */
className?: string;
}

export interface NumberFieldScrubAreaProps {
/** Label text displayed in the scrub area. */
label: string;

/**
* Scrub direction.
* @defaultValue "horizontal"
*/
direction?: 'horizontal' | 'vertical';

/** Additional CSS class names. */
className?: string;
}
Loading
Loading