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
18 changes: 13 additions & 5 deletions .agents/skills/add-new-component/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,13 @@ Key rules:

## Step 2: Create the Index File

Simple re-export:
Re-export the component and its props types:

```tsx
// packages/react/components/<name>/index.tsx
export { Component } from './<name>';
export type { ComponentContentProps } from './<name>-content';
export type { ComponentRootProps as ComponentProps } from './<name>-root';
```

## Step 3: Add CSS Module Styles
Expand Down Expand Up @@ -239,10 +241,12 @@ Common `--rs-*` tokens:
Add to `packages/react/index.tsx` in **alphabetical order**:

```tsx
export { Chip } from './components/chip';
export { CodeBlock } from './components/code-block';
export { Collapsible } from './components/collapsible'; // <-- new
export { Combobox } from './components/combobox';
export { Chip, type ChipProps } from './components/chip';
export {
Component,
type ComponentContentProps,
type ComponentProps
} from './components/<name>'; // <-- new
```

## Step 5: Write Tests
Expand Down Expand Up @@ -496,8 +500,11 @@ Notes:
pnpm --filter @raystack/apsara build
pnpm --filter @raystack/apsara test -- --reporter=verbose components/<name>
pnpm --filter www build
grep -n "from './components/<name>'" packages/react/dist/index.d.ts
```

The `grep` prints the root export of the component. Check that it lists the props type of every part that declares its own type. Type-only exports are erased at runtime, so the tests and the build still pass when one is missing. Parts that take Base UI or HTML props unchanged have no type to export.

Checklist:
- [ ] Component builds without errors
- [ ] All tests pass
Expand All @@ -506,4 +513,5 @@ Checklist:
- [ ] Every rendered element has a `data-slot`, with a `data-slots.test.tsx` covering them
- [ ] CSS uses `--rs-*` tokens only
- [ ] Export in `packages/react/index.tsx` in alphabetical order
- [ ] Props types exported from the component index and `packages/react/index.tsx`, and listed in `dist/index.d.ts`
- [ ] Interactive `playground` added to `demo.ts`, covering the component's main props
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Skills are in `.agents/skills/`. `.claude/skills` is a symlink to that folder, s
## Code

- Each component is in `packages/react/components/<name>/`. Its `index.tsx` only re-exports.
- Export new components from `packages/react/index.tsx`, in alphabetical order.
- Export new components from `packages/react/index.tsx`, in alphabetical order, with their props types: `<Component>Props` for the root part and `<Component><Part>Props` for each part with its own type.
- Wrap Base UI primitives, for example `import { Tabs as TabsPrimitive } from '@base-ui/react'`. Do not rebuild behavior that Base UI already has.
- For plain elements, use `useRender` and `mergeProps` so the `render` prop works.
- Pass `ref` as a normal prop (React 19). Do not use `forwardRef`.
Expand Down
14 changes: 14 additions & 0 deletions apps/www/src/content/docs/(overview)/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,20 @@ function ThemeToggle() {
}
```

## Importing prop types

Components export their props types from the main entry, for example `ButtonProps`, `TabsProps` and `TabsTabProps`:

```tsx
import { Button, type ButtonProps } from "@raystack/apsara";

function SaveButton({ saving, ...props }: ButtonProps & { saving: boolean }) {
return <Button {...props} loading={saving} loaderText="Saving" />;
}
```

Some parts, such as `Tabs.List`, take Base UI or HTML props unchanged and have no type of their own. Use `ComponentProps<typeof Tabs.List>` from React for them.

## Next steps

- [Theme Overview](/docs/theme/overview): tokens, the seven settings, and nested scopes
Expand Down
1 change: 1 addition & 0 deletions packages/react/components/accordion/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { Accordion } from './accordion';
export type { AccordionRootProps as AccordionProps } from './accordion-root';
1 change: 1 addition & 0 deletions packages/react/components/alert-dialog/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { AlertDialog } from './alert-dialog';
export type { AlertDialogContentProps } from './alert-dialog-content';
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const announcementBar = cva(styles['announcement-bar'], {
}
});

type AnnouncementBarProps = VariantProps<typeof announcementBar> & {
export type AnnouncementBarProps = VariantProps<typeof announcementBar> & {
leadingIcon?: ReactNode;
className?: string;
text: ReactNode;
Expand Down
2 changes: 1 addition & 1 deletion packages/react/components/announcement-bar/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { AnnouncementBar } from './announcement-bar';
export { AnnouncementBar, type AnnouncementBarProps } from './announcement-bar';
7 changes: 6 additions & 1 deletion packages/react/components/avatar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export { Avatar, AvatarGroup } from './avatar';
export {
Avatar,
AvatarGroup,
type AvatarGroupProps,
type AvatarProps
} from './avatar';
export {
AVATAR_COLORS,
type AvatarColor,
Expand Down
2 changes: 1 addition & 1 deletion packages/react/components/badge/badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const badge = cva(styles['badge'], {
}
});

type BadgeProps = VariantProps<typeof badge> &
export type BadgeProps = VariantProps<typeof badge> &
ComponentProps<'span'> & {
icon?: ReactNode;
screenReaderText?: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/react/components/badge/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { Badge } from './badge';
export { Badge, type BadgeProps } from './badge';
6 changes: 6 additions & 0 deletions packages/react/components/breadcrumb/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
export { Breadcrumb } from './breadcrumb';
export type { BreadcrumbItemProps } from './breadcrumb-item';
export type {
BreadcrumbEllipsisProps,
BreadcrumbSeparatorProps
} from './breadcrumb-misc';
export type { BreadcrumbProps } from './breadcrumb-root';
2 changes: 1 addition & 1 deletion packages/react/components/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const getLoaderOnlyClass = (size: 'small' | 'normal' | null) =>
? styles['loader-only-button-small']
: styles['loader-only-button-normal'];

type ButtonProps = VariantProps<typeof button> &
export type ButtonProps = VariantProps<typeof button> &
ButtonPrimitive.Props & {
loading?: boolean;
loaderText?: ReactNode;
Expand Down
2 changes: 1 addition & 1 deletion packages/react/components/button/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { Button } from './button';
export { Button, type ButtonProps } from './button';
6 changes: 5 additions & 1 deletion packages/react/components/calendar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ export type { CalendarProps, CalendarPropsExtended } from './calendar';
export { Calendar } from './calendar';
export type { DatePickerProps, DatePickerSlotProps } from './date-picker';
export { DatePicker } from './date-picker';
export { RangePicker } from './range-picker';
export {
RangePicker,
type RangePickerProps,
type RangePickerSlotProps
} from './range-picker';
4 changes: 2 additions & 2 deletions packages/react/components/calendar/range-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ import { usePickerPopover } from './use-picker-popover';
*/
type RangePickerCalendarSlot = Omit<PropsBase, 'mode'> & CalendarPropsExtended;

interface RangePickerSlotProps {
export interface RangePickerSlotProps {
startInput?: InputProps;
endInput?: InputProps;
calendar?: RangePickerCalendarSlot;
popover?: PopoverContentProps;
}

interface RangePickerProps {
export interface RangePickerProps {
dateFormat?: string;
/**
* Props for each picker slot. When both this and the legacy
Expand Down
2 changes: 1 addition & 1 deletion packages/react/components/callout/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { Callout } from './callout';
export { Callout, type CalloutProps } from './callout';
7 changes: 6 additions & 1 deletion packages/react/components/chat-panel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ export type {
ChatPanelSide
} from './chat-panel-context';
export type {
ChatPanelActionsProps,
ChatPanelContentProps,
ChatPanelExpandTriggerProps,
ChatPanelExpandTriggerState
ChatPanelExpandTriggerState,
ChatPanelHeaderProps,
ChatPanelMinimizeTriggerProps,
ChatPanelTitleProps
} from './chat-panel-parts';
export type {
ChatPanelDragBoundary,
Expand Down
12 changes: 9 additions & 3 deletions packages/react/components/chat/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
export { Chat } from './chat';
export type { ChatAttachmentState } from './chat-attachment';
export { Chat, type ChatRootProps as ChatProps } from './chat';
export type {
ChatAttachmentProps,
ChatAttachmentState
} from './chat-attachment';
export type { ChatComposerProps } from './chat-composer';
export {
type ChatMessagesActions,
type UseChatMessagesReturn,
useChatMessages
} from './chat-context';
export type { ChatJumpButtonProps } from './chat-messages';
export type { ChatItemProps } from './chat-item';
export type { ChatJumpButtonProps, ChatMessagesProps } from './chat-messages';
export type { ChatSeparatorProps } from './chat-separator';
4 changes: 2 additions & 2 deletions packages/react/components/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const checkboxVariants = cva(styles.checkbox, {
}
});

interface CheckboxGroupProps extends CheckboxGroupPrimitive.Props {
export interface CheckboxGroupProps extends CheckboxGroupPrimitive.Props {
/** Layout direction of the checkbox group.
* @defaultValue 'vertical'
*/
Expand All @@ -85,7 +85,7 @@ function CheckboxGroup({

CheckboxGroup.displayName = 'Checkbox.Group';

interface CheckboxItemProps
export interface CheckboxItemProps
extends CheckboxPrimitive.Root.Props,
VariantProps<typeof checkboxVariants> {}

Expand Down
6 changes: 5 additions & 1 deletion packages/react/components/checkbox/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export { Checkbox } from './checkbox';
export {
Checkbox,
type CheckboxGroupProps,
type CheckboxItemProps as CheckboxProps
} from './checkbox';
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import { SingleSelectProps } from '../select/select-root';
import styles from './code-block.module.css';
import { useCodeBlockContext } from './code-block-root';

export const CodeBlockLanguageSelect = (props: SingleSelectProps) => {
export type CodeBlockLanguageSelectProps = SingleSelectProps;

export const CodeBlockLanguageSelect = (
props: CodeBlockLanguageSelectProps
) => {
const { value, setValue } = useCodeBlockContext();

const handleValueChange = (value: string) => {
Expand Down
7 changes: 7 additions & 0 deletions packages/react/components/code-block/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
export { CodeBlock } from './code-block';
export type { CodeBlockCodeProps } from './code-block-code';
export type { CodeBlockLanguageSelectProps } from './code-block-language-select';
export type {
CodeBlockCollapseTriggerProps,
CodeBlockCopyButtonProps
} from './code-block-misc';
export type { CodeBlockProps } from './code-block-root';
6 changes: 6 additions & 0 deletions packages/react/components/color-picker/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
export { ColorPicker } from './color-picker';
export type { ColorPickerAlphaProps } from './color-picker-alpha';
export type { ColorPickerAreaProps } from './color-picker-area';
export type { ColorPickerHueProps } from './color-picker-hue';
export type { ColorPickerInputProps } from './color-picker-input';
export type { ColorPickerModeProps } from './color-picker-mode';
export type { ColorPickerProps } from './color-picker-root';
export { formatColor } from './utils';
4 changes: 4 additions & 0 deletions packages/react/components/combobox/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export { Combobox } from './combobox';
export type { ComboboxContentProps } from './combobox-content';
export type { ComboboxInputProps } from './combobox-input';
export type { ComboboxItemProps } from './combobox-item';
export type { ComboboxRootProps as ComboboxProps } from './combobox-root';
6 changes: 4 additions & 2 deletions packages/react/components/command/command-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { Autocomplete as AutocompletePrimitive } from '@base-ui/react/autocomple
import { cx } from 'class-variance-authority';
import styles from './command.module.css';

export type CommandContentProps = AutocompletePrimitive.List.Props;
export type CommandContentProps = AutocompletePrimitive.List.Props & {
ref?: React.Ref<HTMLDivElement>;
};

export const CommandContent = ({
ref,
className,
...props
}: CommandContentProps & { ref?: React.Ref<HTMLDivElement> }) => {
}: CommandContentProps) => {
return (
<AutocompletePrimitive.List
ref={ref}
Expand Down
2 changes: 1 addition & 1 deletion packages/react/components/command/command-empty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const CommandEmpty = ({
ref,
className,
...props
}: CommandEmptyProps & { ref?: React.Ref<HTMLDivElement> }) => {
}: CommandEmptyProps) => {
return (
<div
ref={ref}
Expand Down
11 changes: 11 additions & 0 deletions packages/react/components/command/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
export { Command } from './command';
export type { CommandContentProps } from './command-content';
export type { CommandDialogContentProps } from './command-dialog';
export type { CommandEmptyProps } from './command-empty';
export type { CommandInputProps } from './command-input';
export type { CommandItemProps } from './command-item';
export type {
CommandGroupProps,
CommandLabelProps,
CommandSeparatorProps
} from './command-misc';
export type { CommandRootProps as CommandProps } from './command-root';
2 changes: 1 addition & 1 deletion packages/react/components/container/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const container = cva(styles.container, {
}
});

type ContainerProps = VariantProps<typeof container> &
export type ContainerProps = VariantProps<typeof container> &
useRender.ComponentProps<'div'>;

export function Container({
Expand Down
2 changes: 1 addition & 1 deletion packages/react/components/container/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { Container } from './container';
export { Container, type ContainerProps } from './container';
14 changes: 14 additions & 0 deletions packages/react/components/context-menu/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
export { ContextMenu } from './context-menu';
export type { ContextMenuContentProps } from './context-menu-content';
export type { ContextMenuItemProps } from './context-menu-item';
export type {
ContextMenuGroupProps,
ContextMenuLabelProps
} from './context-menu-misc';
export type {
ContextMenuRootProps as ContextMenuProps,
ContextMenuSubMenuProps
} from './context-menu-root';
export type {
ContextMenuSubTriggerProps,
ContextMenuTriggerProps
} from './context-menu-trigger';
2 changes: 1 addition & 1 deletion packages/react/components/copy-button/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { CopyButton } from './copy-button';
export { CopyButton, type CopyButtonProps } from './copy-button';
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { DisplayProperties } from './display-properties';
import { Grouping } from './grouping';
import { Ordering } from './ordering';

interface DisplaySettingsProps {
export interface DataTableDisplayControlsProps {
trigger?: ReactNode;
classNames?: { content?: string };
}
Expand All @@ -34,7 +34,7 @@ export function DisplaySettings<TData, TValue>({
</Button>
),
classNames
}: DisplaySettingsProps) {
}: DataTableDisplayControlsProps) {
const {
table,
updateTableQuery,
Expand Down
14 changes: 8 additions & 6 deletions packages/react/components/data-table/components/filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,18 +88,20 @@ function AddFilter<TData, TValue>({
) : null;
}

export function Filters<TData, TValue>({
classNames,
className,
trigger
}: {
export interface DataTableFiltersProps<TData, TValue> {
classNames?: {
filterChips?: string;
addFilter?: string;
};
className?: string;
trigger?: Trigger<TData, TValue>;
}) {
}

export function Filters<TData, TValue>({
classNames,
className,
trigger
}: DataTableFiltersProps<TData, TValue>) {
const { table, tableQuery } = useDataTable();
const columns = table?.getAllColumns() as DataTableColumn<TData, TValue>[];

Expand Down
6 changes: 5 additions & 1 deletion packages/react/components/data-table/components/toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { useDataTable } from '../hooks/useDataTable';
import { DisplaySettings } from './display-settings';
import { Filters } from './filters';

export function Toolbar<TData, TValue>({ className }: { className?: string }) {
export interface DataTableToolbarProps {
className?: string;
}

export function Toolbar<TData, TValue>({ className }: DataTableToolbarProps) {
const { shouldShowFilters = false } = useDataTable();

if (!shouldShowFilters) {
Expand Down
Loading
Loading