Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
cadbb00
details summary
mj12albert Jun 16, 2025
101b98e
tweaks
mj12albert Jun 19, 2025
71b548a
arrow nav
mj12albert Jun 20, 2025
c67f38a
AttributesReferenceTable
mj12albert Jun 23, 2025
edcca3f
Fix styles
mj12albert Jun 23, 2025
7d00fd4
CssVariablesReferenceTable
mj12albert Jun 23, 2025
eeab5af
cleanup
mj12albert Jun 23, 2025
cf5d6f1
tweak styles
mj12albert Jun 30, 2025
2378c77
Tweak typography
mj12albert Jul 2, 2025
a1ae0ad
Tweak styles
mj12albert Jul 2, 2025
1fe35d8
Fix short prop name
mj12albert Jul 3, 2025
6f65a62
Fix styles
mj12albert Jul 3, 2025
e2c5c0f
Add missing key
mj12albert Jul 3, 2025
d71b9b4
Move layout styles to tailwind
mj12albert Jul 3, 2025
ee23cbb
Handle text selection on triggers
mj12albert Jul 3, 2025
d65ec65
docs:api
mj12albert Jul 7, 2025
e68c482
Add a Chip for text selection
mj12albert Jul 7, 2025
a50abf5
Tweak colors
mj12albert Jul 7, 2025
0887e43
Tweak colors again
mj12albert Jul 7, 2025
cc3941b
Revert chip island
mj12albert Jul 8, 2025
0259b01
Fixes from review comments
mj12albert Jul 8, 2025
cda7000
Fix mobile layout
mj12albert Jul 8, 2025
dc17bab
Fix dt element color
mj12albert Jul 8, 2025
a0fa992
Add required prop indicator
mj12albert Jul 9, 2025
560b1cc
Fix required prop aria-label
mj12albert Jul 9, 2025
157cb18
Fix prop default colors
mj12albert Jul 9, 2025
5b164a1
Detailed type tooltip
mj12albert Jul 9, 2025
a4cbd4d
Fix styles
mj12albert Jul 10, 2025
d649736
Fix alignment
mj12albert Jul 10, 2025
e5805f6
Fix alignment again
mj12albert Jul 10, 2025
fe83e84
Fix mobile layout
mj12albert Jul 10, 2025
b9e7ce6
Bump TableCellInner min height to match accordion
mj12albert Jul 10, 2025
c79ecf5
Show tooltip for Union
mj12albert Jul 10, 2025
b420d00
Tweak styles
mj12albert Jul 14, 2025
6197246
Fix code block margins
mj12albert Jul 14, 2025
ff646df
Increase tooltip max width
mj12albert Jul 15, 2025
af8e1dc
Link to individual props
mj12albert Jul 15, 2025
43cf2dd
Fix import
mj12albert Jul 22, 2025
43b096c
Fix styles
mj12albert Jul 22, 2025
20e4136
Update scroll margin
mj12albert Jul 22, 2025
7795433
Handle overflowing content in summary element
mj12albert Jul 23, 2025
b5e635a
Prop name overflow
mj12albert Jul 23, 2025
9e8ca4d
Fix gap
mj12albert Jul 24, 2025
5457b8c
Merge branch 'master' into docs/disclosure-props-table
atomiks Jul 28, 2025
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
171 changes: 171 additions & 0 deletions docs/src/components/Accordion.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
@layer components {
.AccordionRoot {
--color-trigger-hover: hsl(0 0 99);
--color-open-trigger-shadow: var(--color-gray-300);

@media (prefers-color-scheme: dark) {
--color-trigger-hover: hsl(0 0 9);
--color-open-trigger-shadow: hsl(0 0 5);
}

box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
color: var(--color-gray);

border: 1px solid var(--color-gray-200);
border-collapse: separate;
border-radius: var(--radius-md);
}

.AccordionHeaderRow {
border-radius: var(--radius-md) var(--radius-md) 0 0;
border-bottom: 1px solid var(--color-gray-200);
background-color: var(--color-gray-50);
}

.AccordionHeaderCell {
padding: 0.5rem 0.75rem;
font-size: var(--text-sm);
font-weight: var(--font-weight-medium);
letter-spacing: -0.00625em;
color: var(--color-foreground);
}

.AccordionItem:not(:last-child) {
border-bottom: 1px solid var(--color-gray-200);
}

.AccordionTrigger {
box-sizing: border-box;
position: relative;
display: flex;
width: 100%;
min-height: 2.5rem;
align-items: center;
/* make padding match TableCell height */
padding-block: 0.5rem;
padding-inline: 0.75rem 0.5rem;
border: none;
outline: none;
text-align: left;
background-color: var(--color-content);

/* remove default disclosure triangle */
&::-webkit-details-marker {
display: none;
}
list-style: none;

@media (hover: hover) {
&:hover {
background-color: var(--color-trigger-hover);
cursor: default;
}
}

&:focus-visible {
outline: 2px solid var(--color-blue);
z-index: 1;
}

[open] & {
box-shadow:
0 2px 4px -3px var(--color-open-trigger-shadow),
inset 0 -1px 1px transparent;
}

.AccordionItem:not([open]):last-child & {
border-bottom-left-radius: var(--radius-md);
border-bottom-right-radius: var(--radius-md);
}
}

.AccordionIcon {
box-sizing: border-box;
flex-shrink: 0;
width: 0.75rem;
height: 0.75rem;
margin-inline: auto 0.5rem;

[open] & {
transform: rotate(180deg);
}
}

.AccordionPanel {
box-sizing: border-box;
height: auto;
overflow: hidden;
color: var(--color-foreground);
font-size: 1rem;
line-height: 1.5rem;
}

.AccordionContent {
padding-block: 1px;

[open] & {
background-color: var(--color-gray-50);
box-shadow: inset 0px 1px 0px 0px var(--color-gray-100);
}

.AccordionItem:last-child & {
border-bottom-left-radius: var(--radius-md);
border-bottom-right-radius: var(--radius-md);
}
}

.AccordionScrollable {
overflow-x: hidden;

&[data-scrollable] {
position: relative;

/* Overscroll overlay */
&::after {
content: '';
position: absolute;
pointer-events: none;
top: 1px;
bottom: 1px;
right: -1px;
width: 1.25rem;
background-image: linear-gradient(to right, transparent, var(--color-content));
animation: accordion-overscroll-overlay 500ms;
}

@media (hover: hover) {
.AccordionTrigger:hover &::after {
background-image: linear-gradient(to right, transparent, var(--color-trigger-hover));
}
}
}
}

@keyframes accordion-overscroll-overlay {
from {
opacity: 0;
}
}

.AccordionScrollableInner {
/* Ensure consistent height of the cells */
display: flex;
align-items: center;
gap: 0.5rem;
min-height: 2.5rem;
padding-block: 0.5rem;
padding-right: 1rem;

/* Make individual cells scrollable */
overflow-x: auto;
overscroll-behavior-x: contain;
scrollbar-width: none;

&::-webkit-scrollbar {
display: none;
}
}
}
161 changes: 161 additions & 0 deletions docs/src/components/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
'use client';
import * as React from 'react';
import clsx from 'clsx';
import { useEventCallback } from '@base-ui-components/utils/useEventCallback';
import { observeScrollableInner } from '../utils/observeScrollableInner';

const ARROW_UP = 'ArrowUp';
const ARROW_DOWN = 'ArrowDown';
const ARROW_LEFT = 'ArrowLeft';
const ARROW_RIGHT = 'ArrowRight';
const HOME = 'Home';
const END = 'End';

const SUPPORTED_KEYS = new Set([ARROW_DOWN, ARROW_UP, ARROW_RIGHT, ARROW_LEFT, HOME, END]);

const AccordionContext = React.createContext<
{ rootRef: React.RefObject<HTMLElement | null> } | undefined
>(undefined);

export function Root(props: React.ComponentProps<'section'>) {
const rootRef = React.useRef<HTMLElement>(null);

const context = React.useMemo(() => ({ rootRef }), [rootRef]);

return (
<AccordionContext.Provider value={context}>
<section {...props} ref={rootRef} className={clsx('AccordionRoot', props.className)} />
</AccordionContext.Provider>
);
}

export function Trigger({
index: thisIndex,
id,
...props
}: React.ComponentProps<'summary'> & {
index: number;
}) {
const context = React.useContext(AccordionContext);

return (
<summary
{...props}
id={id}
className={clsx('AccordionTrigger', props.className)}
onKeyDown={(event) => {
if (!context || !SUPPORTED_KEYS.has(event.key)) {
return;
}

event.preventDefault();
event.stopPropagation();

const rootElement = context.rootRef?.current;
if (!rootElement) {
return;
}

const triggers = rootElement.querySelectorAll<HTMLElement>('summary');

let nextIndex = -1;
const lastIndex = triggers.length - 1;

switch (event.key) {
case ARROW_LEFT:
case ARROW_UP:
nextIndex = thisIndex === 0 ? lastIndex : thisIndex - 1;
break;
case ARROW_RIGHT:
case ARROW_DOWN:
nextIndex = thisIndex + 1 > lastIndex ? 0 : thisIndex + 1;
break;
case 'Home':
nextIndex = 0;
break;
case 'End':
nextIndex = lastIndex;
break;
default:
break;
}

if (nextIndex > -1) {
triggers.item(nextIndex).focus();
}

props.onKeyDown?.(event);
}}
onClick={(event) => {
const selection = window.getSelection();
if (!selection?.isCollapsed) {
event.preventDefault();
}
props.onClick?.(event);
}}
onMouseDown={(event) => {
if (!event.defaultPrevented && event.detail > 1) {
event.preventDefault();
}
}}
/>
);
}

export function Item(props: React.ComponentProps<'details'>) {
const [open, setOpen] = React.useState<boolean>(false);
// in Chrome, the <details> opens automatically when the hash part of a URL
// matches the `id` on <summary> but needs to be manually handled for Safari
// and Firefox
const handleRef = useEventCallback((element: HTMLDetailsElement | null) => {
if (element) {
const trigger = element.querySelector<HTMLElement>('summary');
const triggerId = trigger?.getAttribute('id');
const hash = window.location.hash.slice(1);

if (triggerId && hash && triggerId === hash) {
setOpen(true);
}
}
});

return (
<details
{...props}
ref={handleRef}
open={open || undefined}
className={clsx('AccordionItem', props.className)}
/>
);
}

export function Panel(props: React.ComponentProps<'div'>) {
return <div {...props} className={clsx('AccordionPanel', props.className)} />;
}

export function Content(props: React.ComponentProps<'div'>) {
return <div {...props} className={clsx('AccordionContent', props.className)} />;
}

/* Scroll container with an overscroll overlay graphic for overflowing content inside the trigger */
export function Scrollable({ children, className, ...props }: React.ComponentProps<'span'>) {
return (
<span
ref={observeScrollableInner}
className={clsx('AccordionScrollable', className)}
{...props}
>
<span className="AccordionScrollableInner">{children}</span>
</span>
);
}

/* Fake <tr> */
export function HeaderRow(props: React.ComponentProps<'div'>) {
return <div {...props} aria-hidden className={clsx('AccordionHeaderRow', props.className)} />;
}

/* Fake <th scope="col"> */
export function HeaderCell(props: React.ComponentProps<'div'>) {
return <div {...props} className={clsx('AccordionHeaderCell', props.className)} />;
}
1 change: 1 addition & 0 deletions docs/src/components/CodeBlock.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@layer components {
.CodeBlockRoot {
align-self: stretch;
background-color: var(--color-content);
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-md);
Expand Down
Loading
Loading