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
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ export const NavAccountMenu = ({
className,
selectedAccountId = "olivia",
...dialogProps
}: AriaDialogProps & { className?: string; accounts?: NavAccountType[]; selectedAccountId?: string }) => {
}: AriaDialogProps & {
/** Additional classes for the popover dialog that wraps the menu. */
className?: string;
/** The accounts offered in the "Switch account" section. Accepted for parity with `NavAccountCard`, which passes its own list down; the menu itself always renders the built-in demo accounts. */
accounts?: NavAccountType[];
/** The `id` of the account to present as the current one — its row is highlighted and its radio button is selected. */
selectedAccountId?: string;
}) => {
const focusManager = useFocusManager();
const dialogRef = useRef<HTMLDivElement>(null);

Expand Down Expand Up @@ -128,8 +135,11 @@ const NavAccountCardMenuItem = ({
shortcut,
...buttonProps
}: {
/** Icon rendered at the leading edge of the item, before the label. */
icon?: FC<{ className?: string }>;
/** Text of the menu item. */
label: string;
/** Keyboard shortcut shown as a `kbd` badge at the trailing edge. Display only — the item does not bind the key combination. */
shortcut?: string;
} & HTMLAttributes<HTMLButtonElement>) => {
return (
Expand Down Expand Up @@ -159,9 +169,16 @@ export const NavAccountCard = ({
items = placeholderAccounts,
avatarRounded,
}: {
/**
* Where the account menu opens relative to the card.
* @default "right bottom" from the `lg` breakpoint up, "top right" below it
*/
popoverPlacement?: AriaPlacement;
/** The `id` of the account from `items` to show in the card. Nothing is rendered if no item matches, and a warning is logged. */
selectedAccountId?: string;
/** The accounts the card can display. The one matching `selectedAccountId` fills the card, and the list is handed to the account menu. */
items?: NavAccountType[];
/** Whether the account avatar is a circle. Pass `false` for a rounded square. */
avatarRounded?: boolean;
}) => {
const triggerRef = useRef<HTMLDivElement>(null);
Expand Down
2 changes: 2 additions & 0 deletions components/application/carousel/carousel-base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,9 @@ const CarouselIndicator = ({ index, isSelected = false, children, asChild, class
};

interface CarouselIndicatorGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
/** The indicators to render. Can be a render prop, which is called once per scroll snap with its index. */
children: ReactNode | ((props: { index: number }) => ReactNode);
/** The class name applied to the indicator group. */
className?: string;
}

Expand Down
18 changes: 17 additions & 1 deletion components/application/charts/charts-base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,18 @@ export const selectEvenlySpacedItems = <T extends readonly unknown[]>(dataArray:
* @param className - The class name of the legend.
* @returns The legend content.
*/
export const ChartLegendContent = ({ reversed, payload, align, layout, className }: LegendContentProps & { reversed?: boolean; className?: string }) => {
export const ChartLegendContent = ({
reversed,
payload,
align,
layout,
className,
}: LegendContentProps & {
/** Whether the legend entries are listed in the reverse of the order Recharts supplies them, so a stacked chart's legend reads in the same order as its stack. */
reversed?: boolean;
/** Additional classes for the legend list, merged after the layout and alignment classes derived from the Recharts `Legend` props. */
className?: string;
}) => {
payload = reversed && payload ? [...payload].reverse() : payload;

return (
Expand Down Expand Up @@ -74,9 +85,13 @@ export const ChartLegendContent = ({ reversed, payload, align, layout, className
};

interface ChartTooltipContentProps extends TooltipProps<ValueType, NameType> {
/** Whether the tooltip belongs to a Recharts `RadialBarChart`. When a single point is hovered, the supporting line is read from `payload[0].payload.name` instead of the axis label. */
isRadialChart?: boolean;
/** Whether the tooltip belongs to a Recharts `PieChart`. When a single slice is hovered, the supporting line is the slice's `name` instead of the axis label. */
isPieChart?: boolean;
/** The category label Recharts passes for the hovered position. Used as the tooltip heading when several series are active, and as the supporting line otherwise. */
label?: string;
/** The active data points Recharts passes to the `Tooltip` content. Each entry supplies the series name and value rendered as a row in the tooltip. */
// We have to use `any` here because the `payload` prop is not typed correctly in the `recharts` library.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
payload?: any;
Expand Down Expand Up @@ -124,6 +139,7 @@ export const ChartTooltipContent = ({ active, payload, label, isRadialChart, isP
};

interface ChartActiveDotProps extends DotProps {
/** The data point Recharts passes to the `activeDot` renderer. Accepted so the dot can be dropped straight into a `Line` or `Area`; the marker itself is drawn from `cx` and `cy` only. */
// We have to use `any` here because the `payload` prop is not typed correctly in the `recharts` library.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
payload?: any;
Expand Down
1 change: 1 addition & 0 deletions components/application/date-picker/date-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface DatePickerProps extends AriaDatePickerProps<DateValue> {
onApply?: () => void;
/** The function to call when the cancel button is clicked. */
onCancel?: () => void;
/** Controls the size of the trigger button that opens the calendar. */
size?: ButtonProps["size"];
}

Expand Down
1 change: 1 addition & 0 deletions components/application/date-picker/date-range-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const now = today(getLocalTimeZone());
const highlightedDates = [today(getLocalTimeZone())];

interface DateRangePickerProps extends AriaDateRangePickerProps<DateValue> {
/** Controls the size of the trigger button that opens the calendar. */
size?: ButtonProps["size"];
/** The function to call when the apply button is clicked. */
onApply?: () => void;
Expand Down
1 change: 1 addition & 0 deletions components/application/date-picker/range-calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const RangeCalendarTitle = ({ part }: { part: "start" | "end" }) => {
};

interface RangePresetButtonProps extends HTMLAttributes<HTMLButtonElement> {
/** The date range applied when the preset is pressed. The button is highlighted while this range is the selected one. */
value: { start: DateValue; end: DateValue };
}

Expand Down
9 changes: 9 additions & 0 deletions components/application/empty-state/empty-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Illustration as Illustrations } from "@/components/shared-assets/illust
import { cx } from "@/utils/cx";

interface RootContextProps {
/** Scales the whole empty state — the header icon or illustration, the heading and description type, the avatar decorations, and the spacing between the sections. Read by every sub-component through context. */
size?: "sm" | "md" | "lg";
}

Expand Down Expand Up @@ -47,7 +48,9 @@ const Illustration = ({ type = "cloud", color = "gray", size = "lg", ...props }:
};

interface FileTypeIconProps extends ComponentPropsWithRef<"div"> {
/** Which file type the icon depicts, forwarded to `FileIcon` from `@untitledui/file-icons`. */
type?: ComponentProps<typeof FileIcon>["type"];
/** Drawing style of the file icon, forwarded to `FileIcon` as its `variant`. */
theme?: ComponentProps<typeof FileIcon>["variant"];
}

Expand All @@ -60,7 +63,9 @@ const FileTypeIcon = ({ type = "folder", theme = "solid", ...props }: FileTypeIc
};

interface HeaderProps extends ComponentPropsWithRef<"div"> {
/** Decorative background pattern drawn behind the header's icon or illustration and centred on it. Pass `"none"` to render the header without a pattern. */
pattern?: "none" | BackgroundPatternProps["pattern"];
/** How far the decorative background pattern extends behind the header content. */
patternSize?: "sm" | "md" | "lg";
}

Expand Down Expand Up @@ -124,6 +129,7 @@ const Description = (props: ComponentPropsWithRef<"p">) => {
};

interface AvatarRadiusProps extends ComponentPropsWithRef<"div"> {
/** Avatars scattered across the concentric rings behind the empty state. Each one takes the next predefined ring and angle, so at most nine are drawn and any extras are ignored. */
avatars?: Array<{ src: string; alt?: string }>;
}

Expand Down Expand Up @@ -188,7 +194,9 @@ const AvatarRadius = ({ avatars = [], ...props }: AvatarRadiusProps) => {
};

interface AvatarRowProps extends ComponentPropsWithRef<"div"> {
/** Avatars fanned out either side of the children, growing towards the centre. The first few fill the left group and the next few the right — three or four per side depending on the root size — and any extras are ignored. */
avatars?: Array<{ src: string; alt?: string }>;
/** Content placed between the two avatar groups, typically the empty state's featured icon or illustration. */
children?: ReactNode;
}

Expand Down Expand Up @@ -244,6 +252,7 @@ const AvatarRow = ({ avatars = [], children, ...props }: AvatarRowProps) => {
};

interface AvatarGridProps extends ComponentPropsWithRef<"div"> {
/** Avatars split into two marquee rows that scroll in opposite directions behind the empty state. The first half of the list fills the top row and the rest the bottom row, each repeated to loop seamlessly. */
avatars?: Array<{ src: string; alt?: string }>;
}

Expand Down
5 changes: 5 additions & 0 deletions components/application/file-upload/draggable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import { FileIcon } from "@untitledui/file-icons";
import { motion, useMotionValue, useSpring } from "motion/react";

interface DraggableProps {
/** The file name shown under the icon and used for the simulated `File` that is dropped. */
name: string;
/** The MIME type of the simulated file. Also used as the file icon type when `fileIconType` is not set. */
type: string;
/** Overrides which file icon is shown, when it should differ from the MIME `type`. */
fileIconType?: ComponentProps<typeof FileIcon>["type"];
/** The visual variant of the file icon. */
theme?: ComponentProps<typeof FileIcon>["variant"];
/** The size of the simulated file in bytes. That many random bytes are generated on drop. */
size: number;
}

Expand Down
6 changes: 6 additions & 0 deletions components/application/pagination/pagination-base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface PaginationRootProps {
page: number;
/** Total number of pages */
total: number;
/** The pagination parts to render, such as `Pagination.PrevTrigger`, `Pagination.Context` and `Pagination.NextTrigger`. */
children: ReactNode;
/** The style of the pagination root. */
style?: CSSProperties;
Expand Down Expand Up @@ -334,9 +335,13 @@ const PaginationItem = ({ value, isCurrent, children, style, className, ariaLabe
);
};
interface PaginationEllipsisProps {
/** The identifier of the ellipsis within the generated list of pagination items. */
key: number;
/** The content rendered in place of the skipped pages, such as an ellipsis character. Hidden from assistive technology. */
children?: ReactNode;
/** The style of the ellipsis. */
style?: CSSProperties;
/** The class name of the ellipsis. Can be a function returning the class name. */
className?: string | (() => string);
}

Expand All @@ -351,6 +356,7 @@ const PaginationEllipsis: FC<PaginationEllipsisProps> = ({ children, style, clas
};

interface PaginationContextComponentProps {
/** A render prop called with the pagination state (pages, current page, total and the page change handler). */
children: (pagination: PaginationContextType) => ReactNode;
}

Expand Down
13 changes: 12 additions & 1 deletion components/application/pagination/pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,18 @@ interface PaginationProps extends Partial<Omit<PaginationRootProps, "children">>
rounded?: boolean;
}

const PaginationItem = ({ value, rounded, isCurrent }: { value: number; rounded?: boolean; isCurrent: boolean }) => {
const PaginationItem = ({
value,
rounded,
isCurrent,
}: {
/** The page number this item navigates to, also used as its label. */
value: number;
/** Whether the button is a circle rather than a rounded square. */
rounded?: boolean;
/** Whether this item is the page currently being viewed, which highlights it and marks it as the current page. */
isCurrent: boolean;
}) => {
return (
<Pagination.Item
value={value}
Expand Down
3 changes: 3 additions & 0 deletions components/application/slideout-menus/slideout-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ export const Dialog = (props: DialogProps) => (
Dialog.displayName = "Dialog";

interface SlideoutMenuProps extends Omit<AriaModalOverlayProps, "children">, RefAttributes<HTMLDivElement> {
/** The content of the menu. Can be a render prop that receives the modal state along with a `close` function. */
children: ReactNode | ((children: AriaModalRenderProps & { close: () => void }) => ReactNode);
/** The class name applied to the inner dialog, while `className` styles the sliding panel. */
dialogClassName?: string;
}

Expand All @@ -89,6 +91,7 @@ const Content = ({ role = "main", ...props }: ComponentPropsWithRef<"div">) => {
Content.displayName = "SlideoutContent";

interface SlideoutHeaderProps extends ComponentPropsWithRef<"header"> {
/** Handler called when the close button in the top right corner is pressed. */
onClose?: () => void;
}

Expand Down
19 changes: 18 additions & 1 deletion components/application/table/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,15 @@ export const TableRowActionsDropdown = () => (

const TableContext = createContext<{ size: "sm" | "md" }>({ size: "md" });

const TableCardRoot = ({ children, className, size = "md", ...props }: HTMLAttributes<HTMLDivElement> & { size?: "sm" | "md" }) => {
const TableCardRoot = ({
children,
className,
size = "md",
...props
}: HTMLAttributes<HTMLDivElement> & {
/** Controls the density of the card and every descendant header, row and cell. */
size?: "sm" | "md";
}) => {
return (
<TableContext.Provider value={{ size }}>
<div {...props} className={cx("overflow-hidden rounded-xl bg-primary shadow-xs ring-1 ring-secondary", className)}>
Expand Down Expand Up @@ -104,6 +112,7 @@ const TableCardHeader = ({ title, badge, description, contentTrailing, className
};

interface TableRootProps extends AriaTableProps, Omit<ComponentPropsWithRef<"table">, "className" | "slot" | "style"> {
/** Controls the density of the header, rows and cells. The size of a wrapping `TableCard.Root` takes precedence. */
size?: "sm" | "md";
}

Expand All @@ -122,7 +131,9 @@ TableRoot.displayName = "Table";

interface TableHeaderProps<T extends object>
extends AriaTableHeaderProps<T>, Omit<ComponentPropsWithRef<"thead">, "children" | "className" | "slot" | "style"> {
/** Whether to draw a hairline border along the bottom edge of the header row. */
bordered?: boolean;
/** Controls the height of the header row. Falls back to the size inherited from the table. */
size?: "sm" | "md";
}

Expand Down Expand Up @@ -165,7 +176,9 @@ const TableHeader = <T extends object>({ columns, children, bordered = true, cla
TableHeader.displayName = "TableHeader";

interface TableHeadProps extends AriaColumnProps, Omit<ThHTMLAttributes<HTMLTableCellElement>, "children" | "className" | "style" | "id"> {
/** The column heading text, rendered before any children. */
label?: string;
/** Help text shown in a tooltip when hovering the help icon next to the heading. */
tooltip?: string;
}

Expand Down Expand Up @@ -214,7 +227,9 @@ TableHead.displayName = "TableHead";

interface TableRowProps<T extends object>
extends AriaRowProps<T>, Omit<ComponentPropsWithRef<"tr">, "children" | "className" | "onClick" | "slot" | "style" | "id"> {
/** Whether the row keeps a secondary background while it is selected. */
highlightSelectedRow?: boolean;
/** Controls the height of the row. Falls back to the size inherited from the table. */
size?: "sm" | "md";
}

Expand Down Expand Up @@ -255,7 +270,9 @@ const TableRow = <T extends object>({ columns, children, className, highlightSel
TableRow.displayName = "TableRow";

interface TableCellProps extends AriaCellProps, Omit<TdHTMLAttributes<HTMLTableCellElement>, "children" | "className" | "style" | "id"> {
/** A ref to the underlying `td` element. */
ref?: Ref<HTMLTableCellElement>;
/** Controls the horizontal and vertical padding of the cell. Falls back to the size inherited from the table. */
size?: "sm" | "md";
}

Expand Down
5 changes: 5 additions & 0 deletions components/base/avatar/avatar-label-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@ const styles = {
};

interface AvatarLabelGroupProps extends AvatarProps {
/** The dimensions of the avatar, which also set the text size of the title and subtitle. */
size: "sm" | "md" | "lg";
/** Whether the avatar is a circle rather than a rounded square. */
rounded?: boolean;
/** The primary line of text shown next to the avatar, such as a person's name. */
title: string | ReactNode;
/** The secondary line of text shown below the title. Truncates when it overflows. */
subtitle: string | ReactNode;
/** The class name applied to the avatar, while `className` styles the whole group. */
avatarClassName?: string;
}

Expand Down
1 change: 1 addition & 0 deletions components/base/avatar/avatar-profile-photo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const tickSizeMap = {
} as const;

interface AvatarProfilePhotoProps extends AvatarProps {
/** The dimensions of the photo, which also scale the surrounding ring, initials and status indicator. */
size: "sm" | "md" | "lg";
}

Expand Down
12 changes: 12 additions & 0 deletions components/base/avatar/avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,25 @@ import { AvatarOnlineIndicator, VerifiedTick } from "./base-components";
import { AvatarCount } from "./base-components/avatar-count";

export interface AvatarProps {
/**
* The dimensions of the avatar, which also scale the initials, placeholder icon and status indicator.
*/
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
/**
* The class name for the outer wrapper of the avatar.
*/
className?: string;
/**
* The class name for the main child of the avatar.
*/
contentClassName?: string;
/**
* The URL of the avatar image. Falls back to the initials, placeholder icon or placeholder if it is missing or fails to load.
*/
src?: string | null;
/**
* The alternative text for the avatar image.
*/
alt?: string;
/**
* Display an inner contrast border around the avatar image.
Expand Down
7 changes: 7 additions & 0 deletions components/base/badges/badge-groups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,23 @@ const colorClasses: Record<Theme, Record<Color, { root?: string; addon?: string;
});

interface BadgeGroupProps {
/** The main text of the group, shown next to the addon. When omitted the group tightens its padding and the addon drops its margin. */
children?: string | ReactNode;
/** The text of the pill-shaped addon, which is placed at the start or the end of the group depending on `align`. */
addonText: string;
/** Controls the padding and font size of the group, its addon, and the trailing icon. */
size?: Size;
/** The color palette of the group. In the `light` theme it drives the background, text, ring, and icon colors; in the `modern` theme it only colors the status dot. */
color: Color;
/** `light` fills the group with a tinted background and gives the addon its own ring, while `modern` renders a white, shadowed group with a colored status dot. */
theme?: Theme;
/**
* Alignment of the badge addon element.
*/
align?: Align;
/** An icon component or element rendered at the end of the group. With `align="trailing"` it sits inside the addon, otherwise it follows the group's text. */
iconTrailing?: FC<{ className?: string }> | ReactNode;
/** Additional CSS classes merged onto the group's root element. */
className?: string;
}

Expand Down
Loading
Loading