Skip to content

Commit

Permalink
feat(organizations): add theming/story for Mantine ActionIcon TASK-14…
Browse files Browse the repository at this point in the history
…48 (#5412)

### 📣 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
  • Loading branch information
jamesrkiger authored Jan 20, 2025
1 parent 1d9c316 commit e424952
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 2 deletions.
27 changes: 27 additions & 0 deletions jsapp/js/components/common/ActionIcon.tsx
Original file line number Diff line number Diff line change
@@ -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<ActionIconPropsMantine, 'size'> {
iconName: IconName;
size: 'sm' | 'md' | 'lg';
}

const ActionIcon = forwardRef<HTMLButtonElement, ActionIconProps>(
({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 (
<ActionIconMantine {...props} ref={ref}>
<Icon name={iconName} size={iconSize} />
</ActionIconMantine>
);
}
);

export default ActionIcon
116 changes: 116 additions & 0 deletions jsapp/js/components/common/actionIcon.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<ActionIconProps['variant']> = [
'filled',
'light',

//// Custom:
'danger',
'danger-secondary',
'transparent',
];

const actionIconSizes: Array<ActionIconProps['size']> = [
'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<typeof ActionIcon>;

type Story = StoryObj<typeof ActionIcon>;

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 = () => (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, auto)',
gridAutoFlow: 'row',
gridGap: '30px 15px',
justifyItems: 'start',
padding: '10px',
}}
>
{actionIconVariants.map((variant) =>
actionIconSizes.map((size) => {
const actionIconProps: ActionIconProps = {
variant,
size: size,
iconName: 'more',
};
return (
<>
<ActionIcon {...actionIconProps} />
<ActionIcon {...actionIconProps} loading />
<ActionIcon {...actionIconProps} disabled />
</>
);
})
)}
</div>
);
7 changes: 7 additions & 0 deletions jsapp/js/theme/kobo/ActionIcon.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.root {
&[data-disabled] {
color: var(--ai-color);
background: var(--ai-bg);
opacity: 0.5;
}
}
38 changes: 38 additions & 0 deletions jsapp/js/theme/kobo/ActionIcon.ts
Original file line number Diff line number Diff line change
@@ -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],
}),
},
};
},
});
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
4 changes: 3 additions & 1 deletion jsapp/js/theme/kobo/index.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand Down Expand Up @@ -87,6 +88,7 @@ export const themeKobo = createTheme({
},

components: {
ActionIcon: ActionIconThemeKobo,
Button: ButtonThemeKobo,
Menu: MenuThemeKobo,
Tooltip: TooltipThemeKobo,
Expand Down

0 comments on commit e424952

Please sign in to comment.