diff --git a/change/@fluentui-contrib-react-cap-theme-90c43f06-4a43-493e-a1c2-0cb63a2feb0a.json b/change/@fluentui-contrib-react-cap-theme-90c43f06-4a43-493e-a1c2-0cb63a2feb0a.json new file mode 100644 index 00000000..fb46ac10 --- /dev/null +++ b/change/@fluentui-contrib-react-cap-theme-90c43f06-4a43-493e-a1c2-0cb63a2feb0a.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "publish component updates", + "packageName": "@fluentui-contrib/react-cap-theme", + "email": "dzukowski@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-cap-theme/.storybook/main.ts b/packages/react-cap-theme/.storybook/main.ts index 521468ce..9f7d90b3 100644 --- a/packages/react-cap-theme/.storybook/main.ts +++ b/packages/react-cap-theme/.storybook/main.ts @@ -1,6 +1,5 @@ import type { StorybookConfig } from '@storybook/react-webpack5'; -// eslint-disable-next-line @nx/enforce-module-boundaries import rootConfig from '../../../.storybook/main'; const config: StorybookConfig = { diff --git a/packages/react-cap-theme/.storybook/preview.tsx b/packages/react-cap-theme/.storybook/preview.tsx index 39683877..2b8a55ac 100644 --- a/packages/react-cap-theme/.storybook/preview.tsx +++ b/packages/react-cap-theme/.storybook/preview.tsx @@ -1,61 +1,9 @@ -import * as React from 'react'; -import type { Decorator, Preview } from '@storybook/react'; +import type { Preview } from '@storybook/react'; -// eslint-disable-next-line @nx/enforce-module-boundaries import rootPreview from '../../../.storybook/preview'; -import { CAPThemeSelectionProvider } from '../stories/StorybookUtils'; - -const withCAPTheme: Decorator = (Story, context) => { - const themeKey = - (context.globals.capTheme as - | 'current' - | 'teams' - | 'onedrive' - | 'sharepoint') ?? 'current'; - return ( - - - - ); -}; - -const rootDecorators = rootPreview.decorators; -const normalizedDecorators: Decorator[] = Array.isArray(rootDecorators) - ? rootDecorators - : rootDecorators - ? [rootDecorators] - : []; const preview: Preview = { ...rootPreview, - decorators: [...normalizedDecorators, withCAPTheme], - parameters: { - ...rootPreview.parameters, - controls: { - ...(rootPreview.parameters?.controls ?? {}), - disable: false, - expanded: true, - }, - }, - globalTypes: { - ...(rootPreview.globalTypes ?? {}), - capTheme: { - name: 'Themes', - description: 'Select the theme for all CAP stories', - defaultValue: 'default', - toolbar: { - icon: 'paintbrush', - showName: true, - dynamicTitle: true, - items: [ - { value: 'default', title: 'Default' }, - { value: 'teams', title: 'CAP (Teams)' }, - { value: 'onedrive', title: 'CAP (OneDrive)' }, - { value: 'sharepoint', title: 'CAP (SharePoint)' }, - ], - }, - }, - }, tags: ['autodocs'], }; diff --git a/packages/react-cap-theme/eslint.config.js b/packages/react-cap-theme/eslint.config.js index 2be3ed1b..5ea41166 100644 --- a/packages/react-cap-theme/eslint.config.js +++ b/packages/react-cap-theme/eslint.config.js @@ -12,4 +12,10 @@ module.exports = [ // Override or add rules here rules: {}, }, + { + files: ['**/*.ts', '**/*.tsx'], + rules: { + '@nx/enforce-module-boundaries': 'off', + }, + }, ]; diff --git a/packages/react-cap-theme/src/components/Badge/Badge.styles.ts b/packages/react-cap-theme/src/components/Badge/Badge.styles.ts deleted file mode 100644 index 09aeed2a..00000000 --- a/packages/react-cap-theme/src/components/Badge/Badge.styles.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { - makeStyles, - mergeClasses, - shorthands, - tokens, - type BadgeState, - useBadgeStyles_unstable, -} from '@fluentui/react-components'; -import * as React from 'react'; - -import { CAP_TOKENS } from '../../theme/CAPTheme'; - -// Copied from Fluent's badge.styles.ts: we are only overriding a few styles, we don't want to break the styles we don't override -// so we follow the existing pattern here -// The text content of the badge has additional horizontal padding, but there is no `text` slot to add that padding to. -// Instead, add extra padding to the root, and a negative margin on the icon to "remove" the extra padding on the icon. -const textPadding = tokens.spacingHorizontalXXS; - -export const useBadgeStyles = makeStyles({ - root: { - padding: `0 calc(${CAP_TOKENS['cap/badge-m/padding']} + ${textPadding})`, - }, - - tiny: { - padding: 'unset', - }, - - 'extra-small': { - padding: 'unset', - }, - - small: { - padding: `0 calc(${CAP_TOKENS['cap/badge-s/padding']} + ${textPadding})`, - }, - - medium: { - // Set by root - }, - - large: { - padding: `0 calc(${CAP_TOKENS['cap/badge-l/padding']} + ${textPadding})`, - }, - - 'extra-large': { - padding: `0 calc(${CAP_TOKENS['cap/badge-xl/padding']} + ${textPadding})`, - }, - - // shape - 'rounded-extra-large': { - borderRadius: CAP_TOKENS['cap/badge-xl/corner-rounded'], - }, - 'rounded-large': { borderRadius: CAP_TOKENS['cap/badge-l/corner-rounded'] }, - - 'outline-brand': { - ...shorthands.borderColor(CAP_TOKENS['cap/badge/brand/outlined/stroke']), - }, - 'outline-warning': { - ...shorthands.borderColor(CAP_TOKENS['cap/badge/warning/outlined/stroke']), - }, - 'outline-important': { - ...shorthands.borderColor( - CAP_TOKENS['cap/badge/important/outlined/stroke'] - ), - }, - 'outline-danger': { - ...shorthands.borderColor(CAP_TOKENS['cap/badge/danger/outlined/stroke']), - }, - 'outline-success': { - ...shorthands.borderColor(CAP_TOKENS['cap/badge/success/outlined/stroke']), - }, - 'outline-informative': { - ...shorthands.borderColor( - CAP_TOKENS['cap/badge/informative/outlined/stroke'] - ), - }, - 'outline-subtle': { - color: CAP_TOKENS['cap/badge/subtle/outlined/foreground'], - ...shorthands.borderColor( - CAP_TOKENS['cap/badge/subtle/outlined/foreground'] - ), - }, - - 'tint-brand': { - color: CAP_TOKENS['cap/badge/brand/tint/foreground'], - }, - - 'ghost-brand': { - color: CAP_TOKENS['cap/badge/brand/ghost/foreground'], - }, - - 'filled-warning': { - color: CAP_TOKENS['cap/badge/warning/filled/foreground'], - backgroundColor: CAP_TOKENS['cap/badge/warning/filled/background'], - }, - - 'tint-informative': { - backgroundColor: CAP_TOKENS['cap/badge/informative/tint/background'], - ...shorthands.borderColor(CAP_TOKENS['cap/badge/informative/tint/stroke']), - }, - - 'filled-important': { - backgroundColor: CAP_TOKENS['cap/badge/important/filled/background'], - color: CAP_TOKENS['cap/badge/important/filled/foreground'], - }, - - 'tint-important': { - backgroundColor: CAP_TOKENS['cap/badge/important/tint/background'], - color: CAP_TOKENS['cap/badge/important/tint/foreground'], - ...shorthands.borderColor(CAP_TOKENS['cap/badge/important/tint/stroke']), - }, - - 'filled-subtle': { - color: CAP_TOKENS['cap/badge/subtle/filled/foreground'], - backgroundColor: CAP_TOKENS['cap/badge/subtle/filled/background'], - }, - - 'tint-subtle': { - ...shorthands.borderColor(CAP_TOKENS['cap/badge/subtle/tint/stroke']), - }, -}); - -const useBadgeIconStyles = makeStyles({ - beforeTextSmall: { - marginRight: `calc(${CAP_TOKENS['cap/badge-s/gap']} + ${textPadding})`, - }, - afterTextSmall: { - marginLeft: `calc(${CAP_TOKENS['cap/badge-s/gap-toSecondaryIcon']} + ${textPadding})`, - }, -}); - -export function useBadgeStylesHook(state: BadgeState): BadgeState { - // Apply base Badge styles first - useBadgeStyles_unstable(state); - - // Then override with CAP styles - const styles = useBadgeStyles(); - const iconStyles = useBadgeIconStyles(); - - state.root.className = mergeClasses( - state.root.className, - styles.root, - styles[state.size], - state.shape === 'rounded' && - `rounded-${state.size}` in styles && - styles[`rounded-${state.size}` as keyof typeof styles], - `${state.appearance}-${state.color}` in styles && - styles[`${state.appearance}-${state.color}` as keyof typeof styles] - ); - - // Copied from Fluent: Handle the edge case where children is 0 (a falsy value that should still render text and have margin) - if (React.Children.toArray(state.root.children).length > 0) { - // Override icon spacing for small size - if (state.icon && state.size === 'small') { - const iconPositionClass = - state.iconPosition === 'after' - ? iconStyles.afterTextSmall - : iconStyles.beforeTextSmall; - state.icon.className = mergeClasses( - state.icon.className, - iconPositionClass - ); - } - } - - return state; -} diff --git a/packages/react-cap-theme/src/components/Button/Button.styles.ts b/packages/react-cap-theme/src/components/Button/Button.styles.ts deleted file mode 100644 index c6ec8b9b..00000000 --- a/packages/react-cap-theme/src/components/Button/Button.styles.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { makeStyles, mergeClasses } from '@fluentui/react-components'; -import type { ButtonState } from './Button.types'; -import { CAP_TOKENS } from '../../theme/CAPTheme'; - -export const useButtonStyles = makeStyles({ - root: { - borderRadius: CAP_TOKENS['fixme/ctrl/button/corner-radius'], - }, - primary: { - backgroundColor: CAP_TOKENS['fixme/ctrl/button/primary-background-color'], - - ':hover': { - backgroundColor: - CAP_TOKENS['fixme/ctrl/button/primary-background-color-hover'], - }, - }, - secondary: { - backgroundColor: CAP_TOKENS['fixme/ctrl/button/secondary-background-color'], - - ':hover': { - backgroundColor: - CAP_TOKENS['fixme/ctrl/button/secondary-background-color-hover'], - }, - }, - outline: { - backgroundColor: CAP_TOKENS['fixme/ctrl/button/outline-background-color'], - - ':hover': { - backgroundColor: - CAP_TOKENS['fixme/ctrl/button/outline-background-color-hover'], - }, - }, - subtle: {}, - tint: { - backgroundColor: CAP_TOKENS['fixme/ctrl/button/tint-background-color'], - - ':hover': { - backgroundColor: - CAP_TOKENS['fixme/ctrl/button/tint-background-color-hover'], - }, - }, - transparent: {}, -}); - -export function useButtonStylesHook(state: ButtonState): ButtonState { - const styles = useButtonStyles(); - - state.root.className = mergeClasses( - state.root.className, - styles.root, - state.appearance && styles[state.appearance] - ); - - return state; -} diff --git a/packages/react-cap-theme/src/components/Button/Button.tsx b/packages/react-cap-theme/src/components/Button/Button.tsx deleted file mode 100644 index a2c6c5e4..00000000 --- a/packages/react-cap-theme/src/components/Button/Button.tsx +++ /dev/null @@ -1,9 +0,0 @@ -'use client'; - -import type { ForwardRefComponent } from '@fluentui/react-utilities'; -import { Button as FluentButton } from '@fluentui/react-components'; -import type { ButtonProps } from './Button.types'; - -export const Button = FluentButton as ForwardRefComponent; - -Button.displayName = 'Button'; diff --git a/packages/react-cap-theme/src/components/Button/Button.types.ts b/packages/react-cap-theme/src/components/Button/Button.types.ts deleted file mode 100644 index a27f0a24..00000000 --- a/packages/react-cap-theme/src/components/Button/Button.types.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { - ButtonProps as FluentButtonProps, - ButtonState as FluentButtonState, -} from '@fluentui/react-components'; - -export type ButtonAppearance = - | NonNullable - | 'tint'; - -export type ButtonProps = Omit & { - appearance?: ButtonAppearance; -}; - -export type ButtonState = Omit & { - appearance?: ButtonAppearance; -}; diff --git a/packages/react-cap-theme/src/components/Card/Card.styles.ts b/packages/react-cap-theme/src/components/Card/Card.styles.ts deleted file mode 100644 index 49b7fb1c..00000000 --- a/packages/react-cap-theme/src/components/Card/Card.styles.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - makeStyles, - mergeClasses, - type CardState, -} from '@fluentui/react-components'; -import { CAP_TOKENS } from '../../theme/CAPTheme'; - -export const useCardStyles = makeStyles({ - root: { - borderRadius: CAP_TOKENS['fixme/ctrl/card/corner-radius'], - }, -}); - -export function useCardStylesHook(state: CardState): CardState { - const styles = useCardStyles(); - - state.root.className = mergeClasses(state.root.className, styles.root); - - return state; -} diff --git a/packages/react-cap-theme/src/components/Card/CardFooter.styles.ts b/packages/react-cap-theme/src/components/Card/CardFooter.styles.ts deleted file mode 100644 index 8d6a5ed6..00000000 --- a/packages/react-cap-theme/src/components/Card/CardFooter.styles.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - makeStyles, - mergeClasses, - type CardFooterState, -} from '@fluentui/react-components'; - -export const useCardFooterStyles = makeStyles({ - root: {}, -}); - -export function useCardFooterStylesHook( - state: CardFooterState -): CardFooterState { - const styles = useCardFooterStyles(); - - state.root.className = mergeClasses(state.root.className, styles.root); - - return state; -} diff --git a/packages/react-cap-theme/src/components/Card/CardHeader.styles.ts b/packages/react-cap-theme/src/components/Card/CardHeader.styles.ts deleted file mode 100644 index 46373c2c..00000000 --- a/packages/react-cap-theme/src/components/Card/CardHeader.styles.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - makeStyles, - mergeClasses, - type CardHeaderState, -} from '@fluentui/react-components'; - -export const useCardHeaderStyles = makeStyles({ - root: {}, -}); - -export function useCardHeaderStylesHook( - state: CardHeaderState -): CardHeaderState { - const styles = useCardHeaderStyles(); - - state.root.className = mergeClasses(state.root.className, styles.root); - - return state; -} diff --git a/packages/react-cap-theme/src/components/Dialog/DialogBody.styles.ts b/packages/react-cap-theme/src/components/Dialog/DialogBody.styles.ts deleted file mode 100644 index 4b7fba5b..00000000 --- a/packages/react-cap-theme/src/components/Dialog/DialogBody.styles.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - makeStyles, - mergeClasses, - type DialogBodyState, - useDialogBodyStyles_unstable, -} from '@fluentui/react-components'; - -import { CAP_TOKENS } from '../../theme/CAPTheme'; - -export const useDialogBodyStyles = makeStyles({ - root: { - gap: CAP_TOKENS['cap/Dialog/Header/Gap'], - }, -}); - -export function useDialogBodyStylesHook( - state: DialogBodyState -): DialogBodyState { - // Apply base DialogBody styles first - useDialogBodyStyles_unstable(state); - - // Then override with CAP styles - const styles = useDialogBodyStyles(); - - state.root.className = mergeClasses(state.root.className, styles.root); - - return state; -} diff --git a/packages/react-cap-theme/src/components/Dialog/DialogSurface.styles.ts b/packages/react-cap-theme/src/components/Dialog/DialogSurface.styles.ts deleted file mode 100644 index edfb3f02..00000000 --- a/packages/react-cap-theme/src/components/Dialog/DialogSurface.styles.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - makeStyles, - mergeClasses, - type DialogSurfaceState, - useDialogSurfaceStyles_unstable, -} from '@fluentui/react-components'; - -import { CAP_TOKENS } from '../../theme/CAPTheme'; - -export const useDialogSurfaceStyles = makeStyles({ - root: { - borderRadius: CAP_TOKENS['cap/Dialog/Corner'], - border: `${CAP_TOKENS['cap/Dialog/strokeWidth']} solid ${CAP_TOKENS['cap/Dialog/strokeColor']}`, - }, -}); - -export function useDialogSurfaceStylesHook( - state: DialogSurfaceState -): DialogSurfaceState { - // Apply base DialogSurface styles first - useDialogSurfaceStyles_unstable(state); - - // Then override with CAP styles - const styles = useDialogSurfaceStyles(); - - state.root.className = mergeClasses(state.root.className, styles.root); - - return state; -} diff --git a/packages/react-cap-theme/src/components/Drawer/Drawer.styles.ts b/packages/react-cap-theme/src/components/Drawer/Drawer.styles.ts deleted file mode 100644 index 21365892..00000000 --- a/packages/react-cap-theme/src/components/Drawer/Drawer.styles.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - makeStyles, - mergeClasses, - type DrawerState, -} from '@fluentui/react-components'; -import { CAP_TOKENS } from '../../theme/CAPTheme'; - -export const useDrawerStyles = makeStyles({ - root: { - borderRadius: CAP_TOKENS['cap/ctrl/flyout/base-corner'], - boxShadow: CAP_TOKENS['cap/ctrl/flyout/Elevation'], - }, -}); - -export function useDrawerStylesHook(state: DrawerState): DrawerState { - const styles = useDrawerStyles(); - - state.root.className = mergeClasses(state.root.className, styles.root); - - return state; -} diff --git a/packages/react-cap-theme/src/components/Drawer/DrawerBody.styles.ts b/packages/react-cap-theme/src/components/Drawer/DrawerBody.styles.ts deleted file mode 100644 index 9e0e3386..00000000 --- a/packages/react-cap-theme/src/components/Drawer/DrawerBody.styles.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - makeStyles, - mergeClasses, - tokens, - type DrawerBodyState, -} from '@fluentui/react-components'; -import { CAP_TOKENS } from '../../theme/CAPTheme'; - -export const useDrawerBodyStyles = makeStyles({ - root: { - paddingLeft: CAP_TOKENS['cap/ctrl/flyout/body-Padding-Left'], - paddingRight: CAP_TOKENS['cap/ctrl/flyout/body-Padding-Right'], - }, -}); - -export function useDrawerBodyStylesHook( - state: DrawerBodyState -): DrawerBodyState { - const styles = useDrawerBodyStyles(); - - state.root.className = mergeClasses(state.root.className, styles.root); - - return state; -} diff --git a/packages/react-cap-theme/src/components/Drawer/DrawerFooter.styles.ts b/packages/react-cap-theme/src/components/Drawer/DrawerFooter.styles.ts deleted file mode 100644 index 65611ee4..00000000 --- a/packages/react-cap-theme/src/components/Drawer/DrawerFooter.styles.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - makeStyles, - mergeClasses, - type DrawerFooterState, -} from '@fluentui/react-components'; -import { CAP_TOKENS } from '../../theme/CAPTheme'; - -export const useDrawerFooterStyles = makeStyles({ - root: { - justifyContent: CAP_TOKENS['fixme/ctrl/drawer/footer/content-alignment'], - paddingTop: CAP_TOKENS['cap/ctrl/flyout/Footer-Padding-top'], - paddingRight: CAP_TOKENS['cap/ctrl/flyout/Footer-Padding-bottom'], - paddingBottom: CAP_TOKENS['cap/ctrl/flyout/footer-Padding-Left'], - paddingLeft: CAP_TOKENS['cap/ctrl/flyout/footer-Padding-Right'], - }, -}); - -export function useDrawerFooterStylesHook( - state: DrawerFooterState -): DrawerFooterState { - const styles = useDrawerFooterStyles(); - - state.root.className = mergeClasses(state.root.className, styles.root); - - return state; -} diff --git a/packages/react-cap-theme/src/components/Drawer/DrawerHeader.styles.ts b/packages/react-cap-theme/src/components/Drawer/DrawerHeader.styles.ts deleted file mode 100644 index 06a0c7d1..00000000 --- a/packages/react-cap-theme/src/components/Drawer/DrawerHeader.styles.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - makeStyles, - mergeClasses, - tokens, - type DrawerHeaderState, -} from '@fluentui/react-components'; -import { CAP_TOKENS } from '../../theme/CAPTheme'; - -export const useDrawerHeaderStyles = makeStyles({ - root: { - paddingTop: CAP_TOKENS['smtc/v1/ctrl/flyout/header/paddingTop'], - paddingRight: CAP_TOKENS['smtc/v1/ctrl/flyout/header/Padding-Right'], - paddingBottom: CAP_TOKENS['fixme/ctrl/drawer/header/padding-bottom'], - paddingLeft: CAP_TOKENS['smtc/v1/ctrl/flyout/header/Padding-Left'], - }, -}); - -export function useDrawerHeaderStylesHook( - state: DrawerHeaderState -): DrawerHeaderState { - const styles = useDrawerHeaderStyles(); - - state.root.className = mergeClasses(state.root.className, styles.root); - - return state; -} diff --git a/packages/react-cap-theme/src/components/Drawer/DrawerHeaderNavigation.styles.ts b/packages/react-cap-theme/src/components/Drawer/DrawerHeaderNavigation.styles.ts deleted file mode 100644 index 9a308dd0..00000000 --- a/packages/react-cap-theme/src/components/Drawer/DrawerHeaderNavigation.styles.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - makeStyles, - mergeClasses, - type DrawerHeaderNavigationState, -} from '@fluentui/react-components'; - -export const useDrawerHeaderNavigationStyles = makeStyles({ - root: {}, -}); - -export function useDrawerHeaderNavigationStylesHook( - state: DrawerHeaderNavigationState -): DrawerHeaderNavigationState { - const styles = useDrawerHeaderNavigationStyles(); - - state.root.className = mergeClasses(state.root.className, styles.root); - - return state; -} diff --git a/packages/react-cap-theme/src/components/Drawer/DrawerHeaderTitle.styles.ts b/packages/react-cap-theme/src/components/Drawer/DrawerHeaderTitle.styles.ts deleted file mode 100644 index f6fc2b88..00000000 --- a/packages/react-cap-theme/src/components/Drawer/DrawerHeaderTitle.styles.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - makeStyles, - mergeClasses, - type DrawerHeaderTitleState, -} from '@fluentui/react-components'; - -export const useDrawerHeaderTitleStyles = makeStyles({ - root: {}, - heading: {}, - action: {}, -}); - -export function useDrawerHeaderTitleStylesHook( - state: DrawerHeaderTitleState -): DrawerHeaderTitleState { - const styles = useDrawerHeaderTitleStyles(); - - state.root.className = mergeClasses(state.root.className, styles.root); - - if (state.heading) { - state.heading.className = mergeClasses( - state.heading.className, - styles.heading - ); - } - - if (state.action) { - state.action.className = mergeClasses( - state.action.className, - styles.action - ); - } - - return state; -} diff --git a/packages/react-cap-theme/src/components/Drawer/InlineDrawer.styles.ts b/packages/react-cap-theme/src/components/Drawer/InlineDrawer.styles.ts deleted file mode 100644 index 1ddea5c0..00000000 --- a/packages/react-cap-theme/src/components/Drawer/InlineDrawer.styles.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - makeStyles, - mergeClasses, - type InlineDrawerState, -} from '@fluentui/react-components'; - -export const useInlineDrawerStyles = makeStyles({ - root: {}, -}); - -export function useInlineDrawerStylesHook( - state: InlineDrawerState -): InlineDrawerState { - const styles = useInlineDrawerStyles(); - - state.root.className = mergeClasses(state.root.className, styles.root); - - return state; -} diff --git a/packages/react-cap-theme/src/components/Drawer/OverlayDrawer.styles.ts b/packages/react-cap-theme/src/components/Drawer/OverlayDrawer.styles.ts deleted file mode 100644 index f8c15699..00000000 --- a/packages/react-cap-theme/src/components/Drawer/OverlayDrawer.styles.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - makeStyles, - mergeClasses, - type OverlayDrawerState, -} from '@fluentui/react-components'; - -export const useOverlayDrawerStyles = makeStyles({ - root: {}, -}); - -export function useOverlayDrawerStylesHook( - state: OverlayDrawerState -): OverlayDrawerState { - const styles = useOverlayDrawerStyles(); - - state.root.className = mergeClasses(state.root.className, styles.root); - - return state; -} diff --git a/packages/react-cap-theme/src/components/Input/Input.styles.ts b/packages/react-cap-theme/src/components/Input/Input.styles.ts deleted file mode 100644 index 67e85b21..00000000 --- a/packages/react-cap-theme/src/components/Input/Input.styles.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - makeStyles, - mergeClasses, - type InputState, -} from '@fluentui/react-components'; - -export const useInputStyles = makeStyles({ - root: {}, -}); - -export function useInputStylesHook(state: InputState): InputState { - const styles = useInputStyles(); - - state.root.className = mergeClasses(state.root.className, styles.root); - - return state; -} diff --git a/packages/react-cap-theme/src/components/react-button/Button.ts b/packages/react-cap-theme/src/components/react-button/Button.ts new file mode 100644 index 00000000..b11788d2 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/Button.ts @@ -0,0 +1,13 @@ +export { Button } from './components/Button/Button'; +export { renderButton } from './components/Button/renderButton'; +export { useButton } from './components/Button/useButton'; +export { + buttonClassNames, + useButtonStyles, +} from './components/Button/useButtonStyles.styles'; +export type { + ButtonProps, + ButtonSlots, + ButtonState, + ButtonAppearance, +} from './components/Button/Button.types'; diff --git a/packages/react-cap-theme/src/components/react-button/MenuButton.ts b/packages/react-cap-theme/src/components/react-button/MenuButton.ts new file mode 100644 index 00000000..08de201c --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/MenuButton.ts @@ -0,0 +1,12 @@ +export { MenuButton } from './components/MenuButton/MenuButton'; +export { renderMenuButton } from './components/MenuButton/renderMenuButton'; +export { useMenuButton } from './components/MenuButton/useMenuButton'; +export { + menuButtonClassNames, + useMenuButtonStyles, +} from './components/MenuButton/useMenuButtonStyles.styles'; +export type { + MenuButtonProps, + MenuButtonSlots, + MenuButtonState, +} from './components/MenuButton/MenuButton.types'; diff --git a/packages/react-cap-theme/src/components/react-button/SplitButton.ts b/packages/react-cap-theme/src/components/react-button/SplitButton.ts new file mode 100644 index 00000000..9973ef64 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/SplitButton.ts @@ -0,0 +1,12 @@ +export { SplitButton } from './components/SplitButton/SplitButton'; +export { renderSplitButton } from './components/SplitButton/renderSplitButton'; +export { useSplitButton } from './components/SplitButton/useSplitButton'; +export { + splitButtonClassNames, + useSplitButtonStyles, +} from './components/SplitButton/useSplitButtonStyles.styles'; +export type { + SplitButtonProps, + SplitButtonSlots, + SplitButtonState, +} from './components/SplitButton/SplitButton.types'; diff --git a/packages/react-cap-theme/src/components/react-button/ToggleButton.ts b/packages/react-cap-theme/src/components/react-button/ToggleButton.ts new file mode 100644 index 00000000..2d261e8c --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/ToggleButton.ts @@ -0,0 +1,11 @@ +export { ToggleButton } from './components/ToggleButton/ToggleButton'; +export { renderToggleButton } from './components/ToggleButton/renderToggleButton'; +export { useToggleButton } from './components/ToggleButton/useToggleButton'; +export { + toggleButtonClassNames, + useToggleButtonStyles, +} from './components/ToggleButton/useToggleButtonStyles.styles'; +export type { + ToggleButtonProps, + ToggleButtonState, +} from './components/ToggleButton/ToggleButton.types'; diff --git a/packages/react-cap-theme/src/components/react-button/components/Button/Button.tsx b/packages/react-cap-theme/src/components/react-button/components/Button/Button.tsx new file mode 100644 index 00000000..1f9ce074 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/Button/Button.tsx @@ -0,0 +1,20 @@ +import { useButtonStyles_unstable } from '@fluentui/react-button'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import * as React from 'react'; +import type { ButtonProps } from './Button.types'; +import { toBaseState } from './Button.utils'; +import { renderButton } from './renderButton'; +import { useButton } from './useButton'; +import { useButtonStyles } from './useButtonStyles.styles'; + +export const Button: ForwardRefComponent = React.forwardRef( + (props, ref) => { + const state = useButton(props, ref); + useButtonStyles_unstable(toBaseState(state)); + useButtonStyles(state); + return renderButton(state); + // Casting is required due to lack of distributive union to support unions on @types/react + } +) as ForwardRefComponent; + +Button.displayName = 'Button'; diff --git a/packages/react-cap-theme/src/components/react-button/components/Button/Button.types.ts b/packages/react-cap-theme/src/components/react-button/components/Button/Button.types.ts new file mode 100644 index 00000000..c01d663b --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/Button/Button.types.ts @@ -0,0 +1,28 @@ +import type { + ButtonSlots, + ButtonProps as BaseButtonProps, + ButtonState as BaseButtonState, +} from '@fluentui/react-button'; +import type { ComponentProps } from '@fluentui/react-utilities'; + +export type { ButtonSlots } from '@fluentui/react-button'; + +export type ButtonAppearance = + | 'primary' + | 'tint' + | 'outline' + | 'outlineColor' + | 'secondary' + | 'subtle' + | 'transparent'; + +export type ButtonProps = ComponentProps & + Pick< + BaseButtonProps, + 'disabledFocusable' | 'disabled' | 'iconPosition' | 'size' + > & { + appearance?: ButtonAppearance; + }; + +export type ButtonState = Omit & + Required>; diff --git a/packages/react-cap-theme/src/components/react-button/components/Button/Button.utils.ts b/packages/react-cap-theme/src/components/react-button/components/Button/Button.utils.ts new file mode 100644 index 00000000..2f7508d8 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/Button/Button.utils.ts @@ -0,0 +1,32 @@ +import type { + ButtonState as BaseButtonState, + ButtonProps as BaseButtonProps, +} from '@fluentui/react-button'; +import type { + ButtonAppearance, + ButtonProps, + ButtonState, +} from './Button.types'; + +export const baseAppearanceMap: Record< + ButtonAppearance, + BaseButtonProps['appearance'] +> = { + secondary: 'secondary', + primary: 'primary', + outline: 'outline', + outlineColor: 'outline', + subtle: 'subtle', + transparent: 'transparent', + tint: 'primary', +}; + +export const toBaseProps = (props: ButtonProps): BaseButtonProps => ({ + ...props, + appearance: props.appearance && baseAppearanceMap[props.appearance], +}); + +export const toBaseState = (state: ButtonState): BaseButtonState => ({ + ...state, + appearance: baseAppearanceMap[state.appearance] ?? 'secondary', +}); diff --git a/packages/react-cap-theme/src/components/react-button/components/Button/renderButton.tsx b/packages/react-cap-theme/src/components/react-button/components/Button/renderButton.tsx new file mode 100644 index 00000000..8708093e --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/Button/renderButton.tsx @@ -0,0 +1,12 @@ +import { + renderButton_unstable, + type ButtonState as BaseButtonState, +} from '@fluentui/react-button'; +import type { ReactElement } from 'react'; +import type { ButtonState } from './Button.types'; +import { toBaseState } from './Button.utils'; + +export const renderButton = (state: ButtonState): ReactElement => { + const baseState: BaseButtonState = toBaseState(state); + return renderButton_unstable(baseState); +}; diff --git a/packages/react-cap-theme/src/components/react-button/components/Button/useButton.ts b/packages/react-cap-theme/src/components/react-button/components/Button/useButton.ts new file mode 100644 index 00000000..6f284a16 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/Button/useButton.ts @@ -0,0 +1,15 @@ +import { useButton_unstable as useBaseState } from '@fluentui/react-button'; +import type { ButtonProps, ButtonState } from './Button.types'; +import { toBaseProps } from './Button.utils'; + +export const useButton = ( + props: ButtonProps, + ref: React.Ref +): ButtonState => { + const appearance = props.appearance ?? 'secondary'; + + return { + ...useBaseState(toBaseProps(props), ref), + appearance, + } as ButtonState; +}; diff --git a/packages/react-cap-theme/src/components/react-button/components/Button/useButtonStyles.styles.ts b/packages/react-cap-theme/src/components/react-button/components/Button/useButtonStyles.styles.ts new file mode 100644 index 00000000..052b3446 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/Button/useButtonStyles.styles.ts @@ -0,0 +1,278 @@ +import { createCustomFocusIndicatorStyle } from '@fluentui/react-tabster'; +import type { SlotClassNames } from '@fluentui/react-utilities'; +import { tokens, typographyStyles } from '../../../tokens'; +import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; +import type { ButtonSlots, ButtonState } from './Button.types'; + +export const buttonClassNames: SlotClassNames = { + root: 'fui-Button', + icon: 'fui-Button__icon', +}; + +const BORDER_WIDTH = tokens.strokeWidthThin; +const buttonSpacingSmall = `calc(${tokens.spacingVerticalSNudge} - ${BORDER_WIDTH})`; +const buttonSpacingMedium = `calc(${tokens.spacingVerticalS} - ${BORDER_WIDTH})`; +const buttonSpacingLarge = `calc(${tokens.spacingVerticalMNudge} - ${BORDER_WIDTH})`; + +const iconFilledClassName = 'fui-Icon-filled'; +const iconRegularClassName = 'fui-Icon-regular'; + +const displayInline = { display: 'inline' }; +const displayNone = { display: 'none' }; + +const useRootStyles = makeStyles({ + base: { + borderRadius: '12px', + minWidth: 'unset', + [`:hover .${iconFilledClassName}`]: displayInline, + [`:hover .${iconRegularClassName}`]: displayNone, + [`:hover:active .${iconFilledClassName}`]: displayInline, + [`:hover:active .${iconRegularClassName}`]: displayNone, + + ...createCustomFocusIndicatorStyle({ + borderRadius: '12px', + boxShadow: ` + 0 0 0 ${tokens.strokeWidthThin} ${tokens.colorStrokeFocus2} inset, + 0 0 0 ${tokens.strokeWidthThick} ${tokens.colorStrokeFocus1} inset + `, + }), + }, + small: { + ...typographyStyles.caption1Strong, + borderRadius: '8px', + height: '28px', + padding: `${buttonSpacingSmall} calc(${tokens.spacingHorizontalS} - ${BORDER_WIDTH})`, + }, + medium: { + height: '36px', + padding: `${buttonSpacingMedium} calc(${tokens.spacingHorizontalM} - ${BORDER_WIDTH})`, + }, + large: { + ...typographyStyles.body1Strong, + height: '44px', + padding: `${buttonSpacingLarge} calc(${tokens.spacingHorizontalM} - ${BORDER_WIDTH})`, + }, + outline: { + ...shorthands.borderColor(tokens.colorNeutralBackground5), + color: tokens.colorNeutralForeground3, + ':hover': { + ...shorthands.borderColor(tokens.colorNeutralStroke2), + backgroundColor: tokens.colorNeutralBackground3Hover, + color: tokens.colorNeutralForeground1Hover, + }, + ':hover:active': { + ...shorthands.borderColor(tokens.colorNeutralBackground3Pressed), + backgroundColor: tokens.colorNeutralBackground3Pressed, + color: tokens.colorNeutralForeground1Pressed, + }, + }, + primary: { + ...shorthands.borderColor(tokens.colorBrandBackground), + ':hover': { + ...shorthands.borderColor(tokens.colorBrandBackgroundHover), + }, + ':hover:active': { + ...shorthands.borderColor(tokens.colorBrandBackgroundPressed), + }, + }, + secondary: { + ...shorthands.borderColor(tokens.colorNeutralBackground5), + backgroundColor: tokens.colorNeutralBackground3, + color: tokens.colorNeutralForeground3, + ':hover': { + ...shorthands.borderColor(tokens.colorNeutralStroke2), + backgroundColor: tokens.colorNeutralBackground3Hover, + color: tokens.colorNeutralForeground1Hover, + }, + ':hover:active': { + ...shorthands.borderColor(tokens.colorNeutralBackground3Pressed), + backgroundColor: tokens.colorNeutralBackground3Pressed, + color: tokens.colorNeutralForeground1Pressed, + }, + }, + subtle: { + ...shorthands.borderColor(tokens.colorTransparentStroke), + backgroundColor: tokens.colorTransparentBackground, + color: tokens.colorNeutralForeground3, + ':hover': { + ...shorthands.borderColor(tokens.colorNeutralBackground3Hover), + backgroundColor: tokens.colorNeutralBackground3Hover, + color: tokens.colorNeutralForeground1, + [`& .${buttonClassNames.icon}`]: { + color: tokens.colorNeutralForeground1, + }, + }, + ':hover:active': { + backgroundColor: tokens.colorNeutralBackground1Pressed, + color: tokens.colorNeutralForeground3Pressed, + [`& .${buttonClassNames.icon}`]: { + color: tokens.colorCompoundBrandForeground1Pressed, + }, + }, + }, + transparent: { + ...shorthands.borderColor(tokens.colorTransparentStroke), + backgroundColor: tokens.colorTransparentBackground, + color: tokens.colorNeutralForeground3, + ':hover': { color: tokens.colorCompoundBrandForeground1Hover }, + [':hover:active']: { + color: tokens.colorCompoundBrandForeground1Pressed, + }, + }, + tint: { + ...shorthands.borderColor(tokens.colorBrandStroke2), + backgroundColor: tokens.colorBrandBackground2, + color: tokens.colorCompoundBrandForeground1, + ':hover': { + ...shorthands.borderColor(tokens.colorBrandStroke2Hover), + backgroundColor: tokens.colorBrandBackground2Hover, + color: tokens.colorCompoundBrandForeground1Hover, + }, + ':hover:active': { + ...shorthands.borderColor(tokens.colorBrandStroke2Pressed), + backgroundColor: tokens.colorBrandBackground2Pressed, + color: tokens.colorCompoundBrandForeground1Pressed, + }, + }, + outlineColor: { + ...shorthands.borderColor(tokens.colorBrandStroke2), + backgroundColor: 'transparent', + color: tokens.colorCompoundBrandForeground1, + ':hover': { + ...shorthands.borderColor(tokens.colorBrandStroke2Hover), + backgroundColor: tokens.colorBrandBackground2Hover, + color: tokens.colorCompoundBrandForeground1Hover, + }, + ':hover:active': { + ...shorthands.borderColor(tokens.colorBrandStroke2Pressed), + backgroundColor: tokens.colorBrandBackground2Pressed, + color: tokens.colorCompoundBrandForeground1Pressed, + }, + }, +}); + +const useRootDisabledStyles = makeStyles({ + outline: { + ...shorthands.borderColor(tokens.colorNeutralBackground5), + backgroundColor: 'transparent', + color: tokens.colorNeutralForegroundDisabled, + ':hover': { + ...shorthands.borderColor(tokens.colorNeutralBackground5), + backgroundColor: 'transparent', + color: tokens.colorNeutralForegroundDisabled, + }, + ':hover:active': { + ...shorthands.borderColor(tokens.colorNeutralBackground5), + backgroundColor: 'transparent', + color: tokens.colorNeutralForegroundDisabled, + }, + }, + secondary: { + ...shorthands.borderColor(tokens.colorNeutralBackground5), + backgroundColor: tokens.colorNeutralBackground3, + color: tokens.colorNeutralForegroundDisabled, + ':hover': { + ...shorthands.borderColor(tokens.colorNeutralBackground5), + backgroundColor: tokens.colorNeutralBackground3, + color: tokens.colorNeutralForegroundDisabled, + }, + ':hover:active': { + ...shorthands.borderColor(tokens.colorNeutralBackground5), + backgroundColor: tokens.colorNeutralBackground3, + color: tokens.colorNeutralForegroundDisabled, + }, + }, + subtle: {}, + transparent: {}, + primary: { + ...shorthands.borderColor(tokens.colorNeutralBackgroundDisabled), + ':hover': { + ...shorthands.borderColor(tokens.colorNeutralBackgroundDisabled), + }, + ':hover:active': { + ...shorthands.borderColor(tokens.colorNeutralBackgroundDisabled), + }, + }, + tint: { + ...shorthands.borderColor(tokens.colorNeutralStrokeDisabled), + ':hover': { + ...shorthands.borderColor(tokens.colorNeutralStrokeDisabled), + }, + ':hover:active': { + ...shorthands.borderColor(tokens.colorNeutralStrokeDisabled), + }, + }, + outlineColor: { + backgroundColor: 'transparent', + ...shorthands.borderColor(tokens.colorNeutralStrokeDisabled), + ':hover': { + backgroundColor: 'transparent', + ...shorthands.borderColor(tokens.colorNeutralStrokeDisabled), + }, + ':hover:active': { + backgroundColor: 'transparent', + ...shorthands.borderColor(tokens.colorNeutralStrokeDisabled), + }, + }, +}); + +const useRootIconOnlyStyles = makeStyles({ + small: { + minWidth: '28px', + maxWidth: '28px', + padding: buttonSpacingSmall, + }, + medium: { + minWidth: '36px', + maxWidth: '36px', + padding: buttonSpacingMedium, + }, + large: { + minWidth: '40px', + maxWidth: '40px', + padding: buttonSpacingLarge, + }, +}); + +const useIconStyles = makeStyles({ + small: { + fontSize: '16px', + height: '16px', + width: '16px', + }, + medium: {}, + large: { + fontSize: '20px', + height: '20px', + width: '20px', + }, +}); + +export const useButtonStyles = (state: ButtonState): ButtonState => { + const rootStyles = useRootStyles(); + const rootDisabledStyles = useRootDisabledStyles(); + const rootIconOnlyStyles = useRootIconOnlyStyles(); + const iconStyles = useIconStyles(); + + const { appearance, disabled, disabledFocusable, iconOnly, size } = state; + + state.root.className = mergeClasses( + state.root.className, + buttonClassNames.root, + rootStyles.base, + rootStyles[appearance], + (disabled || disabledFocusable) && rootDisabledStyles[appearance], + rootStyles[size], + iconOnly && rootIconOnlyStyles[size] + ); + + if (state.icon) { + state.icon.className = mergeClasses( + state.icon.className, + buttonClassNames.icon, + iconStyles[size] + ); + } + + return state; +}; diff --git a/packages/react-cap-theme/src/components/react-button/components/MenuButton/MenuButton.tsx b/packages/react-cap-theme/src/components/react-button/components/MenuButton/MenuButton.tsx new file mode 100644 index 00000000..1c36a60c --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/MenuButton/MenuButton.tsx @@ -0,0 +1,19 @@ +import { useButtonStyles_unstable } from '@fluentui/react-button'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import * as React from 'react'; +import type { ButtonState } from '../Button/Button.types'; +import { toBaseState } from '../Button/Button.utils'; +import type { MenuButtonProps } from './MenuButton.types'; +import { renderMenuButton } from './renderMenuButton'; +import { useMenuButton } from './useMenuButton'; +import { useMenuButtonStyles } from './useMenuButtonStyles.styles'; + +export const MenuButton: ForwardRefComponent = + React.forwardRef((props, ref) => { + const state = useMenuButton(props, ref); + useButtonStyles_unstable(toBaseState(state as ButtonState)); + useMenuButtonStyles(state); + return renderMenuButton(state); + }) as ForwardRefComponent; + +MenuButton.displayName = 'MenuButton'; diff --git a/packages/react-cap-theme/src/components/react-button/components/MenuButton/MenuButton.types.ts b/packages/react-cap-theme/src/components/react-button/components/MenuButton/MenuButton.types.ts new file mode 100644 index 00000000..b56d94a2 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/MenuButton/MenuButton.types.ts @@ -0,0 +1,11 @@ +import type { MenuButtonSlots } from '@fluentui/react-button'; +import type { ComponentProps, ComponentState } from '@fluentui/react-utilities'; +import type { ButtonProps, ButtonState, ButtonSlots } from '../../Button'; + +export type { MenuButtonSlots } from '@fluentui/react-button'; + +export type MenuButtonProps = ComponentProps & + Pick; + +export type MenuButtonState = ComponentState & + Omit; diff --git a/packages/react-cap-theme/src/components/react-button/components/MenuButton/renderMenuButton.tsx b/packages/react-cap-theme/src/components/react-button/components/MenuButton/renderMenuButton.tsx new file mode 100644 index 00000000..ceeacee1 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/MenuButton/renderMenuButton.tsx @@ -0,0 +1,11 @@ +import { renderMenuButton_unstable } from '@fluentui/react-button'; +import type { ReactElement } from 'react'; +import { baseAppearanceMap } from '../Button/Button.utils'; +import type { MenuButtonState } from './MenuButton.types'; + +export const renderMenuButton = (state: MenuButtonState): ReactElement => { + return renderMenuButton_unstable({ + ...state, + appearance: baseAppearanceMap[state.appearance] ?? 'secondary', + }); +}; diff --git a/packages/react-cap-theme/src/components/react-button/components/MenuButton/useMenuButton.ts b/packages/react-cap-theme/src/components/react-button/components/MenuButton/useMenuButton.ts new file mode 100644 index 00000000..be760ca7 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/MenuButton/useMenuButton.ts @@ -0,0 +1,19 @@ +import { useMenuButton_unstable } from '@fluentui/react-button'; +import type * as React from 'react'; +import { baseAppearanceMap } from '../Button/Button.utils'; +import type { MenuButtonProps, MenuButtonState } from './MenuButton.types'; + +export const useMenuButton = ( + props: MenuButtonProps, + ref: React.Ref +): MenuButtonState => { + const { appearance = 'secondary' } = props; + const baseState = useMenuButton_unstable( + { + ...props, + appearance: appearance && baseAppearanceMap[appearance], + }, + ref + ); + return { ...baseState, appearance } as MenuButtonState; +}; diff --git a/packages/react-cap-theme/src/components/react-button/components/MenuButton/useMenuButtonStyles.styles.ts b/packages/react-cap-theme/src/components/react-button/components/MenuButton/useMenuButtonStyles.styles.ts new file mode 100644 index 00000000..d216a3ff --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/MenuButton/useMenuButtonStyles.styles.ts @@ -0,0 +1,61 @@ +import type { SlotClassNames } from '@fluentui/react-utilities'; +import { tokens } from '../../../tokens'; +import { makeStyles, mergeClasses } from '@griffel/react'; +import { useButtonStyles } from '../Button/useButtonStyles.styles'; +import type { MenuButtonSlots, MenuButtonState } from './MenuButton.types'; + +export const menuButtonClassNames: SlotClassNames = { + root: 'fui-MenuButton', + icon: 'fui-MenuButton__icon', + menuIcon: 'fui-MenuButton__menuIcon', +}; + +const useMenuIconStyles = makeStyles({ + base: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, + + small: { + fontSize: tokens.fontSizeBase200, + height: '16px', + lineHeight: tokens.lineHeightBase200, + width: '16px', + }, + medium: { + fontSize: tokens.fontSizeBase400, + height: '20px', + lineHeight: tokens.lineHeightBase400, + width: '20px', + }, + large: { + fontSize: tokens.fontSizeBase400, + height: '20px', + lineHeight: tokens.lineHeightBase400, + width: '20px', + }, + + noIconOnly: { + marginLeft: tokens.spacingHorizontalSNudge, + }, +}); + +export const useMenuButtonStyles = ( + state: MenuButtonState +): MenuButtonState => { + const menuIconStyles = useMenuIconStyles(); + + if (state.menuIcon) { + state.menuIcon.className = mergeClasses( + state.menuIcon.className, + menuButtonClassNames.menuIcon, + menuIconStyles.base, + menuIconStyles[state.size], + !state.iconOnly && menuIconStyles.noIconOnly + ); + } + + useButtonStyles({ ...state, iconPosition: 'before' }); + return state; +}; diff --git a/packages/react-cap-theme/src/components/react-button/components/SplitButton/SplitButton.tsx b/packages/react-cap-theme/src/components/react-button/components/SplitButton/SplitButton.tsx new file mode 100644 index 00000000..e35d0e40 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/SplitButton/SplitButton.tsx @@ -0,0 +1,16 @@ +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import * as React from 'react'; +import { renderSplitButton } from './renderSplitButton'; +import type { SplitButtonProps } from './SplitButton.types'; +import { useSplitButton } from './useSplitButton'; +import { useSplitButtonStyles } from './useSplitButtonStyles.styles'; + +export const SplitButton: ForwardRefComponent = + React.forwardRef((props, ref) => { + const state = useSplitButton(props, ref); + useSplitButtonStyles(state); + return renderSplitButton(state); + // Casting is required due to lack of distributive union to support unions on @types/react + }) as ForwardRefComponent; + +SplitButton.displayName = 'SplitButton'; diff --git a/packages/react-cap-theme/src/components/react-button/components/SplitButton/SplitButton.types.ts b/packages/react-cap-theme/src/components/react-button/components/SplitButton/SplitButton.types.ts new file mode 100644 index 00000000..f703188b --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/SplitButton/SplitButton.types.ts @@ -0,0 +1,25 @@ +import type { + ComponentProps, + ComponentState, + Slot, +} from '@fluentui/react-utilities'; +import type { Button, ButtonProps, ButtonState } from '../../Button'; +import type { + MenuButton, + MenuButtonProps, + MenuButtonState, +} from '../../MenuButton'; + +export type SplitButtonSlots = { + root: NonNullable>; + menuButton?: Slot; + primaryActionButton?: Slot; +}; + +export type SplitButtonProps = ComponentProps & + Omit & + Omit; + +export type SplitButtonState = ComponentState & + Omit & + Omit; diff --git a/packages/react-cap-theme/src/components/react-button/components/SplitButton/renderSplitButton.tsx b/packages/react-cap-theme/src/components/react-button/components/SplitButton/renderSplitButton.tsx new file mode 100644 index 00000000..a5686cb3 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/SplitButton/renderSplitButton.tsx @@ -0,0 +1,14 @@ +import { assertSlots } from '@fluentui/react-utilities'; +import type { ReactElement } from 'react'; +import type { SplitButtonSlots, SplitButtonState } from './SplitButton.types'; + +export const renderSplitButton = (state: SplitButtonState): ReactElement => { + assertSlots(state); + + return ( + + {state.primaryActionButton && } + {state.menuButton && } + + ); +}; diff --git a/packages/react-cap-theme/src/components/react-button/components/SplitButton/useSplitButton.ts b/packages/react-cap-theme/src/components/react-button/components/SplitButton/useSplitButton.ts new file mode 100644 index 00000000..b4efb3b6 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/SplitButton/useSplitButton.ts @@ -0,0 +1,84 @@ +import { + getIntrinsicElementProps, + useId, + slot, +} from '@fluentui/react-utilities'; +import type * as React from 'react'; +import { Button } from '../Button/Button'; +import { MenuButton } from '../MenuButton/MenuButton'; +import type { SplitButtonProps, SplitButtonState } from './SplitButton.types'; + +export const useSplitButton = ( + props: SplitButtonProps, + ref: React.Ref +): SplitButtonState => { + const { + appearance = 'secondary', + children, + disabled = false, + disabledFocusable = false, + icon, + iconPosition = 'before', + menuButton, + menuIcon, + primaryActionButton, + size = 'medium', + } = props; + + const baseId = useId('splitButton-'); + + const menuButtonShorthand = slot.optional(menuButton, { + defaultProps: { + appearance, + disabled, + disabledFocusable, + menuIcon, + size, + }, + renderByDefault: true, + elementType: MenuButton, + }); + + const primaryActionButtonShorthand = slot.optional(primaryActionButton, { + defaultProps: { + appearance, + children, + disabled, + disabledFocusable, + icon, + iconPosition, + id: baseId + '__primaryActionButton', + size, + }, + renderByDefault: true, + elementType: Button, + }); + + if ( + menuButtonShorthand && + primaryActionButtonShorthand && + !menuButtonShorthand['aria-label'] && + !menuButtonShorthand['aria-labelledby'] + ) { + menuButtonShorthand['aria-labelledby'] = primaryActionButtonShorthand.id; + } + + return { + components: { + root: 'div', + menuButton: MenuButton, + primaryActionButton: Button, + }, + root: slot.always(getIntrinsicElementProps('div', { ref, ...props }), { + elementType: 'div', + }), + primaryActionButton: primaryActionButtonShorthand, + menuButton: menuButtonShorthand, + appearance, + disabled, + disabledFocusable, + iconPosition, + shape: 'rounded', + size, + }; +}; diff --git a/packages/react-cap-theme/src/components/react-button/components/SplitButton/useSplitButtonStyles.styles.ts b/packages/react-cap-theme/src/components/react-button/components/SplitButton/useSplitButtonStyles.styles.ts new file mode 100644 index 00000000..2e2cc67d --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/SplitButton/useSplitButtonStyles.styles.ts @@ -0,0 +1,266 @@ +import { createCustomFocusIndicatorStyle } from '@fluentui/react-tabster'; +import type { SlotClassNames } from '@fluentui/react-utilities'; +import { tokens } from '../../../tokens'; +import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; +import type { SplitButtonSlots, SplitButtonState } from './SplitButton.types'; + +export const splitButtonClassNames: SlotClassNames = { + root: 'fui-SplitButton', + primaryActionButton: 'fui-SplitButton__primaryActionButton', + menuButton: 'fui-SplitButton__menuButton', +}; + +const createPaddingWithStrokeAdjustment = ( + horizontal: string +): { + paddingLeft: string; + paddingRight: string; +} => ({ + paddingLeft: `calc(${horizontal} - ${tokens.strokeWidthThin})`, + paddingRight: horizontal, +}); + +const createSizeStyles = ( + dimension: string +): { + height: string; + width: string; +} => ({ + height: dimension, + width: dimension, +}); + +const useFocusStyles = makeStyles({ + primaryActionButton: createCustomFocusIndicatorStyle({ + ...shorthands.borderColor(tokens.colorStrokeFocus1), + borderRadius: 0, + borderRightWidth: 0, + boxShadow: 'none', + outline: `${tokens.strokeWidthThick} solid ${tokens.colorStrokeFocus2}`, + ':after': { + borderRightColor: 'inherit', + height: '100%', + }, + }), + + menuButton: createCustomFocusIndicatorStyle({ + ...shorthands.borderColor(tokens.colorStrokeFocus1), + borderRadius: 0, + borderLeft: 'none', + boxShadow: 'none', + outline: `${tokens.strokeWidthThick} solid ${tokens.colorStrokeFocus2}`, + position: 'relative', + zIndex: 1, + ':after': { + content: '""', + borderLeft: `${tokens.strokeWidthThin} solid ${tokens.colorStrokeFocus1}`, + }, + }), +}); + +const useRootStyles = makeStyles({ + small: { + [`& .${splitButtonClassNames.primaryActionButton}`]: { + borderTopLeftRadius: '8px', + borderBottomLeftRadius: '8px', + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + }, + [`& .${splitButtonClassNames.menuButton}`]: { + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + borderTopRightRadius: '8px', + borderBottomRightRadius: '8px', + }, + }, + medium: { + [`& .${splitButtonClassNames.primaryActionButton}`]: { + borderTopLeftRadius: '12px', + borderBottomLeftRadius: '12px', + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + }, + [`& .${splitButtonClassNames.menuButton}`]: { + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + borderTopRightRadius: '12px', + borderBottomRightRadius: '12px', + }, + }, + large: { + [`& .${splitButtonClassNames.primaryActionButton}`]: { + borderTopLeftRadius: '12px', + borderBottomLeftRadius: '12px', + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + }, + [`& .${splitButtonClassNames.menuButton}`]: { + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + borderTopRightRadius: '12px', + borderBottomRightRadius: '12px', + }, + }, +}); + +const useRootAppearanceStyles = makeStyles({ + tint: { + [`&:hover .${splitButtonClassNames.primaryActionButton}`]: { + ...shorthands.borderColor(tokens.colorBrandStroke2Hover), + }, + [`&:hover .${splitButtonClassNames.menuButton}`]: { + ...shorthands.borderColor(tokens.colorBrandStroke2Hover), + }, + }, + outlineColor: {}, + primary: {}, + outline: {}, + secondary: {}, + subtle: {}, + transparent: {}, +}); + +const usePrimaryActionButtonStyles = makeStyles({ + base: { + borderRadius: tokens.borderRadiusNone, + borderRightWidth: '0', + position: 'relative', + ':after': { + content: '""', + borderRight: `${tokens.strokeWidthThin} solid`, + borderRightColor: 'inherit', + boxSizing: 'border-box', + height: `calc(100% - ${tokens.strokeWidthThin} * 2 - ${tokens.spacingVerticalS} * 2)`, + opacity: 0.3, + position: 'absolute', + right: 0, + }, + + '@media (forced-colors: active)': { + ':after': { borderRightColor: 'inherit' }, + ':hover:after': { borderRightColor: 'inherit' }, + ':active:after': { borderRightColor: 'inherit' }, + }, + }, + outline: {}, + primary: { + ':after': { borderRightColor: tokens.colorNeutralStrokeOnBrand2 }, + ':hover:after': { + borderRightColor: tokens.colorNeutralStrokeOnBrand2Hover, + }, + ':active:after': { + borderRightColor: tokens.colorNeutralStrokeOnBrand2Pressed, + }, + }, + secondary: {}, + subtle: { + ':after': { borderRightColor: tokens.colorNeutralStroke1 }, + ':hover:after': { borderRightColor: tokens.colorNeutralStroke1Hover }, + ':active:after': { + borderRightColor: tokens.colorNeutralStroke1Pressed, + }, + }, + transparent: { + ':after': { borderRightColor: tokens.colorNeutralStroke1 }, + ':hover:after': { borderRightColor: tokens.colorNeutralStroke1Hover }, + ':active:after': { + borderRightColor: tokens.colorNeutralStroke1Pressed, + }, + }, + tint: { + ':after': { + borderRightColor: tokens.colorBrandStroke2, + opacity: 1, + }, + ':hover:after': { + borderRightColor: tokens.colorBrandStroke2Hover, + }, + ':active:after': { + borderRightColor: tokens.colorBrandStroke2Pressed, + }, + }, + outlineColor: {}, + disabled: { + ':after': { borderRightColor: tokens.colorNeutralStrokeDisabled }, + ':hover:after': { borderRightColor: tokens.colorNeutralStrokeDisabled }, + ':active:after': { + borderRightColor: tokens.colorNeutralStrokeDisabled, + }, + }, + small: createPaddingWithStrokeAdjustment(tokens.spacingHorizontalS), + medium: createPaddingWithStrokeAdjustment(tokens.spacingHorizontalM), + large: createPaddingWithStrokeAdjustment(tokens.spacingHorizontalM), +}); + +const useMenuButtonStyles = makeStyles({ + base: { + borderRadius: tokens.borderRadiusNone, + borderLeft: 'none', + ':after': { + borderLeft: `${tokens.strokeWidthThin} solid`, + boxSizing: 'border-box', + height: '100%', + position: 'absolute', + width: '100%', + }, + }, + small: { paddingLeft: tokens.spacingHorizontalSNudge }, + medium: { paddingLeft: tokens.spacingHorizontalS }, + large: { paddingLeft: tokens.spacingHorizontalMNudge }, +}); + +const useMenuIconStyles = makeStyles({ + small: createSizeStyles('12px'), + medium: createSizeStyles('16px'), + large: createSizeStyles('16px'), +}); + +export const useSplitButtonStyles = ( + state: SplitButtonState +): SplitButtonState => { + const rootStyles = useRootStyles(); + const rootAppearanceStyles = useRootAppearanceStyles(); + const focusStyles = useFocusStyles(); + const primaryActionButtonStyles = usePrimaryActionButtonStyles(); + const menuButtonStyles = useMenuButtonStyles(); + const menuIconStyles = useMenuIconStyles(); + + state.root.className = mergeClasses( + splitButtonClassNames.root, + rootStyles[state.size], + rootAppearanceStyles[state.appearance], + state.root.className + ); + + if (state.primaryActionButton) { + state.primaryActionButton.className = mergeClasses( + splitButtonClassNames.primaryActionButton, + primaryActionButtonStyles.base, + primaryActionButtonStyles[state.appearance], + primaryActionButtonStyles[state.size], + focusStyles.primaryActionButton, + (state.disabled || state.disabledFocusable) && + primaryActionButtonStyles.disabled, + state.primaryActionButton.className + ); + } + + if (state.menuButton) { + state.menuButton.className = mergeClasses( + splitButtonClassNames.menuButton, + menuButtonStyles.base, + menuButtonStyles[state.size], + focusStyles.menuButton, + state.menuButton.className + ); + } + + if (state.menuIcon) { + state.menuIcon.className = mergeClasses( + menuIconStyles[state.size], + state.menuIcon.className + ); + } + + return state; +}; diff --git a/packages/react-cap-theme/src/components/react-button/components/ToggleButton/ToggleButton.tsx b/packages/react-cap-theme/src/components/react-button/components/ToggleButton/ToggleButton.tsx new file mode 100644 index 00000000..2f404134 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/ToggleButton/ToggleButton.tsx @@ -0,0 +1,26 @@ +import { + useButtonStyles_unstable, + useToggleButtonStyles_unstable, +} from '@fluentui/react-button'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import * as React from 'react'; + +import { toBaseState } from '../Button/Button.utils'; +import { renderToggleButton } from './renderToggleButton'; +import type { ToggleButtonProps } from './ToggleButton.types'; +import { useToggleButton } from './useToggleButton'; +import { useToggleButtonStyles } from './useToggleButtonStyles.styles'; + +export const ToggleButton: ForwardRefComponent = + React.forwardRef((props, ref) => { + const state = useToggleButton(props, ref); + const baseState = toBaseState(state); + useButtonStyles_unstable(baseState); + useToggleButtonStyles_unstable({ + ...baseState, + checked: state.checked, + }); + useToggleButtonStyles(state); + return renderToggleButton(state); + }) as ForwardRefComponent; +ToggleButton.displayName = 'ToggleButton'; diff --git a/packages/react-cap-theme/src/components/react-button/components/ToggleButton/ToggleButton.types.ts b/packages/react-cap-theme/src/components/react-button/components/ToggleButton/ToggleButton.types.ts new file mode 100644 index 00000000..39ee1088 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/ToggleButton/ToggleButton.types.ts @@ -0,0 +1,8 @@ +import type { ToggleButtonProps as BaseToggleButtonProps } from '@fluentui/react-button'; +import type { ButtonProps, ButtonState } from '../../Button'; + +export type ToggleButtonProps = ButtonProps & + Pick; + +export type ToggleButtonState = ButtonState & + Required>; diff --git a/packages/react-cap-theme/src/components/react-button/components/ToggleButton/renderToggleButton.tsx b/packages/react-cap-theme/src/components/react-button/components/ToggleButton/renderToggleButton.tsx new file mode 100644 index 00000000..0bc41b44 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/ToggleButton/renderToggleButton.tsx @@ -0,0 +1,6 @@ +import type { ReactElement } from 'react'; +import { renderButton } from '../../Button'; +import type { ToggleButtonState } from './ToggleButton.types'; + +export const renderToggleButton = (state: ToggleButtonState): ReactElement => + renderButton(state); diff --git a/packages/react-cap-theme/src/components/react-button/components/ToggleButton/useToggleButton.ts b/packages/react-cap-theme/src/components/react-button/components/ToggleButton/useToggleButton.ts new file mode 100644 index 00000000..f82d5f08 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/ToggleButton/useToggleButton.ts @@ -0,0 +1,15 @@ +import { useToggleState } from '@fluentui/react-button'; +import { useButton } from '../../Button'; + +import type { + ToggleButtonProps, + ToggleButtonState, +} from './ToggleButton.types'; + +export const useToggleButton = ( + props: ToggleButtonProps, + ref: React.Ref +): ToggleButtonState => { + const buttonState = useButton(props, ref) as ToggleButtonState; + return useToggleState(props, buttonState) as ToggleButtonState; +}; diff --git a/packages/react-cap-theme/src/components/react-button/components/ToggleButton/useToggleButtonStyles.styles.ts b/packages/react-cap-theme/src/components/react-button/components/ToggleButton/useToggleButtonStyles.styles.ts new file mode 100644 index 00000000..a320ac0d --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/ToggleButton/useToggleButtonStyles.styles.ts @@ -0,0 +1,139 @@ +import type { SlotClassNames } from '@fluentui/react-utilities'; +import { tokens } from '../../../tokens'; +import { makeStyles, shorthands, mergeClasses } from '@griffel/react'; +import { useButtonStyles, type ButtonSlots } from '../../Button'; +import type { ToggleButtonState } from './ToggleButton.types'; + +export const toggleButtonClassNames: SlotClassNames = { + root: 'fui-ToggleButton', + icon: 'fui-ToggleButton__icon', +}; + +const iconFilledClassName = 'fui-Icon-filled'; +const iconRegularClassName = 'fui-Icon-regular'; + +const displayInline = { display: 'inline' }; +const displayNone = { display: 'none' }; + +const useRootCheckedStyles = makeStyles({ + base: { + [`:hover .${iconFilledClassName}`]: { + color: tokens.colorCompoundBrandForeground1Hover, + }, + }, + outline: { + ...shorthands.borderColor(tokens.colorNeutralStroke1Selected), + color: tokens.colorNeutralForeground3Selected, + }, + + primary: { + [`:hover .${iconFilledClassName}`]: { + color: tokens.colorNeutralForegroundOnBrand, + }, + }, + tint: { + backgroundColor: tokens.colorBrandBackground2Pressed, + color: tokens.colorCompoundBrandForeground1Pressed, + ...shorthands.borderColor(tokens.colorBrandStroke2Pressed), + ...shorthands.borderWidth(tokens.strokeWidthThicker), + }, + outlineColor: { + backgroundColor: tokens.colorBrandBackground2Pressed, + color: tokens.colorCompoundBrandForeground1Pressed, + ...shorthands.borderColor(tokens.colorBrandStroke2Pressed), + ...shorthands.borderWidth(tokens.strokeWidthThicker), + }, + secondary: { + backgroundColor: tokens.colorNeutralBackground3, + ...shorthands.borderColor(tokens.colorNeutralBackground3Hover), + color: tokens.colorNeutralForeground1, + [`& .${toggleButtonClassNames.icon}`]: { + color: tokens.colorCompoundBrandForeground1Pressed, + }, + ':hover': { + color: tokens.colorCompoundBrandForeground1Hover, + }, + ':hover:active': { + color: tokens.colorCompoundBrandForeground1Pressed, + }, + }, + subtle: { + backgroundColor: tokens.colorNeutralBackground1Selected, + color: tokens.colorNeutralForeground3Selected, + ':hover': { + backgroundColor: tokens.colorNeutralBackground1Hover, + }, + }, + transparent: { + color: tokens.colorCompoundBrandForeground1Pressed, + }, +}); + +const useRootCheckedDisabledStyles = makeStyles({ + base: { + ':hover': { + [`& .${iconFilledClassName}`]: displayInline, + [`& .${iconRegularClassName}`]: displayNone, + }, + ':hover:active': { + [`& .${iconFilledClassName}`]: displayInline, + [`& .${iconRegularClassName}`]: displayNone, + }, + [`:hover .${iconFilledClassName}`]: { + color: tokens.colorNeutralForegroundDisabled, + }, + [`:hover:active .${iconFilledClassName}`]: { + color: tokens.colorNeutralForegroundDisabled, + }, + }, + secondary: {}, + primary: {}, + outline: {}, + transparent: {}, + subtle: {}, + tint: { + ...shorthands.borderWidth(tokens.strokeWidthThicker), + }, + outlineColor: { + ...shorthands.borderWidth(tokens.strokeWidthThicker), + }, +}); + +const useIconCheckedStyles = makeStyles({ + outline: { + color: tokens.colorCompoundBrandForeground1Pressed, + }, +}); + +export const useToggleButtonStyles = ( + state: ToggleButtonState +): ToggleButtonState => { + 'use no memo'; + + const rootCheckedStyles = useRootCheckedStyles(); + const iconCheckedStyles = useIconCheckedStyles(); + const checkedDisabledStyles = useRootCheckedDisabledStyles(); + const { appearance, checked, disabled, disabledFocusable } = state; + const showAsDisabled = disabled || disabledFocusable; + + useButtonStyles(state); + + state.root.className = mergeClasses( + state.root.className, + toggleButtonClassNames.root, + checked && !showAsDisabled && rootCheckedStyles.base, + checked && !showAsDisabled && rootCheckedStyles[appearance], + checked && showAsDisabled && checkedDisabledStyles.base, + checked && showAsDisabled && checkedDisabledStyles[appearance] + ); + + if (state.icon) { + state.icon.className = mergeClasses( + state.icon.className, + toggleButtonClassNames.icon, + checked && appearance === 'outline' && iconCheckedStyles.outline + ); + } + + return state; +}; diff --git a/packages/react-cap-theme/src/components/react-button/index.ts b/packages/react-cap-theme/src/components/react-button/index.ts new file mode 100644 index 00000000..81a1f07e --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/index.ts @@ -0,0 +1,45 @@ +export { + Button, + buttonClassNames, + renderButton, + useButton, + useButtonStyles, +} from './Button'; +export type { + ButtonProps, + ButtonSlots, + ButtonState, + ButtonAppearance, +} from './Button'; +export { + MenuButton, + menuButtonClassNames, + renderMenuButton, + useMenuButton, + useMenuButtonStyles, +} from './MenuButton'; +export type { + MenuButtonProps, + MenuButtonSlots, + MenuButtonState, +} from './MenuButton'; +export { + SplitButton, + splitButtonClassNames, + renderSplitButton, + useSplitButton, + useSplitButtonStyles, +} from './SplitButton'; +export type { + SplitButtonProps, + SplitButtonSlots, + SplitButtonState, +} from './SplitButton'; +export { + ToggleButton, + toggleButtonClassNames, + renderToggleButton, + useToggleButton, + useToggleButtonStyles, +} from './ToggleButton'; +export type { ToggleButtonProps, ToggleButtonState } from './ToggleButton'; diff --git a/packages/react-cap-theme/src/components/tokens/alias/colors/darkColor.ts b/packages/react-cap-theme/src/components/tokens/alias/colors/darkColor.ts new file mode 100644 index 00000000..f0573658 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/alias/colors/darkColor.ts @@ -0,0 +1,49 @@ +import type { BrandVariants } from '@fluentui/tokens'; +import type { ColorTokens } from '../../types'; + +export const generateBrandColorTokens = ( + brand: BrandVariants +): Partial => ({ + colorNeutralForeground2BrandHover: brand[110], + colorNeutralForeground2BrandPressed: brand[90], + colorNeutralForeground2BrandSelected: brand[110], + colorNeutralForeground3BrandHover: brand[110], + colorNeutralForeground3BrandPressed: brand[90], + colorNeutralForeground3BrandSelected: brand[110], + colorBrandForegroundLink: brand[110], + colorBrandForegroundLinkHover: brand[130], + colorBrandForegroundLinkPressed: brand[100], + colorBrandForegroundLinkSelected: brand[110], + colorCompoundBrandForeground1: brand[110], + colorCompoundBrandForeground1Hover: brand[130], + colorCompoundBrandForeground1Pressed: brand[100], + colorBrandForeground1: brand[110], + colorBrandForeground2: brand[120], + colorBrandForeground2Hover: brand[140], + colorBrandForeground2Pressed: brand[160], + colorBrandForegroundOnLight: brand[80], + colorBrandForegroundOnLightHover: brand[70], + colorBrandForegroundOnLightPressed: brand[40], + colorBrandForegroundOnLightSelected: brand[60], + colorBrandBackground: brand[70], + colorBrandBackgroundHover: brand[80], + colorBrandBackgroundPressed: brand[40], + colorBrandBackgroundSelected: brand[60], + colorCompoundBrandBackground: brand[110], + colorCompoundBrandBackgroundHover: brand[130], + colorCompoundBrandBackgroundPressed: brand[90], + colorBrandBackgroundStatic: brand[80], + colorBrandBackground2: brand[20], + colorBrandBackground2Hover: brand[40], + colorBrandBackground2Pressed: brand[10], + colorBrandBackgroundInvertedHover: brand[160], + colorBrandBackgroundInvertedPressed: brand[140], + colorBrandBackgroundInvertedSelected: brand[160], + colorBrandStroke1: brand[110], + colorBrandStroke2: brand[50], + colorBrandStroke2Hover: brand[70], + colorBrandStroke2Pressed: brand[40], + colorCompoundBrandStroke: brand[110], + colorCompoundBrandStrokeHover: brand[130], + colorCompoundBrandStrokePressed: brand[100], +}); diff --git a/packages/react-cap-theme/src/components/tokens/alias/colors/lightColor.ts b/packages/react-cap-theme/src/components/tokens/alias/colors/lightColor.ts new file mode 100644 index 00000000..89c0ea76 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/alias/colors/lightColor.ts @@ -0,0 +1,52 @@ +import type { BrandVariants } from '@fluentui/tokens'; +import type { ColorTokens } from '../../types'; + +export const generateBrandColorTokens = ( + brand: BrandVariants +): Partial => ({ + colorNeutralForeground2BrandHover: brand[80], + colorNeutralForeground2BrandPressed: brand[70], + colorNeutralForeground2BrandSelected: brand[80], + colorNeutralForeground3BrandHover: brand[80], + colorNeutralForeground3BrandPressed: brand[70], + colorNeutralForeground3BrandSelected: brand[80], + colorBrandForegroundLink: brand[70], + colorBrandForegroundLinkHover: brand[60], + colorBrandForegroundLinkPressed: brand[40], + colorBrandForegroundLinkSelected: brand[70], + colorCompoundBrandForeground1: brand[80], + colorCompoundBrandForeground1Hover: brand[70], + colorCompoundBrandForeground1Pressed: brand[40], + colorBrandForeground1: brand[80], + colorBrandForeground2: brand[70], + colorBrandForeground2Hover: brand[60], + colorBrandForeground2Pressed: brand[30], + colorBrandForegroundInverted: brand[110], + colorBrandForegroundInvertedHover: brand[130], + colorBrandForegroundInvertedPressed: brand[100], + colorBrandForegroundOnLight: brand[80], + colorBrandForegroundOnLightHover: brand[70], + colorBrandForegroundOnLightPressed: brand[40], + colorBrandForegroundOnLightSelected: brand[60], + colorBrandBackground: brand[80], + colorBrandBackgroundHover: brand[70], + colorBrandBackgroundPressed: brand[40], + colorBrandBackgroundSelected: brand[60], + colorCompoundBrandBackground: brand[80], + colorCompoundBrandBackgroundHover: brand[70], + colorCompoundBrandBackgroundPressed: brand[40], + colorBrandBackgroundStatic: brand[80], + colorBrandBackground2: brand[160], + colorBrandBackground2Hover: brand[150], + colorBrandBackground2Pressed: brand[130], + colorBrandBackgroundInvertedHover: brand[160], + colorBrandBackgroundInvertedPressed: brand[140], + colorBrandBackgroundInvertedSelected: brand[160], + colorBrandStroke1: brand[80], + colorBrandStroke2: brand[140], + colorBrandStroke2Hover: brand[130], + colorBrandStroke2Pressed: brand[90], + colorCompoundBrandStroke: brand[80], + colorCompoundBrandStrokeHover: brand[70], + colorCompoundBrandStrokePressed: brand[40], +}); diff --git a/packages/react-cap-theme/src/components/tokens/alias/fonts/fontFamily.ts b/packages/react-cap-theme/src/components/tokens/alias/fonts/fontFamily.ts new file mode 100644 index 00000000..8cd3cb5f --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/alias/fonts/fontFamily.ts @@ -0,0 +1,23 @@ +import type { FontVariants, FontFamilyCustomFontTokens } from '../../types'; + +export const generateTokens = ( + font: FontVariants +): FontFamilyCustomFontTokens => ({ + fontFamilyCustomFont100: font[100].fontFamily, + fontFamilyCustomFont200: font[200].fontFamily, + fontFamilyCustomFont300: font[300].fontFamily, + fontFamilyCustomFont400: font[400].fontFamily, + fontFamilyCustomFont500: font[500].fontFamily, + fontFamilyCustomFont600: font[600].fontFamily, + fontFamilyCustomFont700: font[700].fontFamily, + fontFamilyCustomFont800: font[800].fontFamily, + fontFamilyCustomFont900: font[900].fontFamily, + fontFamilyCustomFont1000: font[1000].fontFamily, + fontFamilyCustomFont1100: font[1100].fontFamily, + fontFamilyCustomFont1200: font[1200].fontFamily, + fontFamilyCustomFont1300: font[1300].fontFamily, + fontFamilyCustomFont1400: font[1400].fontFamily, + fontFamilyCustomFont1500: font[1500].fontFamily, + fontFamilyCustomFont1600: font[1600].fontFamily, + fontFamilyCustomFont1700: font[1700].fontFamily, +}); diff --git a/packages/react-cap-theme/src/components/tokens/alias/fonts/fontWeight.ts b/packages/react-cap-theme/src/components/tokens/alias/fonts/fontWeight.ts new file mode 100644 index 00000000..0ef4dd93 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/alias/fonts/fontWeight.ts @@ -0,0 +1,22 @@ +import type { + Font, + FontVariants, + FontWeightCustomFontTokens, +} from '../../types'; + +export const generateTokens = ( + font: FontVariants +): Partial => { + const tokens: Partial = {}; + const fontKeys: string[] = Object.keys(font); + + fontKeys.forEach((key) => { + const fontKey: Font = Number(key) as Font; + const value: number | undefined = font[fontKey].fontWeight; + if (value !== undefined) { + tokens[`fontWeightCustomFont${fontKey}`] = value; + } + }); + + return tokens; +}; diff --git a/packages/react-cap-theme/src/components/tokens/alias/fonts/index.ts b/packages/react-cap-theme/src/components/tokens/alias/fonts/index.ts new file mode 100644 index 00000000..90bb81d4 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/alias/fonts/index.ts @@ -0,0 +1,2 @@ +export { generateTokens as generateFontFamilyTokens } from './fontFamily'; +export { generateTokens as generateFontWeightTokens } from './fontWeight'; diff --git a/packages/react-cap-theme/src/components/tokens/global/brandColors.ts b/packages/react-cap-theme/src/components/tokens/global/brandColors.ts new file mode 100644 index 00000000..a485ff68 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/global/brandColors.ts @@ -0,0 +1,22 @@ +import type { BrandVariants } from '@fluentui/tokens'; + +export const brandTeal: BrandVariants = { + 10: '#091e1f', + 20: '#0b2829', + 30: '#0d3133', + 40: '#0e3d40', + 50: '#0f4b4f', + 60: '#0f575c', + 70: '#0e656b', + 80: '#0c757d', + 90: '#17878f', + 100: '#2799a1', + 110: '#3babb2', + 120: '#55b9c2', + 130: '#74ccd4', + 140: '#94dae0', + 150: '#b6eaf0', + 160: '#d7f2f5', +}; + +export const brandCAP: BrandVariants = brandTeal; diff --git a/packages/react-cap-theme/src/components/tokens/global/fonts.ts b/packages/react-cap-theme/src/components/tokens/global/fonts.ts new file mode 100644 index 00000000..782eaa56 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/global/fonts.ts @@ -0,0 +1,18 @@ +import type { + FontSizeTokens, + FontStyleTokens, + LineHeightTokens, +} from '../types'; + +export const fontSizes: Pick = { + fontSizeBase450: '18px', +}; + +export const fontStyles: FontStyleTokens = { + fontStyleRegular: 'normal', + fontStyleItalic: 'italic', +}; + +export const lineHeights: Pick = { + lineHeightBase450: '24px', +}; diff --git a/packages/react-cap-theme/src/components/tokens/global/neutralColors.ts b/packages/react-cap-theme/src/components/tokens/global/neutralColors.ts new file mode 100644 index 00000000..3c83a730 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/global/neutralColors.ts @@ -0,0 +1,59 @@ +import type { Greys } from '../types'; + +export const black = '#000000'; + +export const white = '#ffffff'; + +export const grey: Record = { + '0': black, + '2': '#050505', + '4': '#0a0a0a', + '6': '#0f0f0f', + '8': '#141414', + '10': '#1a1a1a', + '12': '#1f1f1f', + '14': '#242424', + '16': '#292929', + '18': '#2e2e2e', + '20': '#333333', + '22': '#383838', + '24': '#3d3d3d', + '26': '#424242', + '28': '#474747', + '30': '#4d4d4d', + '32': '#525252', + '34': '#575757', + '36': '#5c5c5c', + '38': '#616161', + '40': '#666666', + '42': '#6b6b6b', + '44': '#707070', + '46': '#757575', + '48': '#7a7a7a', + '50': '#808080', + '52': '#858585', + '54': '#8a8a8a', + '56': '#8f8f8f', + '58': '#949494', + '60': '#999999', + '62': '#9e9e9e', + '64': '#a3a3a3', + '66': '#a8a8a8', + '68': '#adadad', + '70': '#b3b3b3', + '72': '#b8b8b8', + '74': '#bdbdbd', + '76': '#c2c2c2', + '78': '#c7c7c7', + '80': '#cccccc', + '82': '#d1d1d1', + '84': '#d6d6d6', + '86': '#dbdbdb', + '88': '#e0e0e0', + '90': '#e6e6e6', + '92': '#ebebeb', + '94': '#f0f0f0', + '96': '#f5f5f5', + '98': '#fafafa', + '100': white, +}; diff --git a/packages/react-cap-theme/src/components/tokens/global/typographyStyles.ts b/packages/react-cap-theme/src/components/tokens/global/typographyStyles.ts new file mode 100644 index 00000000..bbaf5a77 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/global/typographyStyles.ts @@ -0,0 +1,148 @@ +import { typographyStyles as defaultTypographyStyles } from '@fluentui/tokens'; +import { tokens } from '../tokens'; +import type { Font, TypographyStyle, TypographyStyles } from '../types'; + +export const typographyStyles: TypographyStyles = { + ...defaultTypographyStyles, + custom: { + body1: createVariableFallback(700, { + ...defaultTypographyStyles.body1, + fontStyle: tokens.fontStyleRegular, + }), + body1Mono: createVariableFallback(700, { + ...defaultTypographyStyles.body1, + fontFamily: tokens.fontFamilyMonospace, + fontStyle: tokens.fontStyleRegular, + }), + body1Strong: createVariableFallback(700, { + ...defaultTypographyStyles.body1Strong, + fontStyle: tokens.fontStyleRegular, + }), + body1Stronger: createVariableFallback(700, { + ...defaultTypographyStyles.body1Stronger, + fontStyle: tokens.fontStyleRegular, + }), + body2: createVariableFallback(800, { + ...defaultTypographyStyles.body2, + fontStyle: tokens.fontStyleRegular, + }), + body2Italic: createVariableFallback(800, { + ...defaultTypographyStyles.body2, + fontStyle: tokens.fontStyleItalic, + }), + body2Mono: createVariableFallback(800, { + ...defaultTypographyStyles.body2, + fontFamily: tokens.fontFamilyMonospace, + fontStyle: tokens.fontStyleRegular, + }), + body3: createVariableFallback(900, { + fontFamily: tokens.fontFamilyBase, + fontSize: tokens.fontSizeBase450, + fontStyle: tokens.fontStyleRegular, + fontWeight: tokens.fontWeightRegular, + lineHeight: tokens.lineHeightBase450, + }), + body3Strong: createVariableFallback(900, { + fontFamily: tokens.fontFamilyBase, + fontSize: tokens.fontSizeBase450, + fontStyle: tokens.fontStyleRegular, + fontWeight: tokens.fontWeightSemibold, + lineHeight: tokens.lineHeightBase450, + }), + buttonLarge: createVariableFallback(600, { + ...defaultTypographyStyles.subtitle2, + fontStyle: tokens.fontStyleRegular, + }), + buttonMedium: createVariableFallback(500, { + ...defaultTypographyStyles.body1Strong, + fontStyle: tokens.fontStyleRegular, + }), + buttonSmall: createVariableFallback(400, { + ...defaultTypographyStyles.caption1, + fontStyle: tokens.fontStyleRegular, + }), + caption1: createVariableFallback(200, { + ...defaultTypographyStyles.caption1, + fontStyle: tokens.fontStyleRegular, + }), + caption1Strong: createVariableFallback(200, { + ...defaultTypographyStyles.caption1Strong, + fontStyle: tokens.fontStyleRegular, + }), + caption1Stronger: createVariableFallback(200, { + ...defaultTypographyStyles.caption1Stronger, + fontStyle: tokens.fontStyleRegular, + }), + caption2: createVariableFallback(100, { + ...defaultTypographyStyles.caption2, + fontStyle: tokens.fontStyleRegular, + }), + caption2Strong: createVariableFallback(100, { + ...defaultTypographyStyles.caption2Strong, + fontStyle: tokens.fontStyleRegular, + }), + heading2: createVariableFallback(1300, { + ...defaultTypographyStyles.title2, + fontStyle: tokens.fontStyleRegular, + }), + heading3: createVariableFallback(1200, { + ...defaultTypographyStyles.title3, + fontStyle: tokens.fontStyleRegular, + }), + heading4: createVariableFallback(1100, { + ...defaultTypographyStyles.subtitle1, + fontStyle: tokens.fontStyleRegular, + }), + subtitle1: createVariableFallback(1100, { + ...defaultTypographyStyles.subtitle1, + fontStyle: tokens.fontStyleRegular, + }), + subtitle1Italic: createVariableFallback(1100, { + ...defaultTypographyStyles.subtitle1, + fontStyle: tokens.fontStyleItalic, + }), + subtitle2: createVariableFallback(1000, { + ...defaultTypographyStyles.subtitle2, + fontStyle: tokens.fontStyleRegular, + }), + subtitle2Stronger: createVariableFallback(1000, { + ...defaultTypographyStyles.subtitle2Stronger, + fontStyle: tokens.fontStyleRegular, + }), + title1: createVariableFallback(1400, { + ...defaultTypographyStyles.title1, + fontStyle: tokens.fontStyleRegular, + }), + title2: createVariableFallback(1300, { + ...defaultTypographyStyles.title2, + fontStyle: tokens.fontStyleRegular, + }), + title3: createVariableFallback(1200, { + ...defaultTypographyStyles.title3, + fontStyle: tokens.fontStyleRegular, + }), + largeTitle: createVariableFallback(1500, { + ...defaultTypographyStyles.largeTitle, + fontStyle: tokens.fontStyleRegular, + }), + display: createVariableFallback(1600, { + ...defaultTypographyStyles.display, + fontStyle: tokens.fontStyleRegular, + }), + webPartHeader: createVariableFallback(1100, { + ...defaultTypographyStyles.subtitle1, + fontStyle: tokens.fontStyleRegular, + }), + }, +}; + +function createVariableFallback( + font: Font, + style: TypographyStyle +): TypographyStyle { + return { + ...style, + fontFamily: `var(--fontFamilyCustomFont${font}, ${style.fontFamily})`, + fontWeight: `var(--fontWeightCustomFont${font}, ${style.fontWeight})`, + }; +} diff --git a/packages/react-cap-theme/src/components/tokens/index.ts b/packages/react-cap-theme/src/components/tokens/index.ts new file mode 100644 index 00000000..1f968917 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/index.ts @@ -0,0 +1,78 @@ +export { black, white, grey } from './global/neutralColors'; +export { typographyStyles } from './global/typographyStyles'; +export { darkTheme } from './themes/darkTheme'; +export { lightTheme } from './themes/lightTheme'; +export { themeToTokensObject } from './themeToTokensObject'; +export { tokens } from './tokens'; +export type { + BorderRadiusTokens, + Brands, + BrandVariants, + ColorPaletteAnchor, + ColorPaletteBeige, + ColorPaletteBerry, + ColorPaletteBlue, + ColorPaletteBrass, + ColorPaletteBrown, + ColorPaletteCornflower, + ColorPaletteCranberry, + ColorPaletteDarkGreen, + ColorPaletteDarkOrange, + ColorPaletteDarkRed, + ColorPaletteForest, + ColorPaletteGrape, + ColorPaletteGold, + ColorPaletteGreen, + ColorPaletteLavender, + ColorPaletteLilac, + ColorPaletteLightGreen, + ColorPaletteLightTeal, + ColorPaletteMagenta, + ColorPaletteMarigold, + ColorPaletteMink, + ColorPaletteNavy, + ColorPalettePeach, + ColorPalettePink, + ColorPalettePlatinum, + ColorPalettePlum, + ColorPalettePumpkin, + ColorPalettePurple, + ColorPaletteRed, + ColorPaletteRoyalBlue, + ColorPaletteSeafoam, + ColorPaletteSteel, + ColorPaletteTeal, + ColorPaletteTokens, + ColorPaletteYellow, + ColorTokens, + CurveTokens, + CustomizableTypographyStyle, + CustomizableTypographyStyles, + DurationTokens, + Font, + FontFamilyCustomFontTokens, + FontFamilyTokens, + FontSizeTokens, + FontStyleTokens, + FontVariants, + FontWeightCustomFontTokens, + FontWeightTokens, + Greys, + HorizontalSpacingTokens, + LineHeightTokens, + PartialTheme, + ShadowBrandTokens, + ShadowTokens, + SpacingTokens, + StrokeWidthTokens, + Theme, + TokenName, + TypographyStyle, + TypographyStyles, + VerticalSpacingTokens, + ZIndexTokens, +} from './types'; +export { applyFonts } from './utils/applyFonts'; +export { createDarkTheme } from './utils/createDarkTheme'; +export { createLightTheme } from './utils/createLightTheme'; +export { extractNeutralTokens } from './utils/extractNeutralTokens'; diff --git a/packages/react-cap-theme/src/components/tokens/themeToTokensObject.ts b/packages/react-cap-theme/src/components/tokens/themeToTokensObject.ts new file mode 100644 index 00000000..e4a25fa6 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/themeToTokensObject.ts @@ -0,0 +1 @@ +export { themeToTokensObject } from '@fluentui/tokens'; diff --git a/packages/react-cap-theme/src/components/tokens/themes/darkTheme.ts b/packages/react-cap-theme/src/components/tokens/themes/darkTheme.ts new file mode 100644 index 00000000..1405b412 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/themes/darkTheme.ts @@ -0,0 +1,5 @@ +import { brandCAP } from '../global/brandColors'; +import type { Theme } from '../types'; +import { createDarkTheme } from '../utils/createDarkTheme'; + +export const darkTheme: Theme = { ...createDarkTheme(brandCAP) }; diff --git a/packages/react-cap-theme/src/components/tokens/themes/lightTheme.ts b/packages/react-cap-theme/src/components/tokens/themes/lightTheme.ts new file mode 100644 index 00000000..6784bc84 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/themes/lightTheme.ts @@ -0,0 +1,5 @@ +import { brandCAP } from '../global/brandColors'; +import type { Theme } from '../types'; +import { createLightTheme } from '../utils/createLightTheme'; + +export const lightTheme: Theme = { ...createLightTheme(brandCAP) }; diff --git a/packages/react-cap-theme/src/components/tokens/tokens.ts b/packages/react-cap-theme/src/components/tokens/tokens.ts new file mode 100644 index 00000000..46977844 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/tokens.ts @@ -0,0 +1,55 @@ +import { tokens as fluentTokens } from '@fluentui/tokens'; +import type { TokenName } from './types'; + +export const tokens: Record = { + ...fluentTokens, + + fontFamilyCustomFont100: 'var(--fontFamilyCustomFont100)', + fontFamilyCustomFont200: 'var(--fontFamilyCustomFont200)', + fontFamilyCustomFont300: 'var(--fontFamilyCustomFont300)', + fontFamilyCustomFont400: 'var(--fontFamilyCustomFont400)', + fontFamilyCustomFont500: 'var(--fontFamilyCustomFont500)', + fontFamilyCustomFont600: 'var(--fontFamilyCustomFont600)', + fontFamilyCustomFont700: 'var(--fontFamilyCustomFont700)', + fontFamilyCustomFont800: 'var(--fontFamilyCustomFont800)', + fontFamilyCustomFont900: 'var(--fontFamilyCustomFont900)', + fontFamilyCustomFont1000: 'var(--fontFamilyCustomFont1000)', + fontFamilyCustomFont1100: 'var(--fontFamilyCustomFont1100)', + fontFamilyCustomFont1200: 'var(--fontFamilyCustomFont1200)', + fontFamilyCustomFont1300: 'var(--fontFamilyCustomFont1300)', + fontFamilyCustomFont1400: 'var(--fontFamilyCustomFont1400)', + fontFamilyCustomFont1500: 'var(--fontFamilyCustomFont1500)', + fontFamilyCustomFont1600: 'var(--fontFamilyCustomFont1600)', + fontFamilyCustomFont1700: 'var(--fontFamilyCustomFont1700)', + + fontSizeBase450: 'var(--fontSizeBase450)', + + fontStyleItalic: 'var(--fontStyleItalic)', + fontStyleRegular: 'var(--fontStyleRegular)', + + fontWeightCustomFont100: 'var(--fontWeightCustomFont100)', + fontWeightCustomFont200: 'var(--fontWeightCustomFont200)', + fontWeightCustomFont300: 'var(--fontWeightCustomFont300)', + fontWeightCustomFont400: 'var(--fontWeightCustomFont400)', + fontWeightCustomFont500: 'var(--fontWeightCustomFont500)', + fontWeightCustomFont600: 'var(--fontWeightCustomFont600)', + fontWeightCustomFont700: 'var(--fontWeightCustomFont700)', + fontWeightCustomFont800: 'var(--fontWeightCustomFont800)', + fontWeightCustomFont900: 'var(--fontWeightCustomFont900)', + fontWeightCustomFont1000: 'var(--fontWeightCustomFont1000)', + fontWeightCustomFont1100: 'var(--fontWeightCustomFont1100)', + fontWeightCustomFont1200: 'var(--fontWeightCustomFont1200)', + fontWeightCustomFont1300: 'var(--fontWeightCustomFont1300)', + fontWeightCustomFont1400: 'var(--fontWeightCustomFont1400)', + fontWeightCustomFont1500: 'var(--fontWeightCustomFont1500)', + fontWeightCustomFont1600: 'var(--fontWeightCustomFont1600)', + fontWeightCustomFont1700: 'var(--fontWeightCustomFont1700)', + + lineHeightBase450: 'var(--lineHeightBase450)', + + borderRadius2XLarge: 'var(--borderRadius2XLarge)', + borderRadius4XLarge: 'var(--borderRadius4XLarge)', + colorNeutralStroke4: 'var(--colorNeutralStroke4)', + colorNeutralStroke4Hover: 'var(--colorNeutralStroke4Hover)', + colorNeutralStroke4Pressed: 'var(--colorNeutralStroke4Pressed)', +}; diff --git a/packages/react-cap-theme/src/components/tokens/types.ts b/packages/react-cap-theme/src/components/tokens/types.ts new file mode 100644 index 00000000..08daef0d --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/types.ts @@ -0,0 +1,230 @@ +import type { + ColorTokens as BaseColorTokens, + FontFamilyTokens as BaseFontFamilyTokens, + FontSizeTokens as BaseFontSizeTokens, + FontWeightTokens as BaseFontWeightTokens, + LineHeightTokens as BaseLineHeightTokens, + Theme as BaseTheme, + TypographyStyle as BaseTypographyStyle, + TypographyStyles as BaseTypographyStyles, +} from '@fluentui/tokens'; + +export type Greys = + | 0 + | 2 + | 4 + | 6 + | 8 + | 10 + | 12 + | 14 + | 16 + | 18 + | 20 + | 22 + | 24 + | 26 + | 28 + | 30 + | 32 + | 34 + | 36 + | 38 + | 40 + | 42 + | 44 + | 46 + | 48 + | 50 + | 52 + | 54 + | 56 + | 58 + | 60 + | 62 + | 64 + | 66 + | 68 + | 70 + | 72 + | 74 + | 76 + | 78 + | 80 + | 82 + | 84 + | 86 + | 88 + | 90 + | 92 + | 94 + | 96 + | 98 + | 100; + +export type Font = + | 100 + | 200 + | 300 + | 400 + | 500 + | 600 + | 700 + | 800 + | 900 + | 1000 + | 1100 + | 1200 + | 1300 + | 1400 + | 1500 + | 1600 + | 1700; + +export type ColorTokens = BaseColorTokens & { + /* placeholder: may be used to create custom color tokens to support Theme v2 */ +}; + +export type FontSizeTokens = BaseFontSizeTokens & { + fontSizeBase450: string; +}; + +export type FontFamilyCustomFontTokens = Record< + `fontFamilyCustomFont${Font}`, + string +>; + +export type FontStyleTokens = { + fontStyleRegular: string; + fontStyleItalic: string; +}; + +export type FontWeightCustomFontTokens = Record< + `fontWeightCustomFont${Font}`, + number +>; + +export type LineHeightTokens = BaseLineHeightTokens & { + lineHeightBase450: string; +}; + +export type TypographyStyle = BaseTypographyStyle & { + fontStyle: string; + lineHeight: string; +}; + +export type TypographyStyles = BaseTypographyStyles & { + custom: CustomizableTypographyStyles; +}; + +export type CustomizableTypographyStyles = Record< + keyof BaseTypographyStyles, + TypographyStyle +> & { + body1Mono: TypographyStyle; + body2Italic: TypographyStyle; + body2Mono: TypographyStyle; + body3: TypographyStyle; + body3Strong: TypographyStyle; + buttonLarge: TypographyStyle; + buttonMedium: TypographyStyle; + buttonSmall: TypographyStyle; + subtitle1Italic: TypographyStyle; + webPartHeader: TypographyStyle; + heading2: TypographyStyle; + heading3: TypographyStyle; + heading4: TypographyStyle; +}; + +export type CustomizableTypographyStyle = { + fontFamily: string; + fontWeight?: number; +}; + +export type FontVariants = Record; + +export type FontFamilyTokens = BaseFontFamilyTokens & + Partial; + +export type FontWeightTokens = BaseFontWeightTokens & + Partial; + +export type FIXME_MISSING_TOKENS = { + borderRadius2XLarge: string; + borderRadius4XLarge: string; + colorNeutralStroke4: string; + colorNeutralStroke4Hover: string; + colorNeutralStroke4Pressed: string; +}; + +export const FIXME_MISSING_TOKENS_DEFAULTS: FIXME_MISSING_TOKENS = { + borderRadius2XLarge: '', + borderRadius4XLarge: '', + colorNeutralStroke4: '', + colorNeutralStroke4Hover: '', + colorNeutralStroke4Pressed: '', +}; + +export type Theme = BaseTheme & + ColorTokens & + FontFamilyTokens & + FontSizeTokens & + FontStyleTokens & + FontWeightTokens & + LineHeightTokens & + FIXME_MISSING_TOKENS; + +export type PartialTheme = Partial; + +export type TokenName = keyof Theme; + +export type { + BorderRadiusTokens, + Brands, + BrandVariants, + ColorPaletteAnchor, + ColorPaletteBeige, + ColorPaletteBerry, + ColorPaletteBlue, + ColorPaletteBrass, + ColorPaletteBrown, + ColorPaletteCornflower, + ColorPaletteCranberry, + ColorPaletteDarkGreen, + ColorPaletteDarkOrange, + ColorPaletteDarkRed, + ColorPaletteForest, + ColorPaletteGrape, + ColorPaletteGold, + ColorPaletteGreen, + ColorPaletteLavender, + ColorPaletteLilac, + ColorPaletteLightGreen, + ColorPaletteLightTeal, + ColorPaletteMagenta, + ColorPaletteMarigold, + ColorPaletteMink, + ColorPaletteNavy, + ColorPalettePeach, + ColorPalettePink, + ColorPalettePlatinum, + ColorPalettePlum, + ColorPalettePumpkin, + ColorPalettePurple, + ColorPaletteRed, + ColorPaletteRoyalBlue, + ColorPaletteSeafoam, + ColorPaletteSteel, + ColorPaletteTeal, + ColorPaletteTokens, + ColorPaletteYellow, + CurveTokens, + DurationTokens, + HorizontalSpacingTokens, + ShadowBrandTokens, + ShadowTokens, + SpacingTokens, + StrokeWidthTokens, + VerticalSpacingTokens, + ZIndexTokens, +} from '@fluentui/tokens'; diff --git a/packages/react-cap-theme/src/components/tokens/utils/applyFonts.ts b/packages/react-cap-theme/src/components/tokens/utils/applyFonts.ts new file mode 100644 index 00000000..8bf333d6 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/utils/applyFonts.ts @@ -0,0 +1,30 @@ +import type { Theme as FluentTheme } from '@fluentui/tokens'; +import { + generateFontFamilyTokens, + generateFontWeightTokens, +} from '../alias/fonts/index'; +import { fontSizes, fontStyles, lineHeights } from '../global/fonts'; +import { + FIXME_MISSING_TOKENS_DEFAULTS, + type FontFamilyCustomFontTokens, + type FontVariants, + type FontWeightCustomFontTokens, + type Theme, +} from '../types'; + +export const applyFonts = (theme: FluentTheme, font?: FontVariants): Theme => { + const fontFamilies: FontFamilyCustomFontTokens | undefined = + font && generateFontFamilyTokens(font); + const fontWeights: Partial | undefined = + font && generateFontWeightTokens(font); + + return { + ...FIXME_MISSING_TOKENS_DEFAULTS, + ...theme, + ...fontFamilies, + ...fontSizes, + ...fontStyles, + ...fontWeights, + ...lineHeights, + }; +}; diff --git a/packages/react-cap-theme/src/components/tokens/utils/createDarkTheme.ts b/packages/react-cap-theme/src/components/tokens/utils/createDarkTheme.ts new file mode 100644 index 00000000..8bbae532 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/utils/createDarkTheme.ts @@ -0,0 +1,38 @@ +import type { BrandVariants, Theme as FluentTheme } from '@fluentui/tokens'; +import { createDarkTheme as createFluentDarkTheme } from '@fluentui/tokens'; +import { generateBrandColorTokens } from '../alias/colors/darkColor'; +import { + generateFontFamilyTokens, + generateFontWeightTokens, +} from '../alias/fonts'; +import { brandCAP } from '../global/brandColors'; +import { fontSizes, fontStyles, lineHeights } from '../global/fonts'; +import { + FIXME_MISSING_TOKENS_DEFAULTS, + type FontFamilyCustomFontTokens, + type FontVariants, + type FontWeightCustomFontTokens, + type Theme, +} from '../types'; + +export const createDarkTheme: ( + brand?: BrandVariants, + font?: FontVariants +) => Theme = (brand = brandCAP, font) => { + const fluentTheme: FluentTheme = createFluentDarkTheme(brand); + const fontFamilies: FontFamilyCustomFontTokens | undefined = + font && generateFontFamilyTokens(font); + const fontWeights: Partial | undefined = + font && generateFontWeightTokens(font); + + return { + ...FIXME_MISSING_TOKENS_DEFAULTS, + ...fluentTheme, + ...fontFamilies, + ...fontSizes, + ...fontStyles, + ...fontWeights, + ...lineHeights, + ...generateBrandColorTokens(brand), + }; +}; diff --git a/packages/react-cap-theme/src/components/tokens/utils/createLightTheme.ts b/packages/react-cap-theme/src/components/tokens/utils/createLightTheme.ts new file mode 100644 index 00000000..9d273815 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/utils/createLightTheme.ts @@ -0,0 +1,38 @@ +import type { BrandVariants, Theme as FluentTheme } from '@fluentui/tokens'; +import { createLightTheme as createFluentLightTheme } from '@fluentui/tokens'; +import { generateBrandColorTokens } from '../alias/colors/lightColor'; +import { + generateFontFamilyTokens, + generateFontWeightTokens, +} from '../alias/fonts'; +import { brandCAP } from '../global/brandColors'; +import { fontSizes, fontStyles, lineHeights } from '../global/fonts'; +import { + FIXME_MISSING_TOKENS_DEFAULTS, + type FontFamilyCustomFontTokens, + type FontVariants, + type FontWeightCustomFontTokens, + type Theme, +} from '../types'; + +export const createLightTheme: ( + brand?: BrandVariants, + font?: FontVariants +) => Theme = (brand = brandCAP, font) => { + const fluentTheme: FluentTheme = createFluentLightTheme(brand); + const fontFamilies: FontFamilyCustomFontTokens | undefined = + font && generateFontFamilyTokens(font); + const fontWeights: Partial | undefined = + font && generateFontWeightTokens(font); + + return { + ...FIXME_MISSING_TOKENS_DEFAULTS, + ...fluentTheme, + ...fontFamilies, + ...fontSizes, + ...fontStyles, + ...fontWeights, + ...lineHeights, + ...generateBrandColorTokens(brand), + }; +}; diff --git a/packages/react-cap-theme/src/components/tokens/utils/extractNeutralTokens.ts b/packages/react-cap-theme/src/components/tokens/utils/extractNeutralTokens.ts new file mode 100644 index 00000000..e6dd90e2 --- /dev/null +++ b/packages/react-cap-theme/src/components/tokens/utils/extractNeutralTokens.ts @@ -0,0 +1,12 @@ +import type { PartialTheme } from '../types'; + +export const extractNeutralTokens = ( + theme: PartialTheme +): { token: string; value: string }[] => { + return Object.entries(theme) + .filter( + ([key, value]) => + key.startsWith('colorNeutral') && typeof value === 'string' + ) + .map(([key, value]) => ({ token: key, value: value as string })); +}; diff --git a/packages/react-cap-theme/src/index.ts b/packages/react-cap-theme/src/index.ts index d4b15be2..b05449a8 100644 --- a/packages/react-cap-theme/src/index.ts +++ b/packages/react-cap-theme/src/index.ts @@ -1,11 +1,154 @@ -export { CAPThemeProvider } from './theme/CAPThemeProvider'; +import { FluentProviderProps } from '@fluentui/react-components'; +import { + useButtonStyles, + useMenuButtonStyles, + useSplitButtonStyles, + useToggleButtonStyles, + type ButtonState, + type MenuButtonState, + type SplitButtonState, + type ToggleButtonState, +} from './components/react-button'; + +export { getIntrinsicElementProps, slot } from '@fluentui/react-utilities'; export { - CAP_THEME_ONE_DRIVE, - CAP_THEME_SHAREPOINT, - CAP_THEME_TEAMS, -} from './theme/CAPTheme'; -export { Button } from './components/Button/Button'; + motionTokens, + makeStyles, + mergeClasses, +} from '@fluentui/react-components'; + export type { + ButtonAppearance, ButtonProps, + ButtonSlots, ButtonState, -} from './components/Button/Button.types'; + MenuButtonProps, + MenuButtonSlots, + MenuButtonState, + SplitButtonProps, + SplitButtonSlots, + SplitButtonState, + ToggleButtonProps, + ToggleButtonState, +} from './components/react-button'; +export { + Button, + MenuButton, + SplitButton, + ToggleButton, + buttonClassNames, + menuButtonClassNames, + renderButton, + renderMenuButton, + renderSplitButton, + renderToggleButton, + splitButtonClassNames, + toggleButtonClassNames, + useButton, + useButtonStyles, + useMenuButton, + useMenuButtonStyles, + useSplitButton, + useSplitButtonStyles, + useToggleButton, + useToggleButtonStyles, +} from './components/react-button'; +export type { + BorderRadiusTokens, + BrandVariants, + Brands, + ColorPaletteAnchor, + ColorPaletteBeige, + ColorPaletteBerry, + ColorPaletteBlue, + ColorPaletteBrass, + ColorPaletteBrown, + ColorPaletteCornflower, + ColorPaletteCranberry, + ColorPaletteDarkGreen, + ColorPaletteDarkOrange, + ColorPaletteDarkRed, + ColorPaletteForest, + ColorPaletteGold, + ColorPaletteGrape, + ColorPaletteGreen, + ColorPaletteLavender, + ColorPaletteLightGreen, + ColorPaletteLightTeal, + ColorPaletteLilac, + ColorPaletteMagenta, + ColorPaletteMarigold, + ColorPaletteMink, + ColorPaletteNavy, + ColorPalettePeach, + ColorPalettePink, + ColorPalettePlatinum, + ColorPalettePlum, + ColorPalettePumpkin, + ColorPalettePurple, + ColorPaletteRed, + ColorPaletteRoyalBlue, + ColorPaletteSeafoam, + ColorPaletteSteel, + ColorPaletteTeal, + ColorPaletteTokens, + ColorPaletteYellow, + ColorTokens, + CurveTokens, + CustomizableTypographyStyle, + CustomizableTypographyStyles, + DurationTokens, + Font, + FontFamilyCustomFontTokens, + FontFamilyTokens, + FontSizeTokens, + FontStyleTokens, + FontVariants, + FontWeightCustomFontTokens, + FontWeightTokens, + Greys, + HorizontalSpacingTokens, + LineHeightTokens, + PartialTheme, + ShadowBrandTokens, + ShadowTokens, + SpacingTokens, + StrokeWidthTokens, + Theme, + TokenName, + TypographyStyle, + TypographyStyles, + VerticalSpacingTokens, + ZIndexTokens, +} from './components/tokens'; +export { + applyFonts, + black, + createDarkTheme, + createLightTheme, + darkTheme, + extractNeutralTokens, + grey, + lightTheme, + themeToTokensObject, + tokens, + typographyStyles, + white, +} from './components/tokens'; + +export const CAP_STYLE_HOOKS: NonNullable< + FluentProviderProps['customStyleHooks_unstable'] +> = { + useButtonStyles_unstable: (state) => { + return useButtonStyles(state as ButtonState); + }, + useMenuButtonStyles_unstable: (state) => { + return useMenuButtonStyles(state as MenuButtonState); + }, + useSplitButtonStyles_unstable: (state) => { + return useSplitButtonStyles(state as SplitButtonState); + }, + useToggleButtonStyles_unstable: (state) => { + return useToggleButtonStyles(state as ToggleButtonState); + }, +}; diff --git a/packages/react-cap-theme/src/theme/CAPTheme.tsx b/packages/react-cap-theme/src/theme/CAPTheme.tsx deleted file mode 100644 index 049f2d54..00000000 --- a/packages/react-cap-theme/src/theme/CAPTheme.tsx +++ /dev/null @@ -1,302 +0,0 @@ -import { tokens } from '@fluentui/react-components'; - -type TokenType = 'color' | 'dimension' | 'shadow' | 'alignment'; - -export interface TokenSchema { - type: TokenType; -} - -type AllowedTokenName = - // This token does not exist in semantic tokens and is new to CAP. - | `cap/${string}` - // This token should exist in Fluent v9 (we expect it to be pushed upstream to fluent core). - | `fluent-ext/${string}` - // This token exists in semantic tokens v1 - | `smtc/v1/${string}` - // This token exists in semantic tokens v2 - | `smtc/v2/${string}` - // This token doesn't follow any known structure and needs to be fixed. - | `fixme/${string}`; - -/** - * @param varName - The token name (e.g., 'cap/badge-xl/padding') - * @returns The formatted CSS variable name (e.g., 'cap-badge-xl-padding') - * @remarks Temporarily using dashes instead of escaped slashes to avoid CSS escaping issues in Griffel. - * This is a workaround until the underlying Griffel issue is resolved. - */ -export function formatCAPTokenCssVar(varName: string): string { - return varName.replace(/\//g, '-'); -} - -/** - * FluentExtendedTokens are tokens that should exist in Fluent v9 but don't yet. - * We expect them to be pushed upstream to Fluent core, at which point you can swap them: - * - * ```ts - * // before - * borderRadius: CAP_TOKENS['fluent-ext/borderRadius2XLarge'] - * - * // after - * import { tokens } from '@fluentui/react-components'; - * borderRadius: tokens.borderRadius2XLarge - * ``` - */ -const FluentExtendedTokens = { - 'fluent-ext/borderRadius2XLarge': { type: 'dimension' }, - 'fluent-ext/borderRadius3XLarge': { type: 'dimension' }, - 'fluent-ext/borderRadius2XXLarge': { type: 'dimension' }, - // reason we need this token is the fluent version has value of "0" instead of "0px", which causes issues in CSS var usage - 'fluent-ext/spacingHorizontalNone': { type: 'dimension' }, - 'fluent-ext/brandForegroundCompound': { type: 'color' }, -} as const satisfies Record; - -/** - * ControlTokens are used across multiple components to control a particular aspect - * of the design system (e.g. border radii). - */ -const ControlTokens = { - 'smtc/v1/ctrl/corner/rest': { type: 'dimension' }, -} as const satisfies Record; - -const BadgeTokens = { - 'cap/badge-xl/corner-rounded': { type: 'dimension' }, - 'cap/badge-l/corner-rounded': { type: 'dimension' }, - 'cap/badge-xl/padding': { type: 'dimension' }, - 'cap/badge-l/padding': { type: 'dimension' }, - 'cap/badge-m/padding': { type: 'dimension' }, - 'cap/badge-s/padding': { type: 'dimension' }, - 'cap/badge-s/gap': { type: 'dimension' }, - 'cap/badge-s/gap-toSecondaryIcon': { type: 'dimension' }, - 'cap/badge/brand/outlined/stroke': { type: 'color' }, - 'cap/badge/brand/tint/foreground': { type: 'color' }, - 'cap/badge/brand/tint/iconColor': { type: 'color' }, - 'cap/badge/brand/ghost/foreground': { type: 'color' }, - 'cap/badge/brand/ghost/iconColor': { type: 'color' }, - 'cap/badge/danger/outlined/stroke': { type: 'color' }, - 'cap/badge/warning/filled/background': { type: 'color' }, - 'cap/badge/warning/filled/iconColor': { type: 'color' }, - 'cap/badge/warning/filled/foreground': { type: 'color' }, - 'cap/badge/warning/outlined/stroke': { type: 'color' }, - 'cap/badge/success/outlined/stroke': { type: 'color' }, - 'cap/badge/informative/outlined/stroke': { type: 'color' }, - 'cap/badge/informative/tint/background': { type: 'color' }, - 'cap/badge/informative/tint/stroke': { type: 'color' }, - 'cap/badge/important/filled/background': { type: 'color' }, - 'cap/badge/important/filled/foreground': { type: 'color' }, - 'cap/badge/important/filled/iconColor': { type: 'color' }, - 'cap/badge/important/outlined/stroke': { type: 'color' }, - 'cap/badge/important/tint/background': { type: 'color' }, - 'cap/badge/important/tint/foreground': { type: 'color' }, - 'cap/badge/important/tint/iconColor': { type: 'color' }, - 'cap/badge/important/tint/stroke': { type: 'color' }, - 'cap/badge/subtle/filled/background': { type: 'color' }, - 'cap/badge/subtle/filled/foreground': { type: 'color' }, - 'cap/badge/subtle/filled/iconColor': { type: 'color' }, - 'cap/badge/subtle/outlined/foreground': { type: 'color' }, - 'cap/badge/subtle/outlined/iconColor': { type: 'color' }, - 'cap/badge/subtle/tint/stroke': { type: 'color' }, -} as const satisfies Record; - -const ButtonTokens = { - 'fixme/ctrl/button/corner-radius': { type: 'dimension' }, - 'fixme/ctrl/button/primary-background-color': { type: 'color' }, - 'fixme/ctrl/button/primary-background-color-hover': { type: 'color' }, - 'fixme/ctrl/button/secondary-background-color': { type: 'color' }, - 'fixme/ctrl/button/secondary-background-color-hover': { type: 'color' }, - 'fixme/ctrl/button/subtle-background-color': { type: 'color' }, - 'fixme/ctrl/button/subtle-background-color-hover': { type: 'color' }, - 'fixme/ctrl/button/outline-background-color': { type: 'color' }, - 'fixme/ctrl/button/outline-background-color-hover': { type: 'color' }, - 'fixme/ctrl/button/tint-background-color': { type: 'color' }, - 'fixme/ctrl/button/tint-background-color-hover': { type: 'color' }, -} as const satisfies Record; - -const CardTokens = { - 'fixme/ctrl/card/background-color': { type: 'color' }, - 'fixme/ctrl/card/foreground-color': { type: 'color' }, - 'fixme/ctrl/card/background-color-hover': { type: 'color' }, - 'fixme/ctrl/card/foreground-color-hover': { type: 'color' }, - 'fixme/ctrl/card/background-color-pressed': { type: 'color' }, - 'fixme/ctrl/card/foreground-color-pressed': { type: 'color' }, - 'fixme/ctrl/card/background-color-disabled': { type: 'color' }, - 'fixme/ctrl/card/foreground-color-disabled': { type: 'color' }, - 'fixme/ctrl/card/corner-radius': { type: 'dimension' }, - 'fixme/ctrl/card/header-padding-outside': { type: 'dimension' }, - 'fixme/ctrl/card/header-padding-inside': { type: 'dimension' }, - 'fixme/ctrl/card/footer-horizontal-gap': { type: 'dimension' }, -} as const satisfies Record; - -const DialogTokens = { - 'cap/Dialog/Corner': { type: 'dimension' }, - 'cap/Dialog/Header/Gap': { type: 'dimension' }, - 'cap/Dialog/strokeWidth': { type: 'dimension' }, - 'cap/Dialog/strokeColor': { type: 'color' }, -} as const satisfies Record; - -const InputTokens = {} as const satisfies Record; - -const MenuTokens = {} as const satisfies Record; - -const TooltipTokens = {} as const satisfies Record< - AllowedTokenName, - TokenSchema ->; - -const DrawerTokens = { - 'cap/ctrl/flyout/base-corner': { type: 'dimension' }, - 'cap/ctrl/flyout/Elevation': { type: 'shadow' }, - 'smtc/v1/ctrl/flyout/header/paddingTop': { type: 'dimension' }, - 'smtc/v1/ctrl/flyout/header/Padding-Left': { type: 'dimension' }, - 'fixme/ctrl/drawer/header/padding-bottom': { type: 'dimension' }, - 'smtc/v1/ctrl/flyout/header/Padding-Right': { type: 'dimension' }, - 'cap/ctrl/flyout/body-Padding-Left': { type: 'dimension' }, - 'cap/ctrl/flyout/body-Padding-Right': { type: 'dimension' }, - 'cap/ctrl/flyout/Footer-Padding-top': { type: 'dimension' }, - 'cap/ctrl/flyout/Footer-Padding-bottom': { type: 'dimension' }, - 'cap/ctrl/flyout/footer-Padding-Left': { type: 'dimension' }, - 'cap/ctrl/flyout/footer-Padding-Right': { type: 'dimension' }, - 'fixme/ctrl/drawer/footer/content-alignment': { type: 'alignment' }, -} as const satisfies Record; - -export const CAPTokensSchema = { - ...FluentExtendedTokens, - ...ControlTokens, - ...BadgeTokens, - ...ButtonTokens, - ...CardTokens, - ...DialogTokens, - ...InputTokens, - ...MenuTokens, - ...TooltipTokens, - ...DrawerTokens, -} as const satisfies { [key: AllowedTokenName]: TokenSchema }; - -export const CAP_TOKENS = Object.keys(CAPTokensSchema).reduce((acc, key) => { - return { ...acc, [key]: `var(--${formatCAPTokenCssVar(key)})` }; -}, {} as Record); - -export type CAPTheme = { - [k in keyof typeof CAP_TOKENS]: string | null; -}; - -export const CAP_THEME_DEFAULTS = { - // fluent-ext - 'fluent-ext/brandForegroundCompound': '#03787C', - 'fluent-ext/spacingHorizontalNone': '0px', - 'fluent-ext/borderRadius2XLarge': '12px', - 'fluent-ext/borderRadius3XLarge': '16px', - 'fluent-ext/borderRadius2XXLarge': '24px', - - // smtc/v1 - 'smtc/v1/ctrl/corner/rest': CAP_TOKENS['fluent-ext/borderRadius2XLarge'], - - // badge - 'cap/badge-xl/corner-rounded': tokens.borderRadiusXLarge, - 'cap/badge-l/corner-rounded': tokens.borderRadiusLarge, - 'cap/badge-xl/padding': tokens.spacingHorizontalS, - 'cap/badge-l/padding': tokens.spacingHorizontalSNudge, - 'cap/badge-m/padding': tokens.spacingHorizontalSNudge, - 'cap/badge-s/padding': tokens.spacingHorizontalXS, - 'cap/badge-s/gap': CAP_TOKENS['fluent-ext/spacingHorizontalNone'], - 'cap/badge-s/gap-toSecondaryIcon': - CAP_TOKENS['fluent-ext/spacingHorizontalNone'], - 'cap/badge/brand/outlined/stroke': tokens.colorBrandStroke2, - 'cap/badge/brand/tint/foreground': - CAP_TOKENS['fluent-ext/brandForegroundCompound'], - 'cap/badge/brand/tint/iconColor': - CAP_TOKENS['fluent-ext/brandForegroundCompound'], - 'cap/badge/brand/ghost/foreground': - CAP_TOKENS['fluent-ext/brandForegroundCompound'], - 'cap/badge/brand/ghost/iconColor': - CAP_TOKENS['fluent-ext/brandForegroundCompound'], - 'cap/badge/danger/outlined/stroke': tokens.colorStatusDangerBorder1, - 'cap/badge/warning/filled/background': tokens.colorStatusWarningBackground3, - 'cap/badge/warning/filled/iconColor': tokens.colorNeutralForegroundOnBrand, - 'cap/badge/warning/filled/foreground': tokens.colorNeutralForegroundOnBrand, - 'cap/badge/warning/outlined/stroke': tokens.colorStatusWarningBorder1, - 'cap/badge/success/outlined/stroke': tokens.colorStatusSuccessBorder1, - 'cap/badge/informative/outlined/stroke': tokens.colorNeutralStroke1, - 'cap/badge/informative/tint/background': tokens.colorNeutralBackground5, - 'cap/badge/informative/tint/stroke': tokens.colorNeutralStroke1, - 'cap/badge/important/filled/background': - tokens.colorNeutralBackgroundInverted, - 'cap/badge/important/filled/foreground': tokens.colorNeutralForegroundOnBrand, - 'cap/badge/important/filled/iconColor': tokens.colorNeutralForegroundOnBrand, - 'cap/badge/important/outlined/stroke': tokens.colorNeutralStroke1, - 'cap/badge/important/tint/background': tokens.colorNeutralBackground5, - 'cap/badge/important/tint/foreground': tokens.colorNeutralForeground3, - 'cap/badge/important/tint/iconColor': tokens.colorNeutralForeground3, - 'cap/badge/important/tint/stroke': tokens.colorNeutralStroke1, - 'cap/badge/subtle/filled/background': tokens.colorNeutralBackground5, - 'cap/badge/subtle/filled/foreground': tokens.colorNeutralForeground3, - 'cap/badge/subtle/filled/iconColor': tokens.colorNeutralForeground3, - 'cap/badge/subtle/outlined/foreground': tokens.colorNeutralForeground3, - 'cap/badge/subtle/outlined/iconColor': tokens.colorNeutralForeground3, - 'cap/badge/subtle/tint/stroke': tokens.colorNeutralStroke1, - - // dialog - 'cap/Dialog/Corner': CAP_TOKENS['fluent-ext/borderRadius2XXLarge'], - 'cap/Dialog/Header/Gap': tokens.spacingVerticalL, - 'cap/Dialog/strokeColor': tokens.colorNeutralStroke3, - // Note: the strokeWidthThin token was 1px and now it is also 1px, but defined here for clarity as it is in Diff section in figma - 'cap/Dialog/strokeWidth': tokens.strokeWidthThin, - - // button - 'fixme/ctrl/button/corner-radius': CAP_TOKENS['smtc/v1/ctrl/corner/rest'], - 'fixme/ctrl/button/primary-background-color': tokens.colorBrandBackground, - 'fixme/ctrl/button/primary-background-color-hover': - tokens.colorBrandBackgroundHover, - 'fixme/ctrl/button/secondary-background-color': null, - 'fixme/ctrl/button/secondary-background-color-hover': null, - 'fixme/ctrl/button/subtle-background-color': tokens.colorBrandBackground, - 'fixme/ctrl/button/subtle-background-color-hover': - tokens.colorBrandBackground, - 'fixme/ctrl/button/outline-background-color': - tokens.colorTransparentBackground, - 'fixme/ctrl/button/outline-background-color-hover': - tokens.colorTransparentBackground, - 'fixme/ctrl/button/tint-background-color': 'red', - 'fixme/ctrl/button/tint-background-color-hover': null, - - // card - 'fixme/ctrl/card/corner-radius': tokens.borderRadiusXLarge, // 8px - 'fixme/ctrl/card/background-color': tokens.colorNeutralBackground1, - 'fixme/ctrl/card/foreground-color': tokens.colorNeutralBackground1, - 'fixme/ctrl/card/background-color-hover': '', - 'fixme/ctrl/card/foreground-color-hover': '', - 'fixme/ctrl/card/background-color-pressed': '', - 'fixme/ctrl/card/foreground-color-pressed': '', - 'fixme/ctrl/card/background-color-disabled': '', - 'fixme/ctrl/card/foreground-color-disabled': '', - 'fixme/ctrl/card/header-padding-outside': tokens.spacingVerticalM, - 'fixme/ctrl/card/header-padding-inside': tokens.spacingVerticalS, - 'fixme/ctrl/card/footer-horizontal-gap': tokens.spacingHorizontalS, - - // drawer - // Notes: Component Drawer at design specs and in code, tokens are using 'flyout' - 'cap/ctrl/flyout/base-corner': CAP_TOKENS['fluent-ext/borderRadius3XLarge'], - 'cap/ctrl/flyout/Elevation': tokens.shadow2, - 'smtc/v1/ctrl/flyout/header/paddingTop': tokens.spacingVerticalL, - 'smtc/v1/ctrl/flyout/header/Padding-Left': tokens.spacingHorizontalXL, - 'fixme/ctrl/drawer/header/padding-bottom': '12px', // Not exist in design specs and in tokens - 'smtc/v1/ctrl/flyout/header/Padding-Right': tokens.spacingHorizontalXL, // layout is different, required different value from design spec tokens.spacingHorizontalMNudge - 'cap/ctrl/flyout/body-Padding-Left': tokens.spacingHorizontalXL, - 'cap/ctrl/flyout/body-Padding-Right': tokens.spacingHorizontalXL, - 'cap/ctrl/flyout/Footer-Padding-top': tokens.spacingVerticalXXL, - 'cap/ctrl/flyout/Footer-Padding-bottom': tokens.spacingVerticalXL, - 'cap/ctrl/flyout/footer-Padding-Left': tokens.spacingHorizontalXL, - 'cap/ctrl/flyout/footer-Padding-Right': tokens.spacingHorizontalXL, - 'fixme/ctrl/drawer/footer/content-alignment': 'flex-end', -} as const satisfies CAPTheme; - -export const CAP_THEME_TEAMS = { - ...CAP_THEME_DEFAULTS, -} as const satisfies CAPTheme; - -export const CAP_THEME_ONE_DRIVE = { - ...CAP_THEME_DEFAULTS, -} as const satisfies CAPTheme; - -export const CAP_THEME_SHAREPOINT = { - ...CAP_THEME_DEFAULTS, -} as const satisfies CAPTheme; diff --git a/packages/react-cap-theme/src/theme/CAPThemeProvider.tsx b/packages/react-cap-theme/src/theme/CAPThemeProvider.tsx deleted file mode 100644 index f82b45e0..00000000 --- a/packages/react-cap-theme/src/theme/CAPThemeProvider.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import * as React from 'react'; -import { - BadgeState, - ButtonState, - CardFooterState, - CardHeaderState, - CardState, - DialogBodyState, - DialogSurfaceState, - DrawerBodyState, - DrawerFooterState, - DrawerHeaderNavigationState, - DrawerHeaderState, - DrawerHeaderTitleState, - DrawerState, - FluentProvider, - FluentProviderProps, - InputState, - InlineDrawerState, - OverlayDrawerState, - type Theme, -} from '@fluentui/react-components'; - -import { useButtonStylesHook } from '../components/Button/Button.styles'; -import { CAPTheme, formatCAPTokenCssVar } from './CAPTheme'; -import { useBadgeStylesHook } from '../components/Badge/Badge.styles'; -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 { useDialogBodyStylesHook } from '../components/Dialog/DialogBody.styles'; -import { useDrawerStylesHook } from '../components/Drawer/Drawer.styles'; -import { useDrawerBodyStylesHook } from '../components/Drawer/DrawerBody.styles'; -import { useDrawerHeaderStylesHook } from '../components/Drawer/DrawerHeader.styles'; -import { useDrawerHeaderTitleStylesHook } from '../components/Drawer/DrawerHeaderTitle.styles'; -import { useDrawerHeaderNavigationStylesHook } from '../components/Drawer/DrawerHeaderNavigation.styles'; -import { useDrawerFooterStylesHook } from '../components/Drawer/DrawerFooter.styles'; -import { useInlineDrawerStylesHook } from '../components/Drawer/InlineDrawer.styles'; -import { useOverlayDrawerStylesHook } from '../components/Drawer/OverlayDrawer.styles'; -import { useDialogSurfaceStylesHook } from '../components/Dialog/DialogSurface.styles'; - -const customStyleHooks: NonNullable< - FluentProviderProps['customStyleHooks_unstable'] -> = { - useBadgeStyles_unstable: (state) => useBadgeStylesHook(state as BadgeState), - useButtonStyles_unstable: (state) => - useButtonStylesHook(state as ButtonState), - useCardStyles_unstable: (state) => useCardStylesHook(state as CardState), - useCardHeaderStyles_unstable: (state) => - useCardHeaderStylesHook(state as CardHeaderState), - useCardFooterStyles_unstable: (state) => - useCardFooterStylesHook(state as CardFooterState), - useDialogBodyStyles_unstable: (state) => - useDialogBodyStylesHook(state as DialogBodyState), - useDialogSurfaceStyles_unstable: (state) => - useDialogSurfaceStylesHook(state as DialogSurfaceState), - useDrawerStyles_unstable: (state) => - useDrawerStylesHook(state as DrawerState), - useOverlayDrawerStyles_unstable: (state) => - useOverlayDrawerStylesHook(state as OverlayDrawerState), - useDrawerOverlayStyles_unstable: (state) => - useOverlayDrawerStylesHook(state as OverlayDrawerState), - useInlineDrawerStyles_unstable: (state) => - useInlineDrawerStylesHook(state as InlineDrawerState), - useDrawerInlineStyles_unstable: (state) => - useInlineDrawerStylesHook(state as InlineDrawerState), - useDrawerBodyStyles_unstable: (state) => - useDrawerBodyStylesHook(state as DrawerBodyState), - useDrawerHeaderStyles_unstable: (state) => - useDrawerHeaderStylesHook(state as DrawerHeaderState), - useDrawerHeaderTitleStyles_unstable: (state) => - useDrawerHeaderTitleStylesHook(state as DrawerHeaderTitleState), - useDrawerHeaderNavigationStyles_unstable: (state) => - useDrawerHeaderNavigationStylesHook(state as DrawerHeaderNavigationState), - useDrawerFooterStyles_unstable: (state) => - useDrawerFooterStylesHook(state as DrawerFooterState), - useInputStyles_unstable: (state) => useInputStylesHook(state as InputState), -}; - -type CAPThemeProviderProps = Omit< - FluentProviderProps, - 'theme' | 'customStyleHooks_unstable' -> & { - theme: Partial & Partial; -}; -export const CAPThemeProvider = ({ - children, - theme: _theme, - ...rest -}: CAPThemeProviderProps): React.ReactElement => { - const theme: Record = {}; - for (const [key, value] of Object.entries(_theme)) { - theme[formatCAPTokenCssVar(key)] = value; - } - return ( - - {children} - - ); -}; diff --git a/packages/react-cap-theme/stories/.gitkeep b/packages/react-cap-theme/stories/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/packages/react-cap-theme/stories/StorybookUtils.tsx b/packages/react-cap-theme/stories/StorybookUtils.tsx deleted file mode 100644 index 786ee2c0..00000000 --- a/packages/react-cap-theme/stories/StorybookUtils.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import * as React from 'react'; -import { - FluentProvider, - makeStyles, - teamsLightV21Theme, -} from '@fluentui/react-components'; -import { - CAPThemeProvider, - CAP_THEME_ONE_DRIVE, - CAP_THEME_SHAREPOINT, - CAP_THEME_TEAMS, -} from '@fluentui-contrib/react-cap-theme'; - -export interface CAPThemeExample { - title?: string; - render(variant: 'v9' | 'cap'): React.ReactElement | 'NOT_IMPLEMENTED'; - note?: string; -} - -const CAP_THEMES = { - current: { - label: 'Current', - variant: 'v9' as const, - theme: null, - wrap: (children: React.ReactNode) => <>{children}, - }, - teams: { - label: 'CAP (Teams)', - variant: 'cap' as const, - theme: { ...teamsLightV21Theme, ...CAP_THEME_TEAMS }, - wrap: (children: React.ReactNode) => ( - - {children} - - ), - }, - onedrive: { - label: 'CAP (OneDrive)', - variant: 'cap' as const, - theme: CAP_THEME_ONE_DRIVE, - wrap: (children: React.ReactNode) => ( - - {children} - - ), - }, - sharepoint: { - label: 'CAP (SharePoint)', - variant: 'cap' as const, - theme: CAP_THEME_SHAREPOINT, - wrap: (children: React.ReactNode) => ( - - {children} - - ), - }, -} as const; - -type CAPThemeKey = keyof typeof CAP_THEMES; -type CAPThemeSelection = (typeof CAP_THEMES)[CAPThemeKey]; - -const CAPThemeSelectionContext = React.createContext( - CAP_THEMES.current -); - -export const CAPThemeSelectionProvider = ({ - themeKey, - children, -}: { - themeKey: CAPThemeKey; - children: React.ReactNode; -}) => { - const selection = CAP_THEMES[themeKey] ?? CAP_THEMES.current; - return ( - - {children} - - ); -}; - -export const useCAPThemeSelection = () => - React.useContext(CAPThemeSelectionContext); - -// Single column view that shows only the selected theme -export const CAPThemeExamples = ({ - examples, -}: { - examples: CAPThemeExample[]; -}) => { - const styles = useCAPThemeExamplesStyles(); - const selectedTheme = useCAPThemeSelection(); - - return selectedTheme.wrap( -
-
Showing: {selectedTheme.label}
- {examples.map((example) => ( -
- {example.title && ( -
{example.title}
- )} -
- {renderExample(example, selectedTheme.variant)} -
- {example.note &&
{example.note}
} -
- ))} -
- ); -}; - -// Table view that shows Current + all three CAP themes side by side -export const CAPThemeExamplesTable = ({ - examples, -}: { - examples: CAPThemeExample[]; -}) => { - const styles = useCAPThemeExamplesTableStyles(); - const selectedTheme = useCAPThemeSelection(); - - // Map the selected theme label to our theme config - const getSelectedThemeConfig = () => { - if (selectedTheme.label === 'CAP (Teams)') return CAP_THEMES.teams; - if (selectedTheme.label === 'CAP (OneDrive)') return CAP_THEMES.onedrive; - if (selectedTheme.label === 'CAP (SharePoint)') - return CAP_THEMES.sharepoint; - return CAP_THEMES.current; - }; - - const selectedConfig = getSelectedThemeConfig(); - const themesToShow = [ - selectedConfig, - CAP_THEMES.teams, - - // We intentionally don't show these other themes in the side-by-side comparison tables. - // They currently look exactly the same as the Teams theme, so they eat up horizontal - // space without adding any value. - // CAP_THEMES.onedrive, - // CAP_THEMES.sharepoint, - ]; - - return ( - -
-
-
Example
- {themesToShow.map((config, idx) => ( -
- {idx === 0 ? 'Current' : config.label} -
- ))} -
Note
-
- {examples.map((example, index) => { - return ( -
-
{example.title}
- {themesToShow.map((config, idx) => ( -
- {config.theme ? ( - - {renderExample(example, config.variant)} - - ) : ( - renderExample(example, config.variant) - )} -
- ))} -
- {example.note && ( - - {example.note} - - )} -
-
- ); - })} -
-
- ); -}; - -function renderExample(example: CAPThemeExample, variant: 'v9' | 'cap') { - const result = example.render(variant); - if (result === 'NOT_IMPLEMENTED') { - return NOT IMPLEMENTED; - } - return result; -} - -const useCAPThemeExamplesStyles = makeStyles({ - table: { - display: 'flex', - flexDirection: 'column', - gap: '12px', - }, - themeLabel: { - fontWeight: 700, - padding: '8px 0', - }, - row: { - display: 'flex', - alignItems: 'center', - gap: '16px', - padding: '12px 16px', - border: '1px solid #ddd', - borderRadius: '8px', - backgroundColor: 'white', - }, - example: { - minWidth: '200px', - fontWeight: 600, - }, - note: { - minWidth: '200px', - fontWeight: 600, - }, - rendered: { - flex: 1, - '& > div': { - width: 'fit-content', - }, - }, -}); - -const useCAPThemeExamplesTableStyles = makeStyles({ - table: { - display: 'flex', - flexDirection: 'column', - }, - row: { - display: 'flex', - '& > div': { - flex: 2, - padding: '16px', - border: '1px solid #ddd', - }, - '& > div:first-child': { - flex: 1, - }, - '& > div:last-child': { - flex: 1, - }, - }, - noteColumn: { - flex: 1, - padding: '16px', - border: '1px solid #ddd', - }, -}); diff --git a/packages/react-cap-theme/stories/components/Badge/WithControls.stories.tsx b/packages/react-cap-theme/stories/components/Badge/WithControls.stories.tsx deleted file mode 100644 index 9526acd8..00000000 --- a/packages/react-cap-theme/stories/components/Badge/WithControls.stories.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import * as React from 'react'; -import { Badge, BadgeProps } from '@fluentui/react-components'; -import { CAPThemeExamples } from '../../StorybookUtils'; -import { CircleRegular } from '@fluentui/react-icons'; - -const badgeAppearances = ['outline', 'filled', 'ghost', 'tint'] as const; - -const badgeSizes = [ - 'tiny', - 'extra-small', - 'small', - 'medium', - 'large', - 'extra-large', -] as const; - -export const CAPBadgeWithCtrlsStory = ({ - color, - iconPosition, - shape, -}: { - color: BadgeProps['color']; - iconPosition: BadgeProps['iconPosition']; - shape: BadgeProps['shape']; -}) => { - return ( - - {/* Header row */} -
- {badgeAppearances.map((appearance) => ( -
- {appearance} -
- ))} -
- Icon Only / Filled -
- - {/* Data rows */} - {badgeSizes.map((size) => { - return ( - -
- {size} -
- {badgeAppearances.map((appearance) => ( -
- {size === 'tiny' || size === 'extra-small' ? ( - } - /> - ) : ( - } - > - badge - - )} -
- ))} -
- } - /> -
-
- ); - })} - - ); - }, - }, - ]} - /> - ); -}; - -CAPBadgeWithCtrlsStory.argTypes = { - color: { - options: [ - 'neutral', - 'brand', - 'strong', - 'important', - 'severe', - 'warning', - 'success', - 'informative', - ], - control: { type: 'radio' }, - }, - iconPosition: { - options: ['before', 'after'], - control: { type: 'radio' }, - }, - shape: { - options: ['rounded', 'circular', 'square'], - control: { type: 'radio' }, - }, -}; - -CAPBadgeWithCtrlsStory.args = { - color: 'brand', - iconPosition: 'before', - shape: 'rounded', -}; diff --git a/packages/react-cap-theme/stories/components/Badge/WithTable.stories.tsx b/packages/react-cap-theme/stories/components/Badge/WithTable.stories.tsx deleted file mode 100644 index 70c5cb5f..00000000 --- a/packages/react-cap-theme/stories/components/Badge/WithTable.stories.tsx +++ /dev/null @@ -1,431 +0,0 @@ -import * as React from 'react'; -import { Badge } from '@fluentui/react-components'; -import { CAPThemeExamplesTable } from '../../StorybookUtils'; -import { CircleRegular } from '@fluentui/react-icons'; - -export const CAPBadgeStory = () => { - return ( - } />; - }, - }, - { - title: 'Extra Small', - render() { - return } />; - }, - }, - { - title: 'Small', - render() { - return ( - }> - Badge - - ); - }, - }, - { - title: 'Default/Medium', - render() { - return }> Badge; - }, - }, - { - title: 'Medium No Icon', - render() { - return Badge; - }, - }, - { - title: 'Large', - render() { - return ( - }> - Badge - - ); - }, - }, - { - title: 'Extra Large', - render() { - return ( - }> - Badge - - ); - }, - }, - { - title: 'Tiny Square', - render() { - return ( - } shape="square" /> - ); - }, - }, - { - title: 'Extra Small Square', - render() { - return ( - } - shape="square" - /> - ); - }, - }, - { - title: 'Small Square', - render() { - return ( - } shape="square"> - Badge - - ); - }, - }, - { - title: 'Default/Medium Square', - render() { - return ( - } shape="square"> - {' '} - Badge - - ); - }, - }, - { - title: 'Medium No Icon Square', - render() { - return Badge; - }, - }, - { - title: 'Large Square', - render() { - return ( - } shape="square"> - Badge - - ); - }, - }, - { - title: 'Extra Large Square', - render() { - return ( - } shape="square"> - Badge - - ); - }, - }, - { - title: 'Tiny Rounded', - render() { - return ( - } shape="rounded" /> - ); - }, - }, - { - title: 'Extra Small Rounded ', - render() { - return ( - } - shape="rounded" - /> - ); - }, - }, - { - title: 'Small Rounded', - render() { - return ( - } shape="rounded"> - Badge - - ); - }, - }, - { - title: 'Default/Medium Rounded', - render() { - return ( - } shape="rounded"> - {' '} - Badge - - ); - }, - }, - { - title: 'Medium No Icon Rounded', - render() { - return Badge; - }, - }, - { - title: 'Large Rounded', - render() { - return ( - } shape="rounded"> - Badge - - ); - }, - }, - { - title: 'Extra Large Rounded', - render() { - return ( - } - shape="rounded" - > - Badge - - ); - }, - }, - { - title: 'Outline / Important', - render() { - return ( - } - color="important" - appearance="outline" - > - Badge - - ); - }, - }, - { - title: 'Outline / Brand', - render() { - return ( - } - color="brand" - appearance="outline" - > - Badge - - ); - }, - }, - { - title: 'Outline / Danger', - render() { - return ( - } - color="danger" - appearance="outline" - > - Badge - - ); - }, - }, - { - title: 'Outline / Warning', - render() { - return ( - } - color="warning" - appearance="outline" - > - Badge - - ); - }, - }, - { - title: 'Outline / Success', - render() { - return ( - } - color="success" - appearance="outline" - > - Badge - - ); - }, - }, - { - title: 'Outline / Informative', - render() { - return ( - } - color="informative" - appearance="outline" - > - Badge - - ); - }, - }, - { - title: 'Outline / Subtle', - render() { - return ( - } - color="subtle" - appearance="outline" - > - Badge - - ); - }, - }, - { - title: 'Outline / Severe', - render() { - return ( - } - color="severe" - appearance="outline" - > - Badge - - ); - }, - }, - { - title: 'Brand / Tint', - render() { - return ( - } color="brand" appearance="tint"> - Badge - - ); - }, - }, - { - title: 'Brand / Ghost', - render() { - return ( - } color="brand" appearance="ghost"> - Badge - - ); - }, - }, - { - title: 'Warning / Filled', - render() { - return ( - } - color="warning" - appearance="filled" - > - Badge - - ); - }, - }, - { - title: 'Informative / Tint', - render() { - return ( - } - color="informative" - appearance="tint" - > - Badge - - ); - }, - }, - { - title: 'Important / Filled', - render() { - return ( - } - color="important" - appearance="filled" - > - Badge - - ); - }, - }, - { - title: 'Important / Tint', - render() { - return ( - } - color="important" - appearance="tint" - > - Badge - - ); - }, - }, - { - title: 'Subtle / Filled', - render() { - return ( - } - color="subtle" - appearance="filled" - > - Badge - - ); - }, - }, - { - title: 'Subtle / Tint', - render() { - return ( - } color="subtle" appearance="tint"> - Badge - - ); - }, - }, - { - title: 'Small Icon only badge', - render() { - return ( - } - /> - ); - }, - }, - ]} - /> - ); -}; diff --git a/packages/react-cap-theme/stories/components/Button.stories.tsx b/packages/react-cap-theme/stories/components/Button.stories.tsx deleted file mode 100644 index 4894530c..00000000 --- a/packages/react-cap-theme/stories/components/Button.stories.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import * as React from 'react'; -import { Button, type ButtonProps } from '@fluentui-contrib/react-cap-theme'; -import { CAPThemeExamples, useCAPThemeSelection } from '../StorybookUtils'; -import { - bundleIcon, - CalendarMonthFilled, - CalendarMonthRegular, -} from '@fluentui/react-icons'; - -const CalendarMonth = bundleIcon(CalendarMonthFilled, CalendarMonthRegular); - -const buttonAppearances = [ - 'secondary', - 'primary', - 'outline', - 'subtle', - 'transparent', -] as const; - -type ButtonState = 'default' | 'hover' | 'active' | 'focus' | 'disabled'; - -const stateId = ( - appearance: (typeof buttonAppearances)[number], - state: ButtonState -) => `cap-button-${appearance}-${state}`; - -const hoverSelectors = buttonAppearances.map( - (appearance) => `#${stateId(appearance, 'hover')}` -); -const activeSelectors = buttonAppearances.map( - (appearance) => `#${stateId(appearance, 'active')}` -); -const focusSelectors = buttonAppearances.map( - (appearance) => `#${stateId(appearance, 'focus')}` -); - -export const CAPButtonWithCtrlsStory = ({ - disabledFocusable = false, - iconPosition = 'before', - shape = 'rounded', - size = 'medium', -}: ButtonProps) => { - const selectedTheme = useCAPThemeSelection(); - const commonButtonProps: ButtonProps = { - iconPosition, - shape, - size, - }; - - React.useEffect(() => { - // Force pseudo-state classes back onto the demo buttons whenever the story re-renders for a new theme or arg change. - const doc = globalThis?.document; - if (!doc) { - return; - } - - buttonAppearances.forEach((appearance) => { - const hoverButton = doc.getElementById(stateId(appearance, 'hover')); - hoverButton?.classList.add('pseudo-hover'); - - const activeButton = doc.getElementById(stateId(appearance, 'active')); - activeButton?.classList.add('pseudo-hover', 'pseudo-active'); - - const focusButton = doc.getElementById(stateId(appearance, 'focus')); - if (focusButton) { - focusButton.classList.add('pseudo-focus-visible'); - focusButton.setAttribute('data-fui-focus-visible', 'true'); - } - }); - }, [selectedTheme, iconPosition, shape, size, disabledFocusable]); - - return ( - - {buttonAppearances.map((appearance) => { - return ( -
-
- {appearance} -
- - - - - -
- ); - })} - - ); - }, - }, - ]} - /> - ); -}; - -CAPButtonWithCtrlsStory.parameters = { - pseudo: { - // Keep pressed buttons hovered too since older Button styles rely on :hover:active - hover: [...hoverSelectors, ...activeSelectors], - active: activeSelectors, - focusVisible: focusSelectors, - }, -}; - -CAPButtonWithCtrlsStory.argTypes = { - disabledFocusable: { - control: false, - table: { disable: true }, - }, - iconPosition: { - options: ['before', 'after'], - control: { type: 'radio' }, - }, - shape: { - options: ['rounded', 'circular', 'square'], - control: { type: 'radio' }, - }, - size: { - options: ['small', 'medium', 'large'], - control: { type: 'radio' }, - }, -}; - -CAPButtonWithCtrlsStory.args = { - disabledFocusable: false, - iconPosition: 'before', - shape: 'rounded', - size: 'medium', -}; diff --git a/packages/react-cap-theme/stories/components/Card.stories.tsx b/packages/react-cap-theme/stories/components/Card.stories.tsx deleted file mode 100644 index 3cb9f6fd..00000000 --- a/packages/react-cap-theme/stories/components/Card.stories.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as React from 'react'; -import { - Button, - Caption1, - Card, - CardHeader, - Text, -} from '@fluentui/react-components'; -import { CAPThemeExamplesTable } from '../StorybookUtils'; -import { MoreHorizontal20Regular } from '@fluentui/react-icons'; - -export const CAPCardStory = () => { - return ( - - - App Name - - } - description={Developer} - action={ - - - - - Dialog title - Dialog Content goes here. - - - - - - - - - - ), - }, - ]} - /> - ); -}; - -CAPDialogWithCtrlsStory.argTypes = { - modalType: { - options: ['modal', 'non-modal', 'alert'], - control: { type: 'radio' }, - }, -}; - -CAPDialogWithCtrlsStory.args = { - modalType: 'modal', -}; diff --git a/packages/react-cap-theme/stories/components/Dialog/WithTable.tsx b/packages/react-cap-theme/stories/components/Dialog/WithTable.tsx deleted file mode 100644 index 57fc7778..00000000 --- a/packages/react-cap-theme/stories/components/Dialog/WithTable.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import * as React from 'react'; -import { - Dialog, - DialogTrigger, - DialogSurface, - DialogTitle, - DialogBody, - DialogActions, - DialogContent, - Button, -} from '@fluentui/react-components'; -import { CAPThemeExamplesTable } from '../../StorybookUtils'; - -export const CAPDialogStory = () => { - return ( - - Dialog title - - This shows the Dialogbody component used standalone, without - the Dialog wrapper or overlay. To see the border radius and - border color change, use the next row to open a full Dialog. - - - - - - - - - ); - }, - }, - { - title: 'Regular modal Dialog', - render() { - return ( - - - - - - - Modal dialog title - - This is a regular modal dialog. When this type of dialog - is open, the rest of the page is dimmed out and cannot be - interacted with. * The tab sequence is kept within the - dialog and moving the focus outside * the dialog will - imply closing it. This is the default type of the - component. - - - - - - - - - - - ); - }, - }, - ]} - /> - ); -}; diff --git a/packages/react-cap-theme/stories/components/Drawer.stories.tsx b/packages/react-cap-theme/stories/components/Drawer.stories.tsx deleted file mode 100644 index 78977bbc..00000000 --- a/packages/react-cap-theme/stories/components/Drawer.stories.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import * as React from 'react'; -import { - Button, - Drawer, - DrawerBody, - DrawerFooter, - DrawerHeader, - DrawerHeaderTitle, - DrawerProps, -} from '@fluentui/react-components'; -import { DismissRegular } from '@fluentui/react-icons'; -import { CAPThemeExamples } from '../StorybookUtils'; - -const drawerBodyText = [ - 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Doloribus nam aut amet similique, iure vel voluptates cum cumque repellendus perferendis maiores officia unde in?', - 'Autem neque sequi maiores eum omnis. Lorem ipsum, dolor sit amet consectetur adipisicing elit. Perspiciatis ipsam explicabo tempora ipsum saepe nam.', - 'Eum aliquid aperiam, laborum labore excepturi nisi odio deserunt facilis error. Mollitia dolor quidem a.', -].join(' '); - -const DrawerContent = ({ onClose }: { onClose: () => void }) => ( - <> - - } - onClick={onClose} - /> - } - > - Title goes here - - - - {drawerBodyText} - - - - - - -); - -const PositionExample = ({ - size, - position, -}: { - size: DrawerProps['size']; - position: DrawerProps['position']; -}) => { - const [isOpen, setIsOpen] = React.useState(false); - - return ( -
- - - setIsOpen(open)} - > - setIsOpen(false)} /> - -
- ); -}; - -export const CAPDrawerWithCtrlsStory = ({ - size, - position, -}: { - size: DrawerProps['size']; - position: DrawerProps['position']; -}) => { - return ( - , - }, - ]} - /> - ); -}; - -CAPDrawerWithCtrlsStory.argTypes = { - size: { - options: ['small', 'medium', 'large', 'full'], - control: { type: 'radio' }, - }, - position: { - options: ['start', 'end', 'bottom'], - control: { type: 'radio' }, - }, -}; - -CAPDrawerWithCtrlsStory.args = { - size: 'small', - position: 'end', -}; diff --git a/packages/react-cap-theme/stories/components/Input.stories.tsx b/packages/react-cap-theme/stories/components/Input.stories.tsx deleted file mode 100644 index 7d39b99a..00000000 --- a/packages/react-cap-theme/stories/components/Input.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from 'react'; -import { Input } from '@fluentui/react-components'; -import { CAPThemeExamplesTable } from '../StorybookUtils'; - -export const CAPInputStory = () => { - return ( - ; - }, - }, - ]} - /> - ); -}; diff --git a/packages/react-cap-theme/stories/components/Menu.stories.tsx b/packages/react-cap-theme/stories/components/Menu.stories.tsx deleted file mode 100644 index c476434e..00000000 --- a/packages/react-cap-theme/stories/components/Menu.stories.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import * as React from 'react'; -import { CAPThemeExamplesTable } from '../StorybookUtils'; - -export const CAPMenuStory = () => { - return ; -}; diff --git a/packages/react-cap-theme/stories/components/Tooltip.stories.tsx b/packages/react-cap-theme/stories/components/Tooltip.stories.tsx deleted file mode 100644 index 64f5418a..00000000 --- a/packages/react-cap-theme/stories/components/Tooltip.stories.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import * as React from 'react'; -import { CAPThemeExamplesTable } from '../StorybookUtils'; - -export const CAPTooltipStory = () => { - return ; -}; diff --git a/packages/react-cap-theme/stories/index.stories.tsx b/packages/react-cap-theme/stories/index.stories.tsx deleted file mode 100644 index e17a91d8..00000000 --- a/packages/react-cap-theme/stories/index.stories.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import * as React from 'react'; -import { Meta } from '@storybook/react'; - -export { CAPBadgeStory as Badge } from './components/Badge/WithTable.stories'; -export { CAPBadgeWithCtrlsStory as BadgeWithControls } from './components/Badge/WithControls.stories'; -export { CAPButtonWithCtrlsStory as ButtonWithControls } from './components/Button.stories'; -export { CAPCardStory as Card } from './components/Card.stories'; -export { CAPDialogStory as Dialog } from './components/Dialog/WithTable'; -export { CAPDialogWithCtrlsStory as DialogWithControls } from './components/Dialog/WithControls.stories'; -export { CAPDrawerWithCtrlsStory as DrawerWithControls } from './components/Drawer.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'; - -const DefaultStory = () =>
; - -export default { - title: 'Components/CAP', - component: DefaultStory, - parameters: {}, -} as Meta; diff --git a/packages/react-cap-theme/tsconfig.json b/packages/react-cap-theme/tsconfig.json index 15d0c1ca..37b4d743 100644 --- a/packages/react-cap-theme/tsconfig.json +++ b/packages/react-cap-theme/tsconfig.json @@ -2,7 +2,15 @@ "extends": "../../tsconfig.base.json", "files": [], "compilerOptions": { - "jsx": "react" + "jsx": "react-jsx", + "allowSyntheticDefaultImports": false, + "baseUrl": ".", + "paths": { + "@fluentui-contrib/react-cap-theme/react-icons": [ + "src/components/react-icons/index.ts" + ], + "@fluentui-contrib/react-cap-theme/*": ["src/components/*"] + } }, "include": [], "references": [