diff --git a/packages/react-cap-theme/.storybook/preview.tsx b/packages/react-cap-theme/.storybook/preview.tsx index a02d2b63..047ceeae 100644 --- a/packages/react-cap-theme/.storybook/preview.tsx +++ b/packages/react-cap-theme/.storybook/preview.tsx @@ -9,7 +9,7 @@ import { CAP_STYLE_HOOKS } from '../src/index'; import type { JSXElement } from '@fluentui/react-utilities'; import type { Preview, StoryFn } from '@storybook/react'; -import { CAPTokens } from '../src/components/tokens/types'; +import { CAPTokens } from '../src/customStyleHooks/tokens/types'; const capTheme: Record = { ...webLightTheme, diff --git a/packages/react-cap-theme/.swcrc b/packages/react-cap-theme/.swcrc index 0f4746f4..4f9426e2 100644 --- a/packages/react-cap-theme/.swcrc +++ b/packages/react-cap-theme/.swcrc @@ -24,7 +24,8 @@ ".*\\.test.tsx?$", "./src/jest-setup.ts$", "./**/jest-setup.ts$", - ".*.js$" + ".*.js$", + "src/components" ], "$schema": "https://json.schemastore.org/swcrc" } diff --git a/packages/react-cap-theme/src/components/react-accordion/components/AccordionHeader/AccordionHeader.tsx b/packages/react-cap-theme/src/components/react-accordion/components/AccordionHeader/AccordionHeader.tsx new file mode 100644 index 00000000..97b8a0e5 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-accordion/components/AccordionHeader/AccordionHeader.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import { + renderAccordionHeader_unstable, + useAccordionHeaderContextValues_unstable, + type AccordionHeaderProps, +} from '@fluentui/react-accordion'; +import { useAccordionHeader } from './useAccordionHeader'; +import { useAccordionHeaderStyles_unstable } from '@fluentui/react-components'; +import { useAccordionHeaderStyles as useCAPAccordionHeader } from '../../../../customStyleHooks/react-accordion'; +/** + * An accordion header is used as a button in the heading + * + * @param props - The accordion header configuration and event handlers + * @param ref - Reference to the accordion header element + * @returns The rendered accordion header component + * @alpha + */ +export const AccordionHeader: ForwardRefComponent = + React.forwardRef((props, ref) => { + const state = useAccordionHeader(props, ref); + + useAccordionHeaderStyles_unstable(state); + useCAPAccordionHeader(state); + + return renderAccordionHeader_unstable( + state, + useAccordionHeaderContextValues_unstable(state) + ); + }); diff --git a/packages/react-cap-theme/src/components/react-accordion/index.ts b/packages/react-cap-theme/src/components/react-accordion/index.ts index 292a7bf3..6aa0cf0b 100644 --- a/packages/react-cap-theme/src/components/react-accordion/index.ts +++ b/packages/react-cap-theme/src/components/react-accordion/index.ts @@ -1,2 +1 @@ -export { useAccordionHeaderStyles } from './components/AccordionHeader/useAccordionHeaderStyles.styles'; -export { useAccordionPanelStyles } from './components/AccordionPanel/useAccordionPanelStyles.styles'; +export { AccordionHeader as CAPAccordionHeader } from './components/AccordionHeader/AccordionHeader'; diff --git a/packages/react-cap-theme/src/components/react-avatar/components/AvatarGroupPopover/AvatarGroupPopover.tsx b/packages/react-cap-theme/src/components/react-avatar/components/AvatarGroupPopover/AvatarGroupPopover.tsx new file mode 100644 index 00000000..26bf124f --- /dev/null +++ b/packages/react-cap-theme/src/components/react-avatar/components/AvatarGroupPopover/AvatarGroupPopover.tsx @@ -0,0 +1,25 @@ +import { + renderAvatarGroupPopover_unstable, + useAvatarGroupPopoverContextValues_unstable, + type AvatarGroupPopoverState as FluentAvatarGroupPopoverState, +} from '@fluentui/react-avatar'; +import type * as React from 'react'; +import { useAvatarGroupPopoverStyles_unstable } from '@fluentui/react-components'; +import { useAvatarGroupPopover } from './useAvatarGroupPopover'; +import type { AvatarGroupPopoverProps } from '../../../../customStyleHooks/react-avatar'; +import { useAvatarGroupPopoverStyles as useCAPAvatarGroupPopoverStyles } from '../../../../customStyleHooks/react-avatar'; + +export const AvatarGroupPopover: React.FC = ( + props +) => { + const state = useAvatarGroupPopover(props); + const baseState = state as unknown as FluentAvatarGroupPopoverState; + const contextValues = useAvatarGroupPopoverContextValues_unstable(baseState); + + useAvatarGroupPopoverStyles_unstable(baseState); + useCAPAvatarGroupPopoverStyles(state); + + return renderAvatarGroupPopover_unstable(baseState, contextValues); +}; + +AvatarGroupPopover.displayName = 'AvatarGroupPopover'; diff --git a/packages/react-cap-theme/src/components/react-avatar/components/AvatarGroupPopover/useAvatarGroupPopover.ts b/packages/react-cap-theme/src/components/react-avatar/components/AvatarGroupPopover/useAvatarGroupPopover.ts new file mode 100644 index 00000000..e3eb5bc8 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-avatar/components/AvatarGroupPopover/useAvatarGroupPopover.ts @@ -0,0 +1,41 @@ +import { useAvatarGroupPopover_unstable } from '@fluentui/react-avatar'; +import { slot } from '@fluentui/react-utilities'; +import { PopoverSurface } from '../../../react-popover'; +import { CAPTooltip } from '../../../react-tooltip'; +import type { + AvatarGroupPopoverProps, + AvatarGroupPopoverState, +} from '../../../../customStyleHooks/react-avatar'; + +export const useAvatarGroupPopover = ( + props: AvatarGroupPopoverProps +): AvatarGroupPopoverState => { + const { tooltip: tooltipProps, ...baseProps } = props; + const state = useAvatarGroupPopover_unstable(baseProps); + + const popoverSurface = slot.always(props.popoverSurface, { + defaultProps: { + 'aria-label': 'Overflow', + tabIndex: 0, + }, + elementType: PopoverSurface, + }); + const tooltip = slot.always(tooltipProps, { + defaultProps: { + content: 'View more people.', + relationship: 'label', + }, + elementType: CAPTooltip, + }); + + return { + ...state, + components: { + ...state.components, + popoverSurface: PopoverSurface, + tooltip: CAPTooltip, + }, + popoverSurface, + tooltip, + }; +}; diff --git a/packages/react-cap-theme/src/components/react-avatar/index.ts b/packages/react-cap-theme/src/components/react-avatar/index.ts index cff2a23d..39331e92 100644 --- a/packages/react-cap-theme/src/components/react-avatar/index.ts +++ b/packages/react-cap-theme/src/components/react-avatar/index.ts @@ -1,3 +1 @@ -export { useAvatarStyles } from './components/Avatar/useAvatarStyles.styles'; -export { useAvatarGroupItemStyles } from './components/AvatarGroupItem/useAvatarGroupItemStyles.styles'; -export { useAvatarGroupPopoverStyles } from './components/AvatarGroupPopover/useAvatarGroupPopoverStyles.styles'; +export { AvatarGroupPopover as CAPAvatarGroupPopover } from './components/AvatarGroupPopover/AvatarGroupPopover'; diff --git a/packages/react-cap-theme/src/components/react-button/Button.ts b/packages/react-cap-theme/src/components/react-button/Button.ts index 82fbb80c..0eb6a4b9 100644 --- a/packages/react-cap-theme/src/components/react-button/Button.ts +++ b/packages/react-cap-theme/src/components/react-button/Button.ts @@ -1,7 +1,3 @@ -export { useButtonStyles } from './components/Button/useButtonStyles.styles'; -export type { - ButtonProps, - ButtonSlots, - ButtonState, - ButtonAppearance, -} from './components/Button/Button.types'; +export { Button as CAPButton } from './components/Button/Button'; +export { renderButton } from './components/Button/renderButton'; +export { useButton } from './components/Button/useButton'; diff --git a/packages/react-cap-theme/src/components/react-button/MenuButton.ts b/packages/react-cap-theme/src/components/react-button/MenuButton.ts index 9c97077e..c2d7ad7b 100644 --- a/packages/react-cap-theme/src/components/react-button/MenuButton.ts +++ b/packages/react-cap-theme/src/components/react-button/MenuButton.ts @@ -1,6 +1 @@ -export { useMenuButtonStyles } from './components/MenuButton/useMenuButtonStyles.styles'; -export type { - MenuButtonProps, - MenuButtonSlots, - MenuButtonState, -} from './components/MenuButton/MenuButton.types'; +export { MenuButton as CAPMenuButton } from './components/MenuButton/MenuButton'; diff --git a/packages/react-cap-theme/src/components/react-button/SplitButton.ts b/packages/react-cap-theme/src/components/react-button/SplitButton.ts index 280fad8a..4ca02c89 100644 --- a/packages/react-cap-theme/src/components/react-button/SplitButton.ts +++ b/packages/react-cap-theme/src/components/react-button/SplitButton.ts @@ -1,6 +1 @@ -export { useSplitButtonStyles } from './components/SplitButton/useSplitButtonStyles.styles'; -export type { - SplitButtonProps, - SplitButtonSlots, - SplitButtonState, -} from './components/SplitButton/SplitButton.types'; +export { SplitButton as CAPSplitButton } from './components/SplitButton/SplitButton'; diff --git a/packages/react-cap-theme/src/components/react-button/ToggleButton.ts b/packages/react-cap-theme/src/components/react-button/ToggleButton.ts index fa122770..4bfec336 100644 --- a/packages/react-cap-theme/src/components/react-button/ToggleButton.ts +++ b/packages/react-cap-theme/src/components/react-button/ToggleButton.ts @@ -1,5 +1,2 @@ -export { useToggleButtonStyles } from './components/ToggleButton/useToggleButtonStyles.styles'; -export type { - ToggleButtonProps, - ToggleButtonState, -} from './components/ToggleButton/ToggleButton.types'; +export { ToggleButton as CAPToggleButton } from './components/ToggleButton/ToggleButton'; +export { useToggleButton } from './components/ToggleButton/useToggleButton'; 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..e1eb6fe0 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/Button/Button.tsx @@ -0,0 +1,22 @@ +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import * as React from 'react'; +import { renderButton } from './renderButton'; +import { useButton } from './useButton'; +import { useButtonStyles_unstable } from '@fluentui/react-components'; +import { useButtonStyles as useCAPButtonStyles } from '../../../../customStyleHooks/react-button'; +import type { ButtonProps } from '../../../../customStyleHooks/react-button/'; +import { toBaseState } from '../../utils/toBaseState'; + +export const Button: ForwardRefComponent = React.forwardRef( + (props, ref) => { + const state = useButton(props, ref); + + useButtonStyles_unstable(toBaseState(state)); + useCAPButtonStyles(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.utils.ts b/packages/react-cap-theme/src/components/react-button/components/Button/Button.utils.ts new file mode 100644 index 00000000..e8637204 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/Button/Button.utils.ts @@ -0,0 +1,22 @@ +import type { ButtonProps as BaseButtonProps } from '@fluentui/react-button'; +import type { + ButtonAppearance, + ButtonProps, +} from '../../../../customStyleHooks/react-button'; + +export const baseAppearanceMap: Record< + ButtonAppearance, + BaseButtonProps['appearance'] +> = { + secondary: 'secondary', + primary: 'primary', + outline: 'outline', + subtle: 'subtle', + transparent: 'transparent', + tint: 'primary', +}; + +export const toBaseProps = (props: ButtonProps): BaseButtonProps => ({ + ...props, + appearance: props.appearance && baseAppearanceMap[props.appearance], +}); 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..38cde1f4 --- /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 { JSXElement } from '@fluentui/react-utilities'; +import type { ButtonState } from '../../../../customStyleHooks/react-button'; +import { toBaseState } from '../../utils/toBaseState'; + +export const renderButton = (state: ButtonState): JSXElement => { + 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..f998d490 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/Button/useButton.ts @@ -0,0 +1,18 @@ +import { useButton_unstable as useBaseState } from '@fluentui/react-button'; +import type { + ButtonProps, + ButtonState, +} from '../../../../customStyleHooks/react-button'; +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/MenuButton/MenuButton.tsx b/packages/react-cap-theme/src/components/react-button/components/MenuButton/MenuButton.tsx new file mode 100644 index 00000000..2982a82c --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/MenuButton/MenuButton.tsx @@ -0,0 +1,18 @@ +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import * as React from 'react'; +import { renderMenuButton } from './renderMenuButton'; +import { useMenuButton } from './useMenuButton'; +import { useMenuButtonStyles_unstable } from '@fluentui/react-components'; +import { useMenuButtonStyles as useCAPMenuButtonStyles } from '../../../../customStyleHooks/react-button'; +import type { MenuButtonProps } from '../../../../customStyleHooks/react-button'; +import { toBaseState } from '../../utils/toBaseState'; + +export const MenuButton: ForwardRefComponent = + React.forwardRef((props, ref) => { + const state = useMenuButton(props, ref); + useMenuButtonStyles_unstable(toBaseState(state)); + useCAPMenuButtonStyles(state); + return renderMenuButton(state); + }) as ForwardRefComponent; + +MenuButton.displayName = 'MenuButton'; 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..bc54e1b2 --- /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 { JSXElement } from '@fluentui/react-utilities'; +import { baseAppearanceMap } from '../Button/Button.utils'; +import type { MenuButtonState } from '../../../../customStyleHooks/react-button'; + +export const renderMenuButton = (state: MenuButtonState): JSXElement => { + return renderMenuButton_unstable({ + ...state, + appearance: baseAppearanceMap[state.appearance] ?? 'secondary', + }); +}; diff --git a/packages/react-cap-theme/src/components/react-button/components/MenuButton/useMenuButton.tsx b/packages/react-cap-theme/src/components/react-button/components/MenuButton/useMenuButton.tsx new file mode 100644 index 00000000..272347a7 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/MenuButton/useMenuButton.tsx @@ -0,0 +1,41 @@ +import { useMenuButton_unstable } from '@fluentui/react-button'; +import { + bundleIcon, + ChevronDownFilled, + ChevronDownRegular, +} from '@fluentui/react-icons'; +import { slot } from '@fluentui/react-utilities'; +import * as React from 'react'; +import { baseAppearanceMap } from '../Button/Button.utils'; +import type { + MenuButtonProps, + MenuButtonState, +} from '../../../../customStyleHooks/react-button'; + +const ChevronDownIcon = bundleIcon(ChevronDownFilled, ChevronDownRegular); + +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, + menuIcon: slot.optional(props.menuIcon, { + defaultProps: { + children: , + }, + renderByDefault: true, + elementType: 'span', + }), + }; +}; 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..e9f514ad --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/SplitButton/SplitButton.tsx @@ -0,0 +1,19 @@ +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import * as React from 'react'; +import { renderSplitButton } from './renderSplitButton'; +import type { SplitButtonProps } from '../../../../customStyleHooks/react-button/components/SplitButton/SplitButton.types'; +import { useSplitButton } from './useSplitButton'; +import { useSplitButtonStyles as useCAPSplitButtonStyles } from '../../../../customStyleHooks/react-button/components/SplitButton/useSplitButtonStyles.styles'; +import { useSplitButtonStyles_unstable } from '@fluentui/react-components'; +import { toBaseState } from '../../utils/toBaseState'; + +export const SplitButton: ForwardRefComponent = + React.forwardRef((props, ref) => { + const state = useSplitButton(props, ref); + useSplitButtonStyles_unstable(toBaseState(state)); + useCAPSplitButtonStyles(state); + + return renderSplitButton(state); + }); + +SplitButton.displayName = 'SplitButton'; 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..4de1e5cd --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/SplitButton/renderSplitButton.tsx @@ -0,0 +1,19 @@ +/** @jsx createElement */ +/** @jsxRuntime classic */ + +import { assertSlots, type JSXElement } from '@fluentui/react-utilities'; +import type { + SplitButtonSlots, + SplitButtonState, +} from '../../../../customStyleHooks/react-button'; + +export const renderSplitButton = (state: SplitButtonState): JSXElement => { + 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..54d24ed7 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/SplitButton/useSplitButton.ts @@ -0,0 +1,86 @@ +import { Button, MenuButton } from '@fluentui/react-button'; +import { + getIntrinsicElementProps, + useId, + slot, +} from '@fluentui/react-utilities'; +import type * as React from 'react'; +import type { + SplitButtonProps, + SplitButtonState, +} from '../../../../customStyleHooks/react-button'; + +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: appearance as any, + disabled, + disabledFocusable, + menuIcon, + size, + }, + renderByDefault: true, + elementType: MenuButton, + }); + + const primaryActionButtonShorthand = slot.optional(primaryActionButton, { + defaultProps: { + appearance: appearance as any, + 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/ToggleButton/ToggleButton.tsx b/packages/react-cap-theme/src/components/react-button/components/ToggleButton/ToggleButton.tsx new file mode 100644 index 00000000..f3ac9864 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/ToggleButton/ToggleButton.tsx @@ -0,0 +1,20 @@ +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import * as React from 'react'; +import { renderToggleButton } from './renderToggleButton'; +import { useToggleButton } from './useToggleButton'; +import { useToggleButtonStyles_unstable } from '@fluentui/react-components'; +import { useToggleButtonStyles as useCAPToggleButton } from '../../../../customStyleHooks/react-button'; +import type { ToggleButtonProps } from '../../../../customStyleHooks/react-button'; +import { toBaseState } from '../../utils/toBaseState'; + +export const ToggleButton: ForwardRefComponent = + React.forwardRef((props, ref) => { + const state = useToggleButton(props, ref); + + useToggleButtonStyles_unstable(toBaseState(state)); + useCAPToggleButton(state); + + return renderToggleButton(state); + }) as ForwardRefComponent; + +ToggleButton.displayName = 'ToggleButton'; 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..2d736627 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/components/ToggleButton/renderToggleButton.tsx @@ -0,0 +1,6 @@ +import type { JSXElement } from '@fluentui/react-utilities'; +import { renderButton } from '../Button/renderButton'; +import type { ToggleButtonState } from '../../../../customStyleHooks/react-button'; + +export const renderToggleButton = (state: ToggleButtonState): JSXElement => + 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..9924e0d5 --- /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/useButton'; + +import type { + ToggleButtonProps, + ToggleButtonState, +} from '../../../../customStyleHooks/react-button'; + +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/index.ts b/packages/react-cap-theme/src/components/react-button/index.ts index b83e6a91..09615f89 100644 --- a/packages/react-cap-theme/src/components/react-button/index.ts +++ b/packages/react-cap-theme/src/components/react-button/index.ts @@ -1,24 +1,4 @@ -export { useButtonStyles } from './Button'; -export type { - ButtonProps, - ButtonSlots, - ButtonState, - ButtonAppearance, -} from './Button'; - -export { useMenuButtonStyles } from './MenuButton'; -export type { - MenuButtonProps, - MenuButtonSlots, - MenuButtonState, -} from './MenuButton'; - -export { useSplitButtonStyles } from './SplitButton'; -export type { - SplitButtonProps, - SplitButtonSlots, - SplitButtonState, -} from './SplitButton'; - -export { useToggleButtonStyles } from './ToggleButton'; -export type { ToggleButtonProps, ToggleButtonState } from './ToggleButton'; +export { CAPButton, renderButton, useButton } from './Button'; +export { CAPMenuButton } from './MenuButton'; +export { CAPSplitButton } from './SplitButton'; +export { CAPToggleButton, useToggleButton } from './ToggleButton'; diff --git a/packages/react-cap-theme/src/components/react-button/utils/toBaseState.ts b/packages/react-cap-theme/src/components/react-button/utils/toBaseState.ts new file mode 100644 index 00000000..e0a28574 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-button/utils/toBaseState.ts @@ -0,0 +1,27 @@ +import type { ButtonProps as BaseButtonProps } from '@fluentui/react-button'; +import type { ButtonAppearance } from '../../../customStyleHooks/react-button'; + +export const baseAppearanceMap: Record< + ButtonAppearance, + BaseButtonProps['appearance'] +> = { + secondary: 'secondary', + primary: 'primary', + outline: 'outline', + subtle: 'subtle', + transparent: 'transparent', + tint: 'primary', +}; + +/** + * Maps a CAP state's `appearance` to a base Fluent UI appearance. + * + * Shallow spread — slot objects (root, icon, etc.) remain shared references, + * so className mutations by base Fluent style hooks propagate to the original state. + */ +export const toBaseState = ( + state: S +): S & { appearance: BaseButtonProps['appearance'] } => ({ + ...state, + appearance: baseAppearanceMap[state.appearance] ?? 'secondary', +}); diff --git a/packages/react-cap-theme/src/components/react-carousel/components/Carousel/Carousel.tsx b/packages/react-cap-theme/src/components/react-carousel/components/Carousel/Carousel.tsx new file mode 100644 index 00000000..626b6068 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/Carousel/Carousel.tsx @@ -0,0 +1,22 @@ +import { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import * as React from 'react'; +import { renderCarousel } from './renderCarousel'; +import { useCarousel } from './useCarousel'; +import { useCarouselContextValues } from './useCarouselContextValues'; +import { useCarouselStyles } from '../../../../customStyleHooks/react-carousel'; +import type { CarouselProps } from '../../../../customStyleHooks/react-carousel'; + +export const Carousel: ForwardRefComponent = React.forwardRef( + (props, ref) => { + const state = useCarousel(props, ref); + const contextValues = useCarouselContextValues(state); + + useCarouselStyles(state); + useCustomStyleHook_unstable('useCarouselStyles_unstable')(state); + + return renderCarousel(state, contextValues); + } +); + +Carousel.displayName = 'Carousel'; diff --git a/packages/react-cap-theme/src/components/react-carousel/components/Carousel/CarouselContext.ts b/packages/react-cap-theme/src/components/react-carousel/components/Carousel/CarouselContext.ts new file mode 100644 index 00000000..ea5922ea --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/Carousel/CarouselContext.ts @@ -0,0 +1,25 @@ +import { carouselContextDefaultValue as fluentCarouselContextDefaultValue } from '@fluentui/react-carousel'; +import { + type ContextSelector, + createContext, + useContextSelector, +} from '@fluentui/react-context-selector'; +import type { CarouselContextValues } from '../../../../customStyleHooks/react-carousel'; + +export const carouselContextDefaultValue: CarouselContextValues = { + carousel: fluentCarouselContextDefaultValue, + layout: 'inline', +}; + +const CarouselContext = createContext( + undefined +); + +export const CarouselProvider = CarouselContext.Provider; + +export const useCarouselContext = ( + selector: ContextSelector +): T => + useContextSelector(CarouselContext, (ctx = carouselContextDefaultValue) => + selector(ctx) + ); diff --git a/packages/react-cap-theme/src/components/react-carousel/components/Carousel/renderCarousel.tsx b/packages/react-cap-theme/src/components/react-carousel/components/Carousel/renderCarousel.tsx new file mode 100644 index 00000000..b617d495 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/Carousel/renderCarousel.tsx @@ -0,0 +1,28 @@ +/** @jsxRuntime automatic */ +/** @jsxImportSource @fluentui/react-jsx-runtime */ +import { + type CarouselSlots, + renderCarousel_unstable, +} from '@fluentui/react-carousel'; +import { assertSlots, type JSXElement } from '@fluentui/react-utilities'; +import type { + CarouselContextValues, + CarouselState, +} from '../../../../customStyleHooks/react-carousel'; +import { CarouselProvider } from './CarouselContext'; + +export const renderCarousel = ( + state: CarouselState, + contextValues: CarouselContextValues +): JSXElement => { + const { layout, ...fluentState } = state; + void layout; + + assertSlots(state); + + return ( + + {renderCarousel_unstable(fluentState, contextValues)} + + ); +}; diff --git a/packages/react-cap-theme/src/components/react-carousel/components/Carousel/useCarousel.ts b/packages/react-cap-theme/src/components/react-carousel/components/Carousel/useCarousel.ts new file mode 100644 index 00000000..bc8cd065 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/Carousel/useCarousel.ts @@ -0,0 +1,16 @@ +import { useCarousel_unstable } from '@fluentui/react-carousel'; +import type * as React from 'react'; +import type { + CarouselProps, + CarouselState, +} from '../../../../customStyleHooks/react-carousel'; + +export const useCarousel = ( + props: CarouselProps, + ref: React.Ref +): CarouselState => { + const { layout = 'inline', ...fluentProps } = props; + const baseState = useCarousel_unstable(fluentProps, ref); + + return { ...baseState, layout }; +}; diff --git a/packages/react-cap-theme/src/components/react-carousel/components/Carousel/useCarouselContextValues.ts b/packages/react-cap-theme/src/components/react-carousel/components/Carousel/useCarouselContextValues.ts new file mode 100644 index 00000000..b8b9eb91 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/Carousel/useCarouselContextValues.ts @@ -0,0 +1,52 @@ +import * as React from 'react'; +import type { + CarouselContextValues, + CarouselState, +} from '../../../../customStyleHooks/react-carousel'; + +export function useCarouselContextValues( + state: CarouselState +): CarouselContextValues { + const { + activeIndex, + circular, + containerRef, + layout, + viewportRef, + enableAutoplay, + resetAutoplay, + selectPageByElement, + selectPageByDirection, + selectPageByIndex, + subscribeForValues, + } = state; + + const carousel = React.useMemo( + () => ({ + activeIndex, + circular, + containerRef, + viewportRef, + enableAutoplay, + resetAutoplay, + selectPageByElement, + selectPageByDirection, + selectPageByIndex, + subscribeForValues, + }), + [ + activeIndex, + circular, + containerRef, + viewportRef, + enableAutoplay, + resetAutoplay, + selectPageByElement, + selectPageByDirection, + selectPageByIndex, + subscribeForValues, + ] + ); + + return { carousel, layout }; +} diff --git a/packages/react-cap-theme/src/components/react-carousel/components/CarouselAutoplayButton/CarouselAutoplayButton.tsx b/packages/react-cap-theme/src/components/react-carousel/components/CarouselAutoplayButton/CarouselAutoplayButton.tsx new file mode 100644 index 00000000..4217c9c9 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/CarouselAutoplayButton/CarouselAutoplayButton.tsx @@ -0,0 +1,21 @@ +import { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import { forwardRef } from 'react'; +import { renderCarouselAutoplayButton } from './renderCarouselAutoplayButton'; +import { useCarouselAutoplayButton } from './useCarouselAutoplayButton'; +import type { CarouselAutoplayButtonProps } from '../../../../customStyleHooks/react-carousel'; +import { useCarouselAutoplayButtonStyles } from '../../../../customStyleHooks/react-carousel'; + +export const CarouselAutoplayButton: ForwardRefComponent = + forwardRef((props, ref) => { + const state = useCarouselAutoplayButton(props, ref); + + useCarouselAutoplayButtonStyles(state); + useCustomStyleHook_unstable('useCarouselAutoplayButtonStyles_unstable')( + state + ); + + return renderCarouselAutoplayButton(state); + }); + +CarouselAutoplayButton.displayName = 'CarouselAutoplayButton'; diff --git a/packages/react-cap-theme/src/components/react-carousel/components/CarouselAutoplayButton/renderCarouselAutoplayButton.tsx b/packages/react-cap-theme/src/components/react-carousel/components/CarouselAutoplayButton/renderCarouselAutoplayButton.tsx new file mode 100644 index 00000000..e5bb6730 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/CarouselAutoplayButton/renderCarouselAutoplayButton.tsx @@ -0,0 +1,14 @@ +import type { JSXElement } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; +import { renderButton } from '../../../react-button'; +import type { + CarouselAutoplayButtonSlots, + CarouselAutoplayButtonState, +} from '../../../../customStyleHooks/react-carousel'; + +export const renderCarouselAutoplayButton = ( + state: CarouselAutoplayButtonState +): JSXElement => { + assertSlots(state); + return renderButton(state); +}; diff --git a/packages/react-cap-theme/src/components/react-carousel/components/CarouselAutoplayButton/useCarouselAutoplayButton.ts b/packages/react-cap-theme/src/components/react-carousel/components/CarouselAutoplayButton/useCarouselAutoplayButton.ts new file mode 100644 index 00000000..4763025c --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/CarouselAutoplayButton/useCarouselAutoplayButton.ts @@ -0,0 +1,36 @@ +import type { ARIAButtonElement } from '@fluentui/react-aria'; +import { useCarouselAutoplayButton_unstable } from '@fluentui/react-carousel'; +import { useToggleButton } from '../../../react-button'; +import type { + CarouselAutoplayButtonProps, + CarouselAutoplayButtonState, +} from '../../../../customStyleHooks/react-carousel'; +import type { CarouselAutoplayButtonProps as FluentCarouselAutoplayButtonProps } from '@fluentui/react-carousel'; + +export const useCarouselAutoplayButton = ( + props: CarouselAutoplayButtonProps, + ref: React.Ref +): CarouselAutoplayButtonState => { + const fluentState = useCarouselAutoplayButton_unstable( + props as FluentCarouselAutoplayButtonProps, + ref + ); + const { onCheckedChange, checked, defaultChecked, ...buttonProps } = props; + void checked; + void defaultChecked; + void onCheckedChange; + + return { + ...useToggleButton( + { + icon: fluentState.icon, + ...buttonProps, + checked: fluentState.checked, + onClick: fluentState.root.onClick as React.MouseEventHandler< + HTMLAnchorElement & HTMLButtonElement + >, + }, + ref as React.Ref + ), + }; +}; diff --git a/packages/react-cap-theme/src/components/react-carousel/components/CarouselButton/CarouselButton.tsx b/packages/react-cap-theme/src/components/react-carousel/components/CarouselButton/CarouselButton.tsx new file mode 100644 index 00000000..d7b0b3ac --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/CarouselButton/CarouselButton.tsx @@ -0,0 +1,19 @@ +import { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import { forwardRef } from 'react'; +import { renderCarouselButton } from './renderCarouselButton'; +import { useCarouselButton } from './useCarouselButton'; +import type { CarouselButtonProps } from '../../../../customStyleHooks/react-carousel'; +import { useCarouselButtonStyles } from '../../../../customStyleHooks/react-carousel'; + +export const CarouselButton: ForwardRefComponent = + forwardRef((props, ref) => { + const state = useCarouselButton(props, ref); + + useCarouselButtonStyles(state); + useCustomStyleHook_unstable('useCarouselButtonStyles_unstable')(state); + + return renderCarouselButton(state); + }); + +CarouselButton.displayName = 'CarouselButton'; diff --git a/packages/react-cap-theme/src/components/react-carousel/components/CarouselButton/renderCarouselButton.tsx b/packages/react-cap-theme/src/components/react-carousel/components/CarouselButton/renderCarouselButton.tsx new file mode 100644 index 00000000..c9482ba1 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/CarouselButton/renderCarouselButton.tsx @@ -0,0 +1,14 @@ +import type { JSXElement } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; +import { renderButton } from '../../../react-button'; +import type { + CarouselButtonSlots, + CarouselButtonState, +} from '../../../../customStyleHooks/react-carousel'; + +export const renderCarouselButton = ( + state: CarouselButtonState +): JSXElement => { + assertSlots(state); + return renderButton(state); +}; diff --git a/packages/react-cap-theme/src/components/react-carousel/components/CarouselButton/useCarouselButton.ts b/packages/react-cap-theme/src/components/react-carousel/components/CarouselButton/useCarouselButton.ts new file mode 100644 index 00000000..2921362e --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/CarouselButton/useCarouselButton.ts @@ -0,0 +1,41 @@ +import type { ARIAButtonElement } from '@fluentui/react-aria'; +import { useCarouselButton_unstable } from '@fluentui/react-carousel'; +import { useButton } from '../../../react-button'; +import type { Ref } from 'react'; +import type { + CarouselButtonProps, + CarouselButtonState, +} from '../../../../customStyleHooks/react-carousel'; +import type { CarouselButtonProps as FluentCarouselButtonProps } from '@fluentui/react-carousel'; + +export const useCarouselButton = ( + props: CarouselButtonProps, + ref: Ref +): CarouselButtonState => { + const fluentState = useCarouselButton_unstable( + props as FluentCarouselButtonProps, + ref + ); + const { navType = 'next', ...buttonProps } = props; + void navType; + + const buttonState = useButton( + { + icon: fluentState.icon, + disabled: fluentState.disabled, + tabIndex: fluentState.root.tabIndex, + 'aria-disabled': fluentState.root['aria-disabled'], + appearance: 'subtle', + ...buttonProps, + onClick: fluentState.root.onClick as React.MouseEventHandler< + HTMLButtonElement & HTMLAnchorElement + >, + }, + fluentState.root.ref as Ref + ); + + return { + ...fluentState, + ...buttonState, + }; +}; diff --git a/packages/react-cap-theme/src/components/react-carousel/components/CarouselNav/CarouselNav.tsx b/packages/react-cap-theme/src/components/react-carousel/components/CarouselNav/CarouselNav.tsx new file mode 100644 index 00000000..b3847c2c --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/CarouselNav/CarouselNav.tsx @@ -0,0 +1,21 @@ +import { renderCarouselNav_unstable } from '@fluentui/react-carousel'; +import { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import * as React from 'react'; +import { useCarouselNav } from './useCarouselNav'; +import { useCarouselNavContextValues } from './useCarouselNavContextValues'; +import type { CarouselNavProps } from '../../../../customStyleHooks/react-carousel'; +import { useCarouselNavStyles } from '../../../../customStyleHooks/react-carousel'; + +export const CarouselNav: ForwardRefComponent = + React.forwardRef((props, ref) => { + const state = useCarouselNav(props, ref); + const contextValues = useCarouselNavContextValues(state); + + useCarouselNavStyles(state); + useCustomStyleHook_unstable('useCarouselNavStyles_unstable')(state); + + return renderCarouselNav_unstable(state, contextValues); + }); + +CarouselNav.displayName = 'CarouselNav'; diff --git a/packages/react-cap-theme/src/components/react-carousel/components/CarouselNav/useCarouselNav.ts b/packages/react-cap-theme/src/components/react-carousel/components/CarouselNav/useCarouselNav.ts new file mode 100644 index 00000000..decc6841 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/CarouselNav/useCarouselNav.ts @@ -0,0 +1,15 @@ +import { useCarouselNav_unstable } from '@fluentui/react-carousel'; +import type { + CarouselNavProps, + CarouselNavState, +} from '../../../../customStyleHooks/react-carousel'; + +export const useCarouselNav = ( + props: CarouselNavProps, + ref: React.Ref +): CarouselNavState => { + const { density = 'compact' } = props; + const baseState = useCarouselNav_unstable(props, ref); + + return { ...baseState, density }; +}; diff --git a/packages/react-cap-theme/src/components/react-carousel/components/CarouselNav/useCarouselNavContextValues.ts b/packages/react-cap-theme/src/components/react-carousel/components/CarouselNav/useCarouselNavContextValues.ts new file mode 100644 index 00000000..0ce792bf --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/CarouselNav/useCarouselNavContextValues.ts @@ -0,0 +1,14 @@ +import * as React from 'react'; +import type { + CarouselNavContextValues, + CarouselNavState, +} from '../../../../customStyleHooks/react-carousel'; + +export const useCarouselNavContextValues = ( + state: CarouselNavState +): CarouselNavContextValues => { + const { appearance } = state; + const carouselNav = React.useMemo(() => ({ appearance }), [appearance]); + + return { carouselNav }; +}; diff --git a/packages/react-cap-theme/src/components/react-carousel/components/CarouselNavContainer/CarouselNavContainer.tsx b/packages/react-cap-theme/src/components/react-carousel/components/CarouselNavContainer/CarouselNavContainer.tsx new file mode 100644 index 00000000..cf913962 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/CarouselNavContainer/CarouselNavContainer.tsx @@ -0,0 +1,19 @@ +import { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import * as React from 'react'; +import { renderCarouselNavContainer } from './renderCarouselNavContainer'; +import { useCarouselNavContainer } from './useCarouselNavContainer'; +import type { CarouselNavContainerProps } from '../../../../customStyleHooks/react-carousel'; +import { useCarouselNavContainerStyles } from '../../../../customStyleHooks/react-carousel'; + +export const CarouselNavContainer: ForwardRefComponent = + React.forwardRef((props, ref) => { + const state = useCarouselNavContainer(props, ref); + useCarouselNavContainerStyles(state); + useCustomStyleHook_unstable('useCarouselNavContainerStyles_unstable')( + state + ); + return renderCarouselNavContainer(state); + }); + +CarouselNavContainer.displayName = 'CarouselNavContainer'; diff --git a/packages/react-cap-theme/src/components/react-carousel/components/CarouselNavContainer/renderCarouselNavContainer.tsx b/packages/react-cap-theme/src/components/react-carousel/components/CarouselNavContainer/renderCarouselNavContainer.tsx new file mode 100644 index 00000000..0c9ad5e2 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/CarouselNavContainer/renderCarouselNavContainer.tsx @@ -0,0 +1,37 @@ +/** @jsxRuntime automatic */ +/** @jsxImportSource @fluentui/react-jsx-runtime */ +import { assertSlots, type JSXElement } from '@fluentui/react-utilities'; +import type { + CarouselNavContainerSlots, + CarouselNavContainerState, +} from '../../../../customStyleHooks/react-carousel'; + +export const renderCarouselNavContainer = ( + state: CarouselNavContainerState +): JSXElement => { + assertSlots(state); + + return ( + + {!state.autoplayTooltip && state.autoplay && } + {state.autoplayTooltip && state.autoplay && ( + + + + )} + {!state.prevTooltip && state.prev && } + {state.prevTooltip && state.prev && ( + + + + )} + {state.root.children} + {!state.nextTooltip && state.next && } + {state.nextTooltip && state.next && ( + + + + )} + + ); +}; diff --git a/packages/react-cap-theme/src/components/react-carousel/components/CarouselNavContainer/useCarouselNavContainer.ts b/packages/react-cap-theme/src/components/react-carousel/components/CarouselNavContainer/useCarouselNavContainer.ts new file mode 100644 index 00000000..24b61139 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-carousel/components/CarouselNavContainer/useCarouselNavContainer.ts @@ -0,0 +1,49 @@ +import type { CarouselNavContainerProps as FluentCarouselNavContainerProps } from '@fluentui/react-carousel'; +import { useCarouselNavContainer_unstable } from '@fluentui/react-carousel'; +import { slot } from '@fluentui/react-utilities'; +import { CAPTooltip } from '../../../react-tooltip'; +import { useCarouselContext } from '../Carousel/CarouselContext'; +import type { + CarouselNavContainerProps, + CarouselNavContainerState, +} from '../../../../customStyleHooks/react-carousel'; + +export const useCarouselNavContainer = ( + props: CarouselNavContainerProps, + ref: React.Ref +): CarouselNavContainerState => { + const layout = useCarouselContext((ctx) => props.layout ?? ctx.layout); + const baseLayout = layout === 'inline-expanded' ? 'overlay-expanded' : layout; + const baseState = useCarouselNavContainer_unstable( + { ...props, layout: baseLayout } as FluentCarouselNavContainerProps, + ref + ); + + baseState.components.nextTooltip = CAPTooltip; + baseState.components.prevTooltip = CAPTooltip; + baseState.components.autoplayTooltip = CAPTooltip; + const nextTooltip = slot.optional(props.nextTooltip, { + defaultProps: {}, + elementType: CAPTooltip, + renderByDefault: false, + }); + const prevTooltip = slot.optional(props.prevTooltip, { + defaultProps: {}, + elementType: CAPTooltip, + renderByDefault: false, + }); + const autoplayTooltip = slot.optional(props.autoplayTooltip, { + defaultProps: {}, + elementType: CAPTooltip, + renderByDefault: false, + }); + + return { + ...baseState, + autoplayTooltip, + baseLayout, + layout, + nextTooltip, + prevTooltip, + }; +}; diff --git a/packages/react-cap-theme/src/components/react-carousel/index.ts b/packages/react-cap-theme/src/components/react-carousel/index.ts index 5f853910..565df5a7 100644 --- a/packages/react-cap-theme/src/components/react-carousel/index.ts +++ b/packages/react-cap-theme/src/components/react-carousel/index.ts @@ -1,18 +1,5 @@ -export { useCarouselStyles } from './components/Carousel/useCarouselStyles.styles'; -export type { CarouselState } from './components/Carousel/Carousel.types'; - -export { useCarouselAutoplayButtonStyles } from './components/CarouselAutoplayButton/useCarouselAutoplayButtonStyles.styles'; -export type { CarouselAutoplayButtonState } from './components/CarouselAutoplayButton/CarouselAutoplayButton.types'; - -export { useCarouselButtonStyles } from './components/CarouselButton/useCarouselButtonStyles.styles'; -export type { CarouselButtonState } from './components/CarouselButton/CarouselButton.types'; - -export { useCarouselNavStyles } from './components/CarouselNav/useCarouselNavStyles.styles'; -export type { CarouselNavState } from './components/CarouselNav/CarouselNav.types'; - -export { useCarouselNavButtonStyles } from './components/CarouselNavButton/useCarouselNavButtonStyles.styles'; - -export { useCarouselNavContainerStyles } from './components/CarouselNavContainer/useCarouselNavContainerStyles.styles'; -export type { CarouselNavContainerState } from './components/CarouselNavContainer/CarouselNavContainer.types'; - -export { useCarouselNavImageButtonStyles } from './components/CarouselNavImageButton/useCarouselNavImageButtonStyles.styles'; +export { Carousel as CAPCarousel } from './components/Carousel/Carousel'; +export { CarouselAutoplayButton as CAPCarouselAutoplayButton } from './components/CarouselAutoplayButton/CarouselAutoplayButton'; +export { CarouselButton as CAPCarouselButton } from './components/CarouselButton/CarouselButton'; +export { CarouselNav as CAPCarouselNav } from './components/CarouselNav/CarouselNav'; +export { CarouselNavContainer as CAPCarouselNavContainer } from './components/CarouselNavContainer/CarouselNavContainer'; diff --git a/packages/react-cap-theme/src/components/react-checkbox/components/Checkbox/Checkbox.tsx b/packages/react-cap-theme/src/components/react-checkbox/components/Checkbox/Checkbox.tsx new file mode 100644 index 00000000..2d69bb8f --- /dev/null +++ b/packages/react-cap-theme/src/components/react-checkbox/components/Checkbox/Checkbox.tsx @@ -0,0 +1,27 @@ +import { renderCheckbox_unstable } from '@fluentui/react-checkbox'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import * as React from 'react'; +import { useCheckbox } from './useCheckbox'; +import { useCheckboxStyles_unstable } from '@fluentui/react-components'; +import { useCheckboxStyles as useCAPCheckboxStyles } from '../../../../customStyleHooks/react-checkbox'; +import type { CheckboxProps } from '../../../../customStyleHooks/react-checkbox'; +/** + * Experimental Checkbox component that provides enhanced styling and behavior for SharePoint. + * + * @param props - The checkbox configuration and event handlers + * @param ref - Reference to the checkbox input element + * @returns The rendered checkbox component + * @alpha + */ +export const Checkbox: ForwardRefComponent = React.forwardRef( + (props, ref) => { + const state = useCheckbox(props, ref); + + useCheckboxStyles_unstable(state); + useCAPCheckboxStyles(state); + + return renderCheckbox_unstable(state); + } +); + +Checkbox.displayName = 'Checkbox'; diff --git a/packages/react-cap-theme/src/components/react-checkbox/components/Checkbox/useCheckbox.tsx b/packages/react-cap-theme/src/components/react-checkbox/components/Checkbox/useCheckbox.tsx new file mode 100644 index 00000000..f3fb0339 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-checkbox/components/Checkbox/useCheckbox.tsx @@ -0,0 +1,66 @@ +import { useCheckbox_unstable as useBaseState } from '@fluentui/react-checkbox'; +import { + CheckboxUncheckedRegular, + CheckboxCheckedFilled, + CheckboxCheckedRegular, + CheckboxIndeterminateRegular, + RadioButtonRegular, + RadioButtonFilled, + CheckmarkCircleFilled, + CheckmarkCircleRegular, + bundleIcon, +} from '@fluentui/react-icons'; +import { slot } from '@fluentui/react-utilities'; +import * as React from 'react'; +import type { + CheckboxProps, + CheckboxState, +} from '../../../../customStyleHooks/react-checkbox/components/Checkbox/Checkbox.types'; + +const CircleCheckmark = bundleIcon(CheckmarkCircleRegular, RadioButtonRegular); +const SquareCheckmark = bundleIcon( + CheckboxCheckedRegular, + CheckboxUncheckedRegular +); + +export const useCheckbox = ( + props: CheckboxProps, + ref: React.Ref +): CheckboxState => { + const { color = 'brand', ...baseProps } = props; + + const baseState = useBaseState(baseProps, ref); + const { checked, shape } = baseState; + + const isChecked = checked === true; + const isMixed = checked === 'mixed'; + const isCircular = shape === 'circular'; + + let checkmarkIcon; + if (isMixed) { + checkmarkIcon = isCircular ? ( + + ) : ( + + ); + } else if (isCircular) { + checkmarkIcon = isChecked ? : ; + } else { + checkmarkIcon = isChecked ? : ; + } + + const indicator = slot.optional(props.indicator, { + renderByDefault: true, + elementType: 'div', + defaultProps: { + children: checkmarkIcon, + 'aria-hidden': 'true', + }, + }); + + return { + ...baseState, + indicator, + color, + }; +}; diff --git a/packages/react-cap-theme/src/components/react-checkbox/index.ts b/packages/react-cap-theme/src/components/react-checkbox/index.ts index beca3725..9224d90b 100644 --- a/packages/react-cap-theme/src/components/react-checkbox/index.ts +++ b/packages/react-cap-theme/src/components/react-checkbox/index.ts @@ -1,2 +1 @@ -export { useCheckboxStyles } from './components/Checkbox/useCheckboxStyles.styles'; -export type { CheckboxState } from './components/Checkbox/Checkbox.types'; +export { Checkbox as CAPCheckbox } from './components/Checkbox/Checkbox'; diff --git a/packages/react-cap-theme/src/components/react-dialog/components/DialogTitle/DialogTitle.tsx b/packages/react-cap-theme/src/components/react-dialog/components/DialogTitle/DialogTitle.tsx new file mode 100644 index 00000000..e4d2f147 --- /dev/null +++ b/packages/react-cap-theme/src/components/react-dialog/components/DialogTitle/DialogTitle.tsx @@ -0,0 +1,22 @@ +import { + renderDialogTitle_unstable, + useDialogTitleStyles_unstable, +} from '@fluentui/react-dialog'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import { forwardRef } from 'react'; +import { useDialogTitle } from './useDialogTitle'; +import { useDialogTitleStyles as useCAPDialogTitleStyles } from '../../../../customStyleHooks/react-dialog'; +import type { DialogTitleProps } from '../../../../customStyleHooks/react-dialog'; + +export const DialogTitle: ForwardRefComponent = forwardRef( + (props, ref) => { + const state = useDialogTitle(props, ref); + + useDialogTitleStyles_unstable(state); + useCAPDialogTitleStyles(state); + + return renderDialogTitle_unstable(state); + } +); + +DialogTitle.displayName = 'DialogTitle'; diff --git a/packages/react-cap-theme/src/components/react-dialog/components/DialogTitle/useDialogTitle.tsx b/packages/react-cap-theme/src/components/react-dialog/components/DialogTitle/useDialogTitle.tsx new file mode 100644 index 00000000..ca660ded --- /dev/null +++ b/packages/react-cap-theme/src/components/react-dialog/components/DialogTitle/useDialogTitle.tsx @@ -0,0 +1,34 @@ +import { Button } from '@fluentui/react-button'; +import { + DialogTrigger, + useDialogContext_unstable, + useDialogTitle_unstable, +} from '@fluentui/react-dialog'; +import { Dismiss16Regular } from '@fluentui/react-icons'; +import * as React from 'react'; +import type { DialogTitleState } from '@fluentui/react-dialog'; +import type { DialogTitleProps } from '../../../../customStyleHooks/react-dialog'; + +export const useDialogTitle = ( + props: DialogTitleProps, + ref: React.Ref +): DialogTitleState => { + const { closeButton: closeButtonProps, ...restProps } = props; + const modalType = useDialogContext_unstable((context) => context.modalType); + + const action = + restProps.action ?? + (modalType === 'non-modal' ? ( + +