Skip to content

Commit

Permalink
Merge pull request #4740 from kobotoolbox/button-disabled-improve-styles
Browse files Browse the repository at this point in the history
Add tooltip Component for disabled buttons
  • Loading branch information
magicznyleszek authored Jan 15, 2024
2 parents 486cbe7 + e8be5b2 commit fac8cae
Show file tree
Hide file tree
Showing 10 changed files with 303 additions and 145 deletions.
2 changes: 1 addition & 1 deletion jsapp/js/components/common/button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ $button-border-radius: sizes.$x6;
.k-button {
cursor: pointer;
color: inherit;
display: flex;
display: inline-flex;
flex-direction: row;
align-items: center;
align-content: center;
Expand Down
65 changes: 64 additions & 1 deletion jsapp/js/components/common/button.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,74 @@
import React from 'react';
import type {ComponentStory, ComponentMeta} from '@storybook/react';
import Button from './button';
import type {ButtonType, ButtonColor, ButtonSize} from './button';
import type {TooltipAlignment} from './tooltip';
import {IconNames} from 'jsapp/fonts/k-icons';

const buttonTypes: ButtonType[] = ['bare', 'frame', 'full'];

const buttonColors: ButtonColor[] = [
'blue',
'light-blue',
'red',
'storm',
'cloud',
'dark-red',
'dark-blue',
];

const buttonSizes: ButtonSize[] = ['s', 'm', 'l'];

const tooltipPositions: TooltipAlignment[] = ['right', 'left', 'center'];

export default {
title: 'common/Button',
component: Button,
argTypes: {},
argTypes: {
type: {
description: 'Type of button',
options: buttonTypes,
control: 'select',
},
color: {
description: 'Color of button',
options: buttonColors,
control: 'select',
},
size: {
description: 'Size of button',
options: buttonSizes,
control: 'radio',
},
startIcon: {
description: 'Icon on the beginning (please use only one of the icons)',
options: Object.keys(IconNames),
control: {type: 'select'},
},
endIcon: {
description: 'Icon on the end (please use only one of the icons)',
options: Object.keys(IconNames),
control: {type: 'select'},
},
label: {
control: 'text',
},
tooltip: {
description: 'Tooltip text',
control: 'text',
},
tooltipPosition: {
description: 'Position of the tooltip (optional)',
options: tooltipPositions,
control: 'radio',
},
isDisabled: {control: 'boolean'},
isPending: {control: 'boolean'},
isFullWidth: {
description: 'Makes the button take 100% width of the container',
control: 'boolean',
},
},
} as ComponentMeta<typeof Button>;

const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;
Expand Down
25 changes: 21 additions & 4 deletions jsapp/js/components/common/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type {IconName} from 'jsapp/fonts/k-icons';
import type {IconSize} from 'js/components/common/icon';
import Icon from 'js/components/common/icon';
import './button.scss';
import type {TooltipAlignment} from './tooltip';
import Tooltip from './tooltip';

/**
* Note: we use a simple TypeScript types here instead of enums, so we don't
Expand Down Expand Up @@ -61,6 +63,8 @@ export interface ButtonProps {
* for icon-only buttons.
*/
tooltip?: string;
/** Sets the alignment of the tooltip */
tooltipPosition?: TooltipAlignment;
isDisabled?: boolean;
/** Changes the appearance to display spinner. */
isPending?: boolean;
Expand Down Expand Up @@ -134,9 +138,6 @@ const Button = (props: ButtonProps) => {

// For the attributes that don't have a falsy value.
const additionalButtonAttributes: AdditionalButtonAttributes = {};
if (props.tooltip) {
additionalButtonAttributes['data-tip'] = props.tooltip;
}
if (props['data-cy']) {
additionalButtonAttributes['data-cy'] = props['data-cy'];
}
Expand All @@ -153,7 +154,7 @@ const Button = (props: ButtonProps) => {
}
};

return (
const renderButton = () => (
<button
className={classNames.join(' ')}
type={props.isSubmit ? 'submit' : 'button'}
Expand All @@ -176,6 +177,22 @@ const Button = (props: ButtonProps) => {
)}
</button>
);

return (
<>
{props.tooltip !== undefined ? (
<Tooltip
text={props.tooltip}
ariaLabel={props.tooltip}
alignment={props.tooltipPosition}
>
{renderButton()}
</Tooltip>
) : (
renderButton()
)}
</>
);
};

export default Button;
File renamed without changes.
56 changes: 56 additions & 0 deletions jsapp/js/components/common/tooltip.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import type {Meta, Story} from '@storybook/react';

import type {TooltipProps, TooltipAlignment} from './tooltip';
import Tooltip from './tooltip';

const tooltipPositions: TooltipAlignment[] = ['right', 'left', 'center'];

export default {
title: 'Common/Tooltip',
component: Tooltip,
description:
'This is a component that displays a tooltip on a button that is hovered over.',
argTypes: {
text: {
description: 'Content of the tooltip shown on hover over button',
control: 'text',
},
alignment: {
description:
'Position of the tooltip (centered as default)',
options: tooltipPositions,
control: 'radio',
},
ariaLabel: {
description: 'Accessible label for screen readers',
},
},
} as Meta;

const Template: Story<TooltipProps> = (args) => (
<Tooltip {...args}>
<button>Your Button</button>
</Tooltip>
);

export const Default = Template.bind({});
Default.args = {
text: 'Default Tooltip Text',
alignment: 'center',
ariaLabel: 'Default Tooltip Text',
};

export const Right = Template.bind({});
Right.args = {
text: 'Right Aligned Tooltip Text',
alignment: 'right',
ariaLabel: 'Right Aligned Tooltip Text',
};

export const Left = Template.bind({});
Left.args = {
text: 'Left Aligned Tooltip Text',
alignment: 'left',
ariaLabel: 'Left Aligned Tooltip Text',
};
34 changes: 34 additions & 0 deletions jsapp/js/components/common/tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';

export type TooltipAlignment = 'right' | 'left' | 'center';

export interface TooltipProps {
/** Content of the tooltip */
text: string;
/** Accessible label for screen readers */
ariaLabel: string;
/** Position of the tooltip (centered as default) */
alignment?: TooltipAlignment;
}

/**
* Tooltip component that is necessary for managing dynamic content and
* accessibility features, such as readability. While we have the tooltip.scss
* component, that provides styling but not other functionaltiy such as
* allowing tooltips to work on disabled buttons.
*/
const Tooltip: React.FC<TooltipProps> = ({
text,
ariaLabel,
alignment,
children,
}) => (
<span
data-tip={text}
className={`${alignment || 'center'}-tooltip`}
aria-label={ariaLabel}
>
{children}
</span>
);
export default Tooltip;
56 changes: 28 additions & 28 deletions jsapp/js/projects/projectsTable/projectBulkActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,38 +33,38 @@ export default function ProjectBulkActions(props: ProjectBulkActionsProps) {
return (
<div className={actionsStyles.root}>
{/* Archive / Unarchive - Bulk action not supported yet */}
<span data-tip={t('Archive/Unarchive')} className='right-tooltip'>
<Button
isDisabled
type='bare'
color='storm'
size='s'
startIcon='archived'
/>
</span>
<Button
isDisabled
type='bare'
color='storm'
size='s'
startIcon='archived'
tooltip={t('Archive/Unarchive')}
tooltipPosition='right'
/>

{/* Share - Bulk action not supported yet */}
<span data-tip={t('Share projects')} className='right-tooltip'>
<Button
isDisabled
type='bare'
color='storm'
size='s'
startIcon='user-share'
/>
</span>
<Button
isDisabled
type='bare'
color='storm'
size='s'
startIcon='user-share'
tooltip={t('Share projects')}
tooltipPosition='right'
/>

{/* Delete */}
<span data-tip={tooltipForDelete} className='right-tooltip'>
<Button
isDisabled={!canBulkDelete}
type='bare'
color='storm'
size='s'
startIcon='trash'
onClick={() => setIsDeletePromptOpen(true)}
/>
</span>
<Button
isDisabled={!canBulkDelete}
type='bare'
color='storm'
size='s'
startIcon='trash'
onClick={() => setIsDeletePromptOpen(true)}
tooltip={tooltipForDelete}
tooltipPosition='right'
/>

{isDeletePromptOpen && (
<BulkDeletePrompt
Expand Down
Loading

0 comments on commit fac8cae

Please sign in to comment.