diff --git a/change/@fluentui-contrib-react-cap-theme-2b7ce8df-bcb4-4c18-8494-634da4010923.json b/change/@fluentui-contrib-react-cap-theme-2b7ce8df-bcb4-4c18-8494-634da4010923.json new file mode 100644 index 00000000..5f779f81 --- /dev/null +++ b/change/@fluentui-contrib-react-cap-theme-2b7ce8df-bcb4-4c18-8494-634da4010923.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Generated files for Label component", + "packageName": "@fluentui-contrib/react-cap-theme", + "email": "olkatruk@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-cap-theme/src/components/Label/Label.styles.ts b/packages/react-cap-theme/src/components/Label/Label.styles.ts new file mode 100644 index 00000000..1fc32a02 --- /dev/null +++ b/packages/react-cap-theme/src/components/Label/Label.styles.ts @@ -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, + }, + 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; +} diff --git a/packages/react-cap-theme/src/components/Label/Label.tsx b/packages/react-cap-theme/src/components/Label/Label.tsx new file mode 100644 index 00000000..5081c402 --- /dev/null +++ b/packages/react-cap-theme/src/components/Label/Label.tsx @@ -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 = ( + } + /> +); + +export const Label = React.forwardRef( + ({ badge, showBadge = false, children, className, ...rest }, ref) => { + const styles = useLabelStyles(); + const badgeContent = badge ?? (showBadge ? defaultStart : null); + + return ( + + {badgeContent && ( + {badgeContent} + )} + {children} + + ); + } +) as ForwardRefComponent; + +Label.displayName = 'Label'; diff --git a/packages/react-cap-theme/src/components/Label/Label.types.ts b/packages/react-cap-theme/src/components/Label/Label.types.ts new file mode 100644 index 00000000..1bce2b2c --- /dev/null +++ b/packages/react-cap-theme/src/components/Label/Label.types.ts @@ -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; + /** + * Controls whether the badge is shown when `badge` is not provided. + * @default false + */ + showBadge?: boolean; +}; + +export type LabelState = FluentLabelState; diff --git a/packages/react-cap-theme/src/index.ts b/packages/react-cap-theme/src/index.ts index d4b15be2..e5039598 100644 --- a/packages/react-cap-theme/src/index.ts +++ b/packages/react-cap-theme/src/index.ts @@ -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'; diff --git a/packages/react-cap-theme/src/theme/CAPThemeProvider.tsx b/packages/react-cap-theme/src/theme/CAPThemeProvider.tsx index b708a23e..a413c52f 100644 --- a/packages/react-cap-theme/src/theme/CAPThemeProvider.tsx +++ b/packages/react-cap-theme/src/theme/CAPThemeProvider.tsx @@ -5,6 +5,7 @@ import { CardFooterState, CardHeaderState, CardState, + LabelState, FluentProvider, FluentProviderProps, InputState, @@ -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'] @@ -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< diff --git a/packages/react-cap-theme/stories/components/Label/Label.stories.tsx b/packages/react-cap-theme/stories/components/Label/Label.stories.tsx new file mode 100644 index 00000000..232a6e78 --- /dev/null +++ b/packages/react-cap-theme/stories/components/Label/Label.stories.tsx @@ -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 +> = { + appearance: 'filled', + shape: 'rounded', + size: 'small', + color: 'informative', +}; + +type LabelStoryProps = Omit & { + 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 ? ( + } + > + ) : undefined; + + return ( + + Label + + ); + }, + }, + { + title: 'Required', + render() { + return ( + + ); + }, + }, + { + title: 'Disabled', + render() { + return ( + + ); + }, + }, + ]} + /> + ); +}; + +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, +}; diff --git a/packages/react-cap-theme/stories/index.stories.tsx b/packages/react-cap-theme/stories/index.stories.tsx index d4a7829d..64be10ae 100644 --- a/packages/react-cap-theme/stories/index.stories.tsx +++ b/packages/react-cap-theme/stories/index.stories.tsx @@ -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';