From e424952bb07c0ef1346e06fbb3e25bf6f705973d Mon Sep 17 00:00:00 2001 From: James Kiger <68701146+jamesrkiger@users.noreply.github.com> Date: Mon, 20 Jan 2025 11:59:58 -0500 Subject: [PATCH] feat(organizations): add theming/story for Mantine ActionIcon TASK-1448 (#5412) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### 📣 Summary Adds theming and storybook entry for a custom ActionIcon component that automatically includes an appropriate sized icon. ### 💭 Notes We previously used our custom in-house button component to display icon buttons. With Mantine, however, these kinds of buttons are rendered with the ActionIcon component. ### 👀 Preview steps Use storybook stories and compare to existing icon buttons in the repo. Existing examples include: - archive/share/delete options in project list view - edit/delete options in user permissions section of sharing view - member actions dropdown trigger in organizations member table --- jsapp/js/components/common/ActionIcon.tsx | 27 ++++ .../components/common/actionIcon.stories.tsx | 116 ++++++++++++++++++ jsapp/js/theme/kobo/ActionIcon.module.css | 7 ++ jsapp/js/theme/kobo/ActionIcon.ts | 38 ++++++ .../kobo/{menu.module.css => Menu.module.css} | 0 jsapp/js/theme/kobo/{menu.ts => Menu.ts} | 2 +- jsapp/js/theme/kobo/index.ts | 4 +- 7 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 jsapp/js/components/common/ActionIcon.tsx create mode 100644 jsapp/js/components/common/actionIcon.stories.tsx create mode 100644 jsapp/js/theme/kobo/ActionIcon.module.css create mode 100644 jsapp/js/theme/kobo/ActionIcon.ts rename jsapp/js/theme/kobo/{menu.module.css => Menu.module.css} (100%) rename jsapp/js/theme/kobo/{menu.ts => Menu.ts} (91%) diff --git a/jsapp/js/components/common/ActionIcon.tsx b/jsapp/js/components/common/ActionIcon.tsx new file mode 100644 index 0000000000..65a2f8ef49 --- /dev/null +++ b/jsapp/js/components/common/ActionIcon.tsx @@ -0,0 +1,27 @@ +import { + ActionIcon as ActionIconMantine, +} from '@mantine/core'; +import type {ActionIconProps as ActionIconPropsMantine} from '@mantine/core/lib/components'; +import Icon, {IconSize} from './icon'; +import type {IconName} from 'jsapp/fonts/k-icons'; +import {forwardRef} from 'react'; + +export interface ActionIconProps extends Omit { + iconName: IconName; + size: 'sm' | 'md' | 'lg'; +} + +const ActionIcon = forwardRef( + ({iconName, ...props}, ref) => { + // Currently, our icon sizes only use a single letter instead of + // Mantine's 'sm', 'md', etc. So here we grab the first letter. + const iconSize = props.size[0] as IconSize; + return ( + + + + ); + } +); + +export default ActionIcon diff --git a/jsapp/js/components/common/actionIcon.stories.tsx b/jsapp/js/components/common/actionIcon.stories.tsx new file mode 100644 index 0000000000..b9bd117d85 --- /dev/null +++ b/jsapp/js/components/common/actionIcon.stories.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import type {Meta, StoryObj} from '@storybook/react'; +import {IconNames} from 'jsapp/fonts/k-icons'; +import ActionIcon, {type ActionIconProps} from './ActionIcon'; + +const actionIconVariants: Array = [ + 'filled', + 'light', + + //// Custom: + 'danger', + 'danger-secondary', + 'transparent', +]; + +const actionIconSizes: Array = [ + 'sm', + 'md', + 'lg', +]; + +export default { + title: 'common/Action Icon', + component: ActionIcon, + argTypes: { + variant: { + description: 'Variant of action icon', + options: actionIconVariants, + control: 'select', + }, + size: { + description: 'Size of action icon', + options: actionIconSizes, + control: 'radio', + }, + iconName: { + description: 'Icon', + options: Object.keys(IconNames), + control: {type: 'select'}, + }, + disabled: {control: 'boolean'}, + loading: {control: 'boolean'}, + }, +} as Meta; + +type Story = StoryObj; + +export const Filled: Story = { + args: { + variant: 'filled', + size: 'md', + iconName: 'edit', + }, +}; + +export const Light: Story = { + args: { + variant: 'light', + size: 'md', + iconName: 'edit', + }, +}; + +export const Transparent: Story = { + args: { + variant: 'transparent', + size: 'md', + iconName: 'more', + }, +}; + +export const Danger: Story = { + args: { + variant: 'danger', + size: 'md', + iconName: 'trash', + }, +}; + +export const DangerSecondary: Story = { + args: { + variant: 'danger-secondary', + size: 'lg', + iconName: 'trash', + }, +}; + +export const AllIconStyles = () => ( +
+ {actionIconVariants.map((variant) => + actionIconSizes.map((size) => { + const actionIconProps: ActionIconProps = { + variant, + size: size, + iconName: 'more', + }; + return ( + <> + + + + + ); + }) + )} +
+); diff --git a/jsapp/js/theme/kobo/ActionIcon.module.css b/jsapp/js/theme/kobo/ActionIcon.module.css new file mode 100644 index 0000000000..4b2534b16b --- /dev/null +++ b/jsapp/js/theme/kobo/ActionIcon.module.css @@ -0,0 +1,7 @@ +.root { + &[data-disabled] { + color: var(--ai-color); + background: var(--ai-bg); + opacity: 0.5; + } +} diff --git a/jsapp/js/theme/kobo/ActionIcon.ts b/jsapp/js/theme/kobo/ActionIcon.ts new file mode 100644 index 0000000000..bf258143d5 --- /dev/null +++ b/jsapp/js/theme/kobo/ActionIcon.ts @@ -0,0 +1,38 @@ +import {rem, ActionIcon} from '@mantine/core'; +import classes from './ActionIcon.module.css'; + +export const ActionIconThemeKobo = ActionIcon.extend({ + classNames: classes, + vars: (theme, props) => { + return { + root: { + '--ai-size-sm': rem(28), + '--ai-size-md': rem(32), + '--ai-size-lg': rem(38), + + ...(props.variant === 'filled' && { + '--ai-hover': theme.colors.blue[5], + }), + ...(props.variant === 'light' && { + '--ai-color': theme.colors.blue[4], + '--ai-bg': theme.colors.blue[9], + '--ai-hover': theme.colors.blue[8], + }), + ...(props.variant === 'transparent' && { + '--ai-color': theme.colors.blue[4], + '--ai-hover-color': theme.colors.blue[5], + }), + ...(props.variant === 'danger' && { + '--ai-color': 'var(--mantine-color-white)', + '--ai-bg': theme.colors.red[6], + '--ai-hover': theme.colors.red[5], + }), + ...(props.variant === 'danger-secondary' && { + '--ai-color': theme.colors.red[5], + '--ai-bg': theme.colors.red[9], + '--ai-hover': theme.colors.red[8], + }), + }, + }; + }, +}); diff --git a/jsapp/js/theme/kobo/menu.module.css b/jsapp/js/theme/kobo/Menu.module.css similarity index 100% rename from jsapp/js/theme/kobo/menu.module.css rename to jsapp/js/theme/kobo/Menu.module.css diff --git a/jsapp/js/theme/kobo/menu.ts b/jsapp/js/theme/kobo/Menu.ts similarity index 91% rename from jsapp/js/theme/kobo/menu.ts rename to jsapp/js/theme/kobo/Menu.ts index 82e5ee6dc7..16b7008d89 100644 --- a/jsapp/js/theme/kobo/menu.ts +++ b/jsapp/js/theme/kobo/Menu.ts @@ -1,5 +1,5 @@ import {Menu} from '@mantine/core'; -import classes from './menu.module.css'; +import classes from './Menu.module.css'; declare module '@mantine/core' { export interface MenuItemProps { diff --git a/jsapp/js/theme/kobo/index.ts b/jsapp/js/theme/kobo/index.ts index e76c356b33..d93364f1ee 100644 --- a/jsapp/js/theme/kobo/index.ts +++ b/jsapp/js/theme/kobo/index.ts @@ -1,8 +1,9 @@ import {createTheme, rem} from '@mantine/core'; +import {ActionIconThemeKobo} from './ActionIcon'; import {ButtonThemeKobo} from './Button'; import {TableThemeKobo} from './Table'; import {TooltipThemeKobo} from './Tooltip'; -import {MenuThemeKobo} from './menu'; +import {MenuThemeKobo} from './Menu'; export const themeKobo = createTheme({ primaryColor: 'blue', @@ -87,6 +88,7 @@ export const themeKobo = createTheme({ }, components: { + ActionIcon: ActionIconThemeKobo, Button: ButtonThemeKobo, Menu: MenuThemeKobo, Tooltip: TooltipThemeKobo,