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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Generated files for Label component",
"packageName": "@fluentui-contrib/react-cap-theme",
"email": "olkatruk@microsoft.com",
"dependentChangeType": "patch"
}
34 changes: 34 additions & 0 deletions packages/react-cap-theme/src/components/Label/Label.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
makeStyles,
mergeClasses,
tokens,
type LabelState,
} from '@fluentui/react-components';

export const useLabelStyles = makeStyles({
root: {
display: 'inline-flex',
alignItems: 'center',
gap: tokens.spacingHorizontalXS,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be introducing new tokens here so that this follows the same convention as the other components?

},
startSlot: {
marginRight: tokens.spacingHorizontalXXS,
},
required: {
paddingLeft: 0,
},
});

export function useLabelStylesHook(state: LabelState): LabelState {
const styles = useLabelStyles();

state.root.className = mergeClasses(state.root.className, styles.root);
if (state.required) {
state.required.className = mergeClasses(
state.required.className,
styles.required
);
}

return state;
}
41 changes: 41 additions & 0 deletions packages/react-cap-theme/src/components/Label/Label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as React from 'react';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import {
Badge,
Label as FluentLabel,
mergeClasses,
} from '@fluentui/react-components';
import { CircleRegular } from '@fluentui/react-icons';
import { useLabelStyles } from './Label.styles';
import type { LabelProps } from './Label.types';

const defaultStart = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the size is not always medium, and I added a question to drieli if it's always gona be this icon or it's just a placeholder here https://www.figma.com/design/40dstXfCx3jpvlP4prbSBQ?node-id=6290-42144&m=dev#1548157118

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand you are saying we could pass in the badge component to Label component but also offering a default one, but I kinda think the size of the badge should not be indepedent of the label size. Like once the user specify "medium" label, we should configure the size of the badge like what figma indicates/or at least the default size would change according to the size of the label. Right now, in this setup, you could provide a extra large badge to a small label, which doesn't make sense.

<Badge
appearance="ghost"
shape="rounded"
size="medium"
icon={<CircleRegular />}
/>
);

export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
({ badge, showBadge = false, children, className, ...rest }, ref) => {
const styles = useLabelStyles();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing a few important elements from the upstream Label implementation: https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-label/library/src/components/Label/Label.tsx#L15-L21.

  1. It needs to call useLabelStyles_unstable(state);.
  2. It also needs to call useCustomStyleHook_unstable('useLabelStyles_unstable')(state); in order to pull in any custom styles, otherwise they will silently disappear when the core component is swapped for this one.

Our components should be seen purely as overrides to existing styles, so we need to make sure to not silently drop existing styles.

const badgeContent = badge ?? (showBadge ? defaultStart : null);

return (
<FluentLabel
{...rest}
ref={ref}
className={mergeClasses(styles.root, className)}
>
{badgeContent && (
<span className={styles.startSlot}>{badgeContent}</span>
)}
{children}
</FluentLabel>
);
}
) as ForwardRefComponent<LabelProps>;

Label.displayName = 'Label';
19 changes: 19 additions & 0 deletions packages/react-cap-theme/src/components/Label/Label.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from 'react';
import type {
LabelProps as FluentLabelProps,
LabelState as FluentLabelState,
} from '@fluentui/react-components';

export type LabelProps = FluentLabelProps & {
/**
* Optional leading badge rendered before the label text.
*/
badge?: React.ReactNode;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please document that we had to add this prop in the Component Notes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the current setup, should this be a Badge component type instead of React.ReactNode?

/**
* Controls whether the badge is shown when `badge` is not provided.
* @default false
*/
showBadge?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rename to showDefaultDotBadge to reduce confusion? also modify the comment accordingly

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment here: #579 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's confusing that passing showBadge={false} will not cause the badge to be hidden.

A different idea would be to change badge?: React.ReactNode | true (which is functionally equivalent to badge?: React.ReactNode, just more explicit), where passing true causes it to render the default badge?

};

export type LabelState = FluentLabelState;
2 changes: 2 additions & 0 deletions packages/react-cap-theme/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export type {
ButtonProps,
ButtonState,
} from './components/Button/Button.types';
export { Label } from './components/Label/Label';
export type { LabelProps, LabelState } from './components/Label/Label.types';
3 changes: 3 additions & 0 deletions packages/react-cap-theme/src/theme/CAPThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
CardFooterState,
CardHeaderState,
CardState,
LabelState,
FluentProvider,
FluentProviderProps,
InputState,
Expand All @@ -18,6 +19,7 @@ import { useInputStylesHook } from '../components/Input/Input.styles';
import { useCardStylesHook } from '../components/Card/Card.styles';
import { useCardHeaderStylesHook } from '../components/Card/CardHeader.styles';
import { useCardFooterStylesHook } from '../components/Card/CardFooter.styles';
import { useLabelStylesHook } from '../components/Label/Label.styles';

const customStyleHooks: NonNullable<
FluentProviderProps['customStyleHooks_unstable']
Expand All @@ -31,6 +33,7 @@ const customStyleHooks: NonNullable<
useCardFooterStyles_unstable: (state) =>
useCardFooterStylesHook(state as CardFooterState),
useInputStyles_unstable: (state) => useInputStylesHook(state as InputState),
useLabelStyles_unstable: (state) => useLabelStylesHook(state as LabelState),
};

type CAPThemeProviderProps = Omit<
Expand Down
134 changes: 134 additions & 0 deletions packages/react-cap-theme/stories/components/Label/Label.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import * as React from 'react';
import { Label, type LabelProps } from '@fluentui-contrib/react-cap-theme';
import { Badge, type BadgeProps } from '@fluentui/react-components';
import { CircleRegular } from '@fluentui/react-icons';
import { CAPThemeExamplesTable } from '../../StorybookUtils';

const DEFAULT_BADGE: Required<
Pick<BadgeProps, 'appearance' | 'shape' | 'size' | 'color'>
> = {
appearance: 'filled',
shape: 'rounded',
size: 'small',
color: 'informative',
};

type LabelStoryProps = Omit<LabelProps, 'badge'> & {
badgeVisible?: boolean;
badgeAppearance?: BadgeProps['appearance'];
badgeShape?: BadgeProps['shape'];
badgeSize?: BadgeProps['size'];
badgeColor?: BadgeProps['color'];
badgeIcon?: boolean;
};

export const CAPLabelStory = ({
badgeVisible = true,
badgeAppearance = DEFAULT_BADGE.appearance,
badgeShape = DEFAULT_BADGE.shape,
badgeSize = DEFAULT_BADGE.size,
badgeColor = DEFAULT_BADGE.color,
badgeIcon = false,
...props
}: LabelStoryProps) => {
const getBadge = () =>
badgeVisible ? (
<Badge
appearance={badgeAppearance}
shape={badgeShape}
size={badgeSize}
color={badgeColor}
icon={<CircleRegular />}
></Badge>
) : undefined;

return (
<CAPThemeExamplesTable
examples={[
{
title: 'Default',
render() {
return (
<Label {...props} badge={getBadge()}>
Label
</Label>
);
},
},
{
title: 'Required',
render() {
return (
<Label required {...props} badge={getBadge()}>
Required Label
</Label>
);
},
},
{
title: 'Disabled',
render() {
return (
<Label disabled {...props} badge={getBadge()}>
Disabled Label
</Label>
);
},
},
]}
/>
);
};

CAPLabelStory.argTypes = {
size: {
options: ['small', 'medium', 'large'],
control: { type: 'radio' },
},
weight: {
options: ['regular', 'semibold'],
control: { type: 'radio' },
},
badgeVisible: {
control: { type: 'boolean' },
},
badgeAppearance: {
options: ['filled', 'ghost', 'outline', 'tint'],
control: { type: 'radio' },
},
badgeShape: {
options: ['rounded', 'circular', 'square'],
control: { type: 'radio' },
},
badgeSize: {
options: ['tiny', 'extra-small', 'small', 'medium', 'large', 'extra-large'],
control: { type: 'radio' },
},
badgeColor: {
options: [
'brand',
'danger',
'important',
'informative',
'severe',
'subtle',
'success',
'warning',
],
control: { type: 'radio' },
},
badgeIcon: {
control: { type: 'boolean' },
},
};

CAPLabelStory.args = {
size: 'medium',
weight: 'regular',
badgeVisible: true,
badgeAppearance: DEFAULT_BADGE.appearance,
badgeShape: DEFAULT_BADGE.shape,
badgeSize: DEFAULT_BADGE.size,
badgeColor: DEFAULT_BADGE.color,
badgeIcon: false,
};
1 change: 1 addition & 0 deletions packages/react-cap-theme/stories/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Meta } from '@storybook/react';
export { CAPBadgeStory as Badge } from './components/Badge.stories';
export { CAPButtonStory as Button } from './components/Button.stories';
export { CAPCardStory as Card } from './components/Card.stories';
export { CAPLabelStory as Label } from './components/Label/Label.stories';
export { CAPInputStory as Input } from './components/Input.stories';
export { CAPMenuStory as Menu } from './components/Menu.stories';
export { CAPTooltipStory as Tooltip } from './components/Tooltip.stories';
Expand Down