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
10 changes: 8 additions & 2 deletions packages/@react-spectrum/ai/stories/Alert.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ export default meta;
type Story = StoryObj<typeof Alert>;

export const Example: Story = {
render: args => <Alert {...args} styles={style({width: 336})} />
// Storybook uses 16px padding left and right
render: args => <Alert {...args} styles={style({width: 336, maxWidth: 'calc(100vw - 32px)'})} />
};

const VARIANTS = ['informative', 'positive', 'notice', 'negative', 'neutral'] as const;
Expand All @@ -52,7 +53,12 @@ export const AllVariants: Story = {
render: args => (
<div className={style({display: 'flex', flexDirection: 'column', gap: 12})}>
{VARIANTS.map(variant => (
<Alert {...args} key={variant} variant={variant} styles={style({width: 336})} />
<Alert
{...args}
key={variant}
variant={variant}
styles={style({width: 336, maxWidth: 'calc(100vw - 32px)'})}
/>
))}
</div>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ type Story = StoryObj<typeof ResponseStatus>;

export const Example: Story = {
render: args => (
<div className={style({width: 320, minHeight: 240})}>
<div className={style({width: 320, maxWidth: 'calc(100vw - 32px)', minHeight: 240})}>
<ResponseStatus {...args}>
<ResponseStatusTitle>
{args.status === 'pending'
Expand All @@ -86,7 +86,7 @@ export const Example: Story = {

export const NoResponseContent: Story = {
render: args => (
<div className={style({width: 320, minHeight: 240})}>
<div className={style({width: 320, maxWidth: 'calc(100vw - 32px)', minHeight: 240})}>
<ResponseStatus {...args}>
<ResponseStatusTitle>
{args.status === 'pending'
Expand Down Expand Up @@ -228,7 +228,7 @@ function WithExecutionTraceRender(args) {
return (
<div
className={style({
maxWidth: 800,
maxWidth: 'min(800px, calc(100vw - 32px))',
minHeight: 240,
marginX: 'auto',
paddingY: 40,
Expand Down
71 changes: 42 additions & 29 deletions packages/@react-spectrum/s2/stories/ShadowDOM.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ import Server from '../spectrum-illustrations/linear/Server';
import StarFilled1 from '../spectrum-illustrations/linear/Star';
import {style} from '../style' with {type: 'macro'};
import {UNSAFE_PortalProvider} from 'react-aria';
import {useEffect, useRef} from 'react';
import {useEffect, useRef, useState} from 'react';

enableShadowDOM();

Expand Down Expand Up @@ -165,6 +165,41 @@ function unmountRootDeferred(root: ReturnType<typeof createRoot>): void {
});
}

type Scheme = 'light' | 'dark';

function getDocumentColorScheme(): Scheme {
return document.documentElement.getAttribute('data-color-scheme') === 'dark' ? 'dark' : 'light';
}

function useStorybookColorScheme(): Scheme {
const [colorScheme, setColorScheme] = useState<Scheme>(getDocumentColorScheme);
useEffect(() => {
const docEl = document.documentElement;
const update = () => setColorScheme(getDocumentColorScheme());
update();
const observer = new MutationObserver(update);
observer.observe(docEl, {attributes: true, attributeFilter: ['data-color-scheme']});
return () => observer.disconnect();
}, []);
return colorScheme;
}

/**
* The app rendered into the nested shadow root. It tracks Storybook's color scheme itself and
* passes it to `<Provider colorScheme>`, so the scheme updates through React state within this
* root.
*/
function ShadowApp({portalContainerRef}: {portalContainerRef: {current: HTMLDivElement | null}}) {
const colorScheme = useStorybookColorScheme();
return (
<Provider colorScheme={colorScheme} background="base" styles={style({minHeight: 600})}>
<UNSAFE_PortalProvider getContainer={() => portalContainerRef.current}>
<AllComponents />
</UNSAFE_PortalProvider>
</Provider>
);
}

function ShadowDOMContained() {
const hostRef = useRef<HTMLDivElement>(null);
const portalContainerRef = useRef<HTMLDivElement | null>(null);
Expand All @@ -178,12 +213,6 @@ function ShadowDOMContained() {

const shadowRoot = host.attachShadow({mode: 'open'});

// So S2 theme variables apply: :host in the copied CSS targets the shadow host.
const scheme = document.documentElement.getAttribute('data-color-scheme');
if (scheme) {
host.setAttribute('data-color-scheme', scheme);
}

// Copy all styles from the document into the shadow root so S2 (and Storybook) styles apply.
// Shadow DOM does not inherit styles; we must duplicate every stylesheet.
shadowRoot.appendChild(createClonedDocumentStyleRoot());
Expand All @@ -199,13 +228,7 @@ function ShadowDOMContained() {

const root = createRoot(appContainer);
rootRef.current = root;
root.render(
<Provider colorScheme="dark">
<UNSAFE_PortalProvider getContainer={() => portalContainerRef.current}>
<AllComponents />
</UNSAFE_PortalProvider>
</Provider>
);
root.render(<ShadowApp portalContainerRef={portalContainerRef} />);

return () => {
rootRef.current = null;
Expand Down Expand Up @@ -233,13 +256,6 @@ function ShadowDOMPortalToBody() {
const shadowRoot = host.attachShadow({mode: 'open'});
const shadowPortal = portalHost.attachShadow({mode: 'open'});

// So S2 theme variables apply: :host in the copied CSS targets the shadow host.
const scheme = document.documentElement.getAttribute('data-color-scheme');
if (scheme) {
host.setAttribute('data-color-scheme', scheme);
portalHost.setAttribute('data-color-scheme', scheme);
}

// Each shadow root needs its own style clone — reusing one node only leaves styles in the last root.
shadowRoot.appendChild(createClonedDocumentStyleRoot());
shadowPortal.appendChild(createClonedDocumentStyleRoot());
Expand All @@ -255,13 +271,8 @@ function ShadowDOMPortalToBody() {

const root = createRoot(appContainer);
rootRef.current = root;
root.render(
<Provider colorScheme="dark">
<UNSAFE_PortalProvider getContainer={() => portalContainerRef.current}>
<AllComponents />
</UNSAFE_PortalProvider>
</Provider>
);
// The portaled overlays inherit colorScheme through the Provider context, so both shadows theme.
root.render(<ShadowApp portalContainerRef={portalContainerRef} />);

return () => {
rootRef.current = null;
Expand Down Expand Up @@ -557,7 +568,9 @@ function AllComponents() {
</ActionBar>
)}>
<TableHeader>
<Column id="col1">Name</Column>
<Column id="col1" isRowHeader>
Name
</Column>
<Column id="col2">Value</Column>
</TableHeader>
<TableBody>
Expand Down