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 @@ -2,48 +2,61 @@ import * as React from 'react';
import { Popover, PopoverTrigger, PopoverSurface } from '@fluentui/react-headless-components-preview/popover';
import type { PositioningProps } from '@fluentui/react-headless-components-preview/positioning';

const classes = {
outer: 'w-full overflow-auto',
wrapper: 'grid grid-cols-[repeat(3,auto)] grid-rows-[repeat(5,auto)] gap-16 mx-32 my-16 w-max',
trigger:
'h-12 w-32 flex items-center justify-center px-3 rounded-md bg-blue-600 text-white text-xs font-medium hover:bg-blue-700 cursor-pointer border-none',
surface:
'bg-white/95 rounded-lg shadow-lg border border-gray-200 px-4 py-3 text-sm w-56 h-28 flex items-center justify-center',
};
import styles from './positioning.module.css';

const cells: Array<{
const options: Array<{
label: string;
position: NonNullable<PositioningProps['position']>;
align: NonNullable<PositioningProps['align']>;
gridClass: string;
}> = [
{ label: 'above-start', position: 'above', align: 'start', gridClass: 'row-start-1 col-start-1' },
{ label: 'above', position: 'above', align: 'center', gridClass: 'row-start-1 col-start-2' },
{ label: 'above-end', position: 'above', align: 'end', gridClass: 'row-start-1 col-start-3' },
{ label: 'before-top', position: 'before', align: 'start', gridClass: 'row-start-2 col-start-1' },
{ label: 'before', position: 'before', align: 'center', gridClass: 'row-start-3 col-start-1' },
{ label: 'before-bottom', position: 'before', align: 'end', gridClass: 'row-start-4 col-start-1' },
{ label: 'after-top', position: 'after', align: 'start', gridClass: 'row-start-2 col-start-3' },
{ label: 'after', position: 'after', align: 'center', gridClass: 'row-start-3 col-start-3' },
{ label: 'after-bottom', position: 'after', align: 'end', gridClass: 'row-start-4 col-start-3' },
{ label: 'below-start', position: 'below', align: 'start', gridClass: 'row-start-5 col-start-1' },
{ label: 'below', position: 'below', align: 'center', gridClass: 'row-start-5 col-start-2' },
{ label: 'below-end', position: 'below', align: 'end', gridClass: 'row-start-5 col-start-3' },
{ label: 'above-start', position: 'above', align: 'start' },
{ label: 'above', position: 'above', align: 'center' },
{ label: 'above-end', position: 'above', align: 'end' },
{ label: 'before-top', position: 'before', align: 'start' },
{ label: 'before', position: 'before', align: 'center' },
{ label: 'before-bottom', position: 'before', align: 'end' },
{ label: 'after-top', position: 'after', align: 'start' },
{ label: 'after', position: 'after', align: 'center' },
{ label: 'after-bottom', position: 'after', align: 'end' },
{ label: 'below-start', position: 'below', align: 'start' },
{ label: 'below', position: 'below', align: 'center' },
{ label: 'below-end', position: 'below', align: 'end' },
];

export const CoverTarget = (): React.ReactNode => (
<div className={classes.outer}>
<div className={classes.wrapper}>
{cells.map(cell => (
<div key={cell.label} className={cell.gridClass}>
<Popover positioning={{ position: cell.position, align: cell.align, coverTarget: true }}>
export const CoverTarget = (): React.ReactNode => {
const [selected, setSelected] = React.useState(options[1]);

return (
<div className={styles.outer}>
<div className={styles.layout}>
<label className={styles.controls}>
Position
<select
className={styles.select}
value={selected.label}
onChange={e => {
const next = options.find(o => o.label === e.target.value);
if (next) {
setSelected(next);
}
}}
>
{options.map(option => (
<option key={option.label} value={option.label}>
{option.label}
</option>
))}
</select>
</label>
<div className={styles.stage}>
<Popover open positioning={{ position: selected.position, align: selected.align, coverTarget: true }}>
<PopoverTrigger>
<button className={classes.trigger}>{cell.label}</button>
<button className={`${styles.trigger} ${styles.triggerFixed}`}>{selected.label}</button>
</PopoverTrigger>
<PopoverSurface className={classes.surface}>Container</PopoverSurface>
<PopoverSurface className={styles.surfaceCover}>Container</PopoverSurface>
</Popover>
</div>
))}
</div>
</div>
</div>
);
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@ import * as React from 'react';
import { Popover, PopoverTrigger, PopoverSurface } from '@fluentui/react-headless-components-preview/popover';
import type { PositioningProps } from '@fluentui/react-headless-components-preview/positioning';

const classes = {
trigger:
'px-4 py-2 rounded-md bg-blue-600 text-white font-medium hover:bg-blue-700 data-[open]:bg-blue-700 focus-visible:outline-2 focus-visible:outline-blue-500 focus-visible:outline-offset-2 cursor-pointer border-none',
surface: 'bg-white rounded-lg shadow-lg border border-gray-200 p-4 min-w-[160px]',
};
import styles from './positioning.module.css';

export const Default = (props: PositioningProps): React.ReactNode => (
<Popover positioning={props}>
<PopoverTrigger>
<button className={classes.trigger}>Click me</button>
<button className={styles.trigger}>Click me</button>
</PopoverTrigger>
<PopoverSurface className={classes.surface}>Container</PopoverSurface>
<PopoverSurface className={styles.surfaceCallout}>Container</PopoverSurface>
</Popover>
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,112 +1,86 @@
import * as React from 'react';
import { demoBoxClass, demoBoxStyle, flipDemoSurfaceCss } from './demoBox';
import { InlineAnchored } from './InlineAnchored';

const classes = {
page: 'flex flex-col gap-6 p-4',
sectionTitle: 'text-sm font-semibold text-gray-800 mb-1',
sectionNote: 'text-xs text-gray-500 max-w-xl mb-2',
grid: 'grid grid-cols-1 sm:grid-cols-2 gap-4',
trigger:
'px-3 py-1.5 rounded-md bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-blue-500 focus-visible:outline-offset-2 cursor-pointer border-none',
surface: 'flip-demo bg-white rounded-md shadow-md border border-gray-200 p-3 min-w-[200px] max-w-xs text-sm',
};
import styles from './positioning.module.css';

export const FallbackPositions = (): React.ReactNode => (
<div className={classes.page}>
<style>{flipDemoSurfaceCss}</style>

<section>
<h3 className={classes.sectionTitle}>Basic fallback</h3>
<p className={classes.sectionNote}>
<div className={styles.pageRoomy}>
<section className={styles.section}>
<h3 className={styles.sectionTitle}>Basic fallback</h3>
<p className={styles.sectionNote}>
Primary <code>above</code> overflows the box → first fallback <code>below-start</code> fits → surface renders
there.
</p>
<div className={demoBoxClass} style={demoBoxStyle}>
<div className={styles.demoBox}>
<InlineAnchored
positioning={{
position: 'above',
align: 'center',
fallbackPositions: ['below-start', 'after'],
}}
surfaceClassName={classes.surface}
surfaceClassName={`${styles.surfaceFallback} ${styles.flipReadout}`}
trigger={
<button
className={classes.trigger}
style={{ position: 'absolute', top: 12, left: '50%', transform: 'translateX(-50%)' }}
>
trigger near top
</button>
<button className={`${styles.trigger} ${styles.triggerSm} ${styles.pinTopCenter}`}>trigger near top</button>
}
>
<strong>Requested:</strong> above · fallbacks: <code>below-start</code>, <code>after</code>
</InlineAnchored>
</div>
</section>

<section>
<h3 className={classes.sectionTitle}>Chain walking</h3>
<p className={classes.sectionNote}>
<section className={styles.section}>
<h3 className={styles.sectionTitle}>Chain walking</h3>
<p className={styles.sectionNote}>
Trigger pinned to top-left. Primary <code>above</code> overflows, first fallback <code>before</code> also
overflows (no room to the left), so the browser falls through to <code>below</code>. The live{' '}
<strong>Actual</strong> readout should read <code>below</code>.
</p>
<div className={demoBoxClass} style={demoBoxStyle}>
<div className={styles.demoBox}>
<InlineAnchored
positioning={{
position: 'above',
align: 'center',
fallbackPositions: ['before', 'below'],
}}
surfaceClassName={classes.surface}
surfaceClassName={`${styles.surfaceFallback} ${styles.flipReadout}`}
trigger={
<button className={classes.trigger} style={{ position: 'absolute', top: 12, left: 12 }}>
top-left trigger
</button>
<button className={`${styles.trigger} ${styles.triggerSm} ${styles.pinTopLeft}`}>top-left trigger</button>
}
>
<strong>Requested:</strong> above · fallbacks: <code>before</code>, <code>below</code>
</InlineAnchored>
</div>
</section>

<section>
<h3 className={classes.sectionTitle}>Custom chain replaces default flip</h3>
<p className={classes.sectionNote}>
<section className={styles.section}>
<h3 className={styles.sectionTitle}>Custom chain replaces default flip</h3>
<p className={styles.sectionNote}>
Same overflow condition, different chains. Left popover has no <code>fallbackPositions</code> → default{' '}
<code>flip-block, flip-inline</code> fires → surface ends up below. Right popover passes <code>['after']</code>{' '}
→ custom chain replaces defaults → surface goes to the right instead of flipping.
</p>
<div className={classes.grid}>
<div className={demoBoxClass} style={demoBoxStyle}>
<div className={styles.grid}>
<div className={styles.demoBox}>
<InlineAnchored
positioning={{ position: 'above', align: 'center' }}
surfaceClassName={classes.surface}
surfaceClassName={`${styles.surfaceFallback} ${styles.flipReadout}`}
trigger={
<button
className={classes.trigger}
style={{ position: 'absolute', top: 12, left: '50%', transform: 'translateX(-50%)' }}
>
default (flip)
</button>
<button className={`${styles.trigger} ${styles.triggerSm} ${styles.pinTopCenter}`}>default (flip)</button>
}
>
<strong>Requested:</strong> above · no custom fallbacks
</InlineAnchored>
</div>
<div className={demoBoxClass} style={demoBoxStyle}>
<div className={styles.demoBox}>
<InlineAnchored
positioning={{
position: 'above',
align: 'center',
fallbackPositions: ['after', 'below'],
}}
surfaceClassName={classes.surface}
surfaceClassName={`${styles.surfaceFallback} ${styles.flipReadout}`}
trigger={
<button
className={classes.trigger}
style={{ position: 'absolute', top: 12, left: '50%', transform: 'translateX(-50%)' }}
>
<button className={`${styles.trigger} ${styles.triggerSm} ${styles.pinTopCenter}`}>
custom ({`['after']`})
</button>
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,30 @@
import * as React from 'react';
import { demoBoxClass, demoBoxStyle, flipDemoSurfaceCss } from './demoBox';
import { InlineAnchored } from './InlineAnchored';

import descriptionMd from './PositioningFlippingBlockDescription.md';

const classes = {
page: 'flex flex-col gap-4 p-4',
grid: 'grid grid-cols-1 sm:grid-cols-2 gap-4',
trigger:
'px-3 py-1.5 rounded-md bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-blue-500 focus-visible:outline-offset-2 cursor-pointer border-none',
surface: 'flip-demo bg-white rounded-md shadow-md border border-gray-200 p-2 w-[160px] text-xs',
};
import styles from './positioning.module.css';

export const FlippingBlock = (): React.ReactNode => (
<div className={classes.page}>
<style>{flipDemoSurfaceCss}</style>

<div className={classes.grid}>
<div className={demoBoxClass} style={demoBoxStyle}>
<div className={styles.page}>
<div className={styles.grid}>
<div className={styles.demoBox}>
<InlineAnchored
positioning={{ position: 'above' }}
surfaceClassName={classes.surface}
surfaceClassName={`${styles.surfaceFlipNarrow} ${styles.flipReadout}`}
trigger={
<button
className={classes.trigger}
style={{ position: 'absolute', top: 12, left: '50%', transform: 'translateX(-50%)' }}
>
trigger near top
</button>
<button className={`${styles.trigger} ${styles.triggerSm} ${styles.pinTopCenter}`}>trigger near top</button>
}
>
<strong>Requested:</strong> above → flips below
</InlineAnchored>
</div>

<div className={demoBoxClass} style={demoBoxStyle}>
<div className={styles.demoBox}>
<InlineAnchored
positioning={{ position: 'below' }}
surfaceClassName={classes.surface}
surfaceClassName={`${styles.surfaceFlipNarrow} ${styles.flipReadout}`}
trigger={
<button
className={classes.trigger}
style={{ position: 'absolute', bottom: 12, left: '50%', transform: 'translateX(-50%)' }}
>
<button className={`${styles.trigger} ${styles.triggerSm} ${styles.pinBottomCenter}`}>
trigger near bottom
</button>
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
import * as React from 'react';
import { demoBoxClass, demoBoxStyle, flipDemoSurfaceCss } from './demoBox';
import { InlineAnchored } from './InlineAnchored';

import descriptionMd from './PositioningFlippingCornerDescription.md';

const classes = {
page: 'flex flex-col gap-4 p-4',
grid: 'grid grid-cols-1 sm:grid-cols-2 gap-4',
trigger:
'px-3 py-1.5 rounded-md bg-blue-600 text-white text-xs font-medium hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-blue-500 focus-visible:outline-offset-2 cursor-pointer border-none',
surface: 'flip-demo bg-white rounded-md shadow-md border border-gray-200 p-3 w-[320px] max-h-[140px] text-sm',
};
import styles from './positioning.module.css';

export const FlippingCorner = (): React.ReactNode => (
<div className={classes.page}>
<style>{flipDemoSurfaceCss}</style>

<div className={classes.grid}>
<div className={demoBoxClass} style={demoBoxStyle}>
<div className={styles.page}>
<div className={styles.grid}>
<div className={styles.demoBox}>
<InlineAnchored
positioning={{ position: 'above', align: 'end' }}
surfaceClassName={classes.surface}
surfaceClassName={`${styles.surfaceFlipWide} ${styles.flipReadout}`}
trigger={
<button className={classes.trigger} style={{ position: 'absolute', top: 12, left: 12 }}>
<button className={`${styles.trigger} ${styles.triggerSm} ${styles.pinTopLeft}`}>
top-left · requested above-end
</button>
}
Expand All @@ -31,12 +21,12 @@ export const FlippingCorner = (): React.ReactNode => (
</InlineAnchored>
</div>

<div className={demoBoxClass} style={demoBoxStyle}>
<div className={styles.demoBox}>
<InlineAnchored
positioning={{ position: 'above', align: 'start' }}
surfaceClassName={classes.surface}
surfaceClassName={`${styles.surfaceFlipWide} ${styles.flipReadout}`}
trigger={
<button className={classes.trigger} style={{ position: 'absolute', top: 12, right: 12 }}>
<button className={`${styles.trigger} ${styles.triggerSm} ${styles.pinTopRight}`}>
top-right · requested above-start
</button>
}
Expand All @@ -45,12 +35,12 @@ export const FlippingCorner = (): React.ReactNode => (
</InlineAnchored>
</div>

<div className={demoBoxClass} style={demoBoxStyle}>
<div className={styles.demoBox}>
<InlineAnchored
positioning={{ position: 'below', align: 'end' }}
surfaceClassName={classes.surface}
surfaceClassName={`${styles.surfaceFlipWide} ${styles.flipReadout}`}
trigger={
<button className={classes.trigger} style={{ position: 'absolute', bottom: 12, left: 12 }}>
<button className={`${styles.trigger} ${styles.triggerSm} ${styles.pinBottomLeft}`}>
bottom-left · requested below-end
</button>
}
Expand All @@ -59,12 +49,12 @@ export const FlippingCorner = (): React.ReactNode => (
</InlineAnchored>
</div>

<div className={demoBoxClass} style={demoBoxStyle}>
<div className={styles.demoBox}>
<InlineAnchored
positioning={{ position: 'below', align: 'start' }}
surfaceClassName={classes.surface}
surfaceClassName={`${styles.surfaceFlipWide} ${styles.flipReadout}`}
trigger={
<button className={classes.trigger} style={{ position: 'absolute', bottom: 12, right: 12 }}>
<button className={`${styles.trigger} ${styles.triggerSm} ${styles.pinBottomRight}`}>
bottom-right · requested below-start
</button>
}
Expand Down
Loading
Loading