Skip to content
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3ed6120
feat(storybook): implement argTypeEnhancers for nice UX for Slot API …
Hotell Feb 13, 2025
302f495
feat(react-card-stories): use argTypeEnhancer for rendering
Hotell Feb 13, 2025
1d49538
fixup! feat(storybook): implement argTypeEnhancers for nice UX for Sl…
Hotell Feb 13, 2025
151ad36
Revert "feat(react-card-stories): use argTypeEnhancer for rendering"
Hotell Feb 13, 2025
2bc520b
fixup! fixup! feat(storybook): implement argTypeEnhancers for nice UX…
Hotell Feb 13, 2025
1f2d51f
feat: apply enhancer internallly within custom DocPage
Hotell Feb 13, 2025
1b764e4
refactor: dont mutate story object rather use scoped enhancer within …
Hotell Feb 14, 2025
c755e84
improve readability - move message before prop table
carlamntn Feb 21, 2025
5b1cb70
adjusted overrides to handle complex Slot types
carlamntn Feb 21, 2025
9accdbb
adjust for complex Slot types
carlamntn Feb 21, 2025
ca619c1
fix info box
carlamntn Feb 27, 2025
8394dab
additional info about customizing with slots
carlamntn Feb 27, 2025
1c6f3b5
create copy for story
carlamntn Feb 27, 2025
50a6bdb
refactor additional info styles
carlamntn Feb 27, 2025
95ee5d8
fix link
carlamntn Feb 27, 2025
0d7b679
fix link for slots
carlamntn Feb 27, 2025
dd097bb
encapsulate repeated logic into separate component and render conditi…
carlamntn Mar 3, 2025
71a0b6f
remove unnecessary comments
carlamntn Mar 3, 2025
3d54054
revert changes
carlamntn Mar 3, 2025
80fde25
Refactor AdditionalApiDocs to use ReactElement instead of ReactNode
carlamntn Mar 12, 2025
42a2fde
additional docs fix
carlamntn Mar 12, 2025
fb06625
refactor - deep copy and remove global flag
carlamntn Mar 12, 2025
3c3a2f4
cleanup
carlamntn Mar 12, 2025
c6e3aaf
fix for accordion
carlamntn Mar 19, 2025
4717b49
subcomponents enhancement
carlamntn Mar 20, 2025
2f21f83
remove argtypes
carlamntn Mar 20, 2025
eb48240
subcomponents enhance slot types
carlamntn Apr 9, 2025
68ee110
refactor
carlamntn Apr 10, 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
130 changes: 109 additions & 21 deletions apps/public-docsite-v9/src/DocsComponents/FluentDocsPage.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import {
Stories,
type DocsContextProps,
} from '@storybook/addon-docs';
import type { PreparedStory, Renderer } from '@storybook/types';
import type { SBEnumType } from '@storybook/csf';
import type { PreparedStory, Renderer, SBEnumType } from '@storybook/types';
import { makeStyles, shorthands, tokens, Link, Text } from '@fluentui/react-components';
import { InfoFilled } from '@fluentui/react-icons';
import { DIR_ID, THEME_ID, themes } from '@fluentui/react-storybook-addon';
Expand Down Expand Up @@ -52,24 +51,32 @@ const useStyles = makeStyles({
display: 'grid',
gridTemplateColumns: '1fr min-content',
},
nativeProps: {
additionalInfo: {
display: 'flex',
gap: tokens.spacingHorizontalM,

flexDirection: 'column',
gap: tokens.spacingVerticalM,
border: `1px solid ${tokens.colorNeutralStroke1}`,
borderRadius: tokens.borderRadiusMedium,
padding: tokens.spacingHorizontalM,
margin: `0 ${tokens.spacingHorizontalM}`,
},
nativePropsIcon: {
additionalInfoIcon: {
alignSelf: 'center',
color: tokens.colorBrandForeground1,
fontSize: '24px',
marginRight: tokens.spacingHorizontalM,
},
additionalInfoMessage: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: tokens.spacingVerticalXS,
},
nativePropsMessage: {
infoIcon: {
display: 'flex',
flexDirection: 'column',
gap: tokens.spacingVerticalXS,
flex: 1,
},
});

Expand Down Expand Up @@ -120,7 +127,7 @@ const VideoPreviews: React.FC<{
};

const getNativeElementsList = (elements: SBEnumType['value']): JSX.Element => {
const elementsArr = elements.map((el, idx) => [
const elementsArr = elements?.map((el, idx) => [
<code key={idx}>{`<${el}>`}</code>,
idx !== elements.length - 1 ? ', ' : ' ',
]);
Expand All @@ -133,30 +140,110 @@ const getNativeElementsList = (elements: SBEnumType['value']): JSX.Element => {
);
};

const RenderArgsTable = ({ hideArgsTable, primaryStory }: { primaryStory: PrimaryStory; hideArgsTable: boolean }) => {
const slotRegex = /as\?:\s*"([^"]+)"/;
/**
* NOTE: this function mutates original story argTypes including all story subcomponents if they are present
*/
function withSlotEnhancer(story: PreparedStory) {
const hasArgAsProp = story.argTypes.as?.type?.name === 'enum';
const argAsProp = hasArgAsProp ? (story.argTypes.as.type as SBEnumType).value : null;
let hasArgAsSlot = false;

type InternalComponentApi = {
__docgenInfo: {
props?: Record<string, { type: { name: string } }>;
};
[k: string]: unknown;
};

const transformPropsWithSlotShorthand = (props: Record<string, { type: { name: string } }>) => {
Object.entries(props).forEach(([key, argType]) => {
const value: string = argType?.type?.name;
if (value.includes('WithSlotShorthandValue')) {
hasArgAsSlot = true;
const match = value.match(slotRegex);
if (match) {
props[key].type.name = `Slot<\"${match[1]}\">`;
} else {
props[key].type.name = `Slot`;
}
}
});
};

const transformComponent = (component: InternalComponentApi) => {
const docGenProps = component?.__docgenInfo?.props;
if (docGenProps) {
transformPropsWithSlotShorthand(docGenProps);
}
};

const component = story.component as InternalComponentApi;
transformComponent(component);

if (story.subcomponents) {
Object.values(story.subcomponents).forEach((subcomponent: InternalComponentApi) => {
transformComponent(subcomponent);
});
}

return { component, hasArgAsSlot, hasArgAsProp, argAsProp };
}

const AdditionalApiDocs: React.FC<{ children: React.ReactElement | React.ReactElement[] }> = ({ children }) => {
const styles = useStyles();
return (
<div className={styles.additionalInfo}>
<div className={styles.additionalInfoMessage}>
<InfoFilled className={styles.additionalInfoIcon} />
<div className={styles.infoIcon}>{children}</div>
</div>
</div>
);
};
const RenderArgsTable = ({ hideArgsTable, story }: { story: PrimaryStory; hideArgsTable: boolean }) => {
const { component, hasArgAsProp, hasArgAsSlot, argAsProp } = withSlotEnhancer(story);

return hideArgsTable ? null : (
<>
<ArgsTable of={primaryStory.component} />
{primaryStory.argTypes.as && primaryStory.argTypes.as?.type?.name === 'enum' && (
<div className={styles.nativeProps}>
<InfoFilled className={styles.nativePropsIcon} />
<div className={styles.nativePropsMessage}>
{hasArgAsProp && (
<AdditionalApiDocs>
<p>
<b>
Native props are supported <span role="presentation">🙌</span>
<br />
</b>
<span>
All HTML attributes native to the {getNativeElementsList(primaryStory.argTypes.as.type.value)}, including
all <code>aria-*</code> and <code>data-*</code> attributes, can be applied as native props on this
component.
All HTML attributes native to the
{getNativeElementsList(argAsProp!)}, including all <code>aria-*</code> and <code>data-*</code> attributes,
can be applied as native props on this component.
</span>
</div>
</div>
</p>
</AdditionalApiDocs>
)}
{hasArgAsSlot && (
<AdditionalApiDocs>
<p>
<b>
Customizing components with slots <span role="presentation">🙌</span>
</b>
<br />
<span>
Slots in Fluent UI React components are designed to be modified or replaced, providing a flexible approach
to customizing components. Each slot is exposed as a top-level prop and can be filled with primitive
values, JSX/TSX, props objects, or render functions. This allows for more dynamic and reusable component
structures, similar to slots in other frameworks.{' '}
<Link href="/?path=/docs/concepts-developer-customizing-components-with-slots--docs">
Customizing components with slots{' '}
</Link>
</span>
</p>
</AdditionalApiDocs>
)}
<ArgsTable of={component} />
</>
);
};

const RenderPrimaryStory = ({
primaryStory,
skipPrimaryStory,
Expand All @@ -179,6 +266,7 @@ const RenderPrimaryStory = ({
export const FluentDocsPage = () => {
const context = React.useContext(DocsContext);
const stories = context.componentStories();

const primaryStory = stories[0];
const primaryStoryContext = context.getStoryContext(primaryStory);

Expand Down Expand Up @@ -219,7 +307,7 @@ export const FluentDocsPage = () => {
{videos && <VideoPreviews videos={videos} />}
</div>
<RenderPrimaryStory primaryStory={primaryStory} skipPrimaryStory={skipPrimaryStory} />
<RenderArgsTable primaryStory={primaryStory} hideArgsTable={hideArgsTable} />
<RenderArgsTable story={primaryStory} hideArgsTable={hideArgsTable} />
<Stories />
</div>
<div className={styles.toc}>
Expand Down