Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fixed-width-button-content): add component #423

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { SIZES as BUTTON_PILL_SIZES } from '../ButtonPill/ButtonPill.constants';
import { SIZES as ICON_SIZES } from '../Icon/Icon.constants';

const CLASS_PREFIX = 'md-fixed-width-button-pill-content';

const DEFAULTS = {
ICON: false,
ICON_SCALE: ICON_SIZES[32],
BUTTON_PILL_SIZE: BUTTON_PILL_SIZES[40],
};

const STYLE = {
wrapper: `${CLASS_PREFIX}-wrapper`,
buttonContent: `${CLASS_PREFIX}-button-content`,
hidden: `${CLASS_PREFIX}-hidden`,
};

export { CLASS_PREFIX, DEFAULTS, STYLE };
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { commonStyles } from '../../storybook/helper.stories.argtypes';
import { BUTTON_PILL_CONSTANTS } from '../ButtonPill';
import { DEFAULTS } from './FixedWidthButtonPillContent.constants';
import { SIZES as ICON_SIZES } from '../Icon/Icon.constants';

const fixedWidthButtonPillContentArgTypes = {
children: {
description: 'Provides the child nodes for this element.',
control: { type: 'text' },
table: {
type: {
summary: 'ReactNode',
},
defaultValue: {
summary: 'undefined',
},
},
},
buttonPillSize: {
description: 'Modifies the size of the content to fit in any given `<ButtonPill />` component.',
options: [...Object.values(BUTTON_PILL_CONSTANTS.SIZES)],
control: { type: 'select' },
table: {
type: {
summary: 'number',
},
defaultValue: {
summary: BUTTON_PILL_CONSTANTS.DEFAULTS.SIZE,
},
},
},
icon: {
description: 'Whether the content of this component will contain an `<Icon />` component.',
options: [true, false],
control: { type: 'boolean' },
table: {
type: {
summary: 'boolean',
},
defaultValue: {
summary: DEFAULTS.ICON,
},
},
},
iconScale: {
description: 'The size of the `<Icon />` component if there is one.',
options: [...Object.values(ICON_SIZES)],
control: { type: 'select' },
table: {
type: {
summary: 'number',
},
defaultValue: {
summary: DEFAULTS.ICON_SCALE,
},
},
},
stringContentVariations: {
description:
'An array of strings that will be used in the `<ButtonPill />` component. The longest string will dictate the fixed width of the `<ButtonPill />`',
control: { type: 'text' },
table: {
type: {
summary: 'string[]',
},
defaultValue: {
summary: ['Long, longer, longest'],
},
},
},
};

export { fixedWidthButtonPillContentArgTypes };

export default {
...commonStyles,
...fixedWidthButtonPillContentArgTypes,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
The `<FixedWidthButtonPillContent />` component.

This component can be used if you have a `<ButtonPill />`, or a `<ButtonPillToggle />`, component
where the string content changes onPress or as a consequence of a state change. A button which
changes width in these circumstances may change the layout of a page and result in a suboptimal UX.

Tips:

We recommend adding `overflow: hidden;` to the button you use this with. Otherwise the user may be
able to trigger the onPress callback on the button from above the button.

Internally this component uses the Momentum React V2 `<Icon />` and `<Text />` components to mimic
the button content to achieve the fixed width. We recommend using the `<Text />` component for the
string child component to ensure truncation works as expected if you set a maximum width to the
button.
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import React, { useCallback, useState } from 'react';
import { Template } from '../../storybook/helper.stories.templates';
import { DocumentationPage } from '../../storybook/helper.stories.docs';
import StyleDocs from '../../storybook/docs.stories.style.mdx';

import FixedWidthButtonPillContent, { FixedWidthButtonPillContentProps } from './';
import argTypes from './FixedWidthButtonPillContent.stories.args';
import Documentation from './FixedWidthButtonPillContent.stories.docs.mdx';
import Icon from '../Icon';
import Text from '../Text';
import ButtonPill from '../ButtonPill';
import ButtonGroup from '../ButtonGroup';
import ButtonCircle from '../ButtonCircle';
import { getTextTypeFromButtonPillSize } from './FixedWidthButtonPillContent.utils';

export default {
title: 'Momentum UI/FixedWidthButtonPillContent',
component: FixedWidthButtonPillContent,
parameters: {
expanded: true,
docs: {
page: DocumentationPage(Documentation, StyleDocs),
},
},
};

const Common = Template<FixedWidthButtonPillContentProps>(() => {
const englishStringContentVariations = ['Stop video', 'Start video'];
const germanStringContentVariations = ['Stummschalten', 'Stummschaltung aufheben'];
const englishChildren = [
<>
<Icon name="camera-on-colored" scale={20} />
<Text>{englishStringContentVariations[0]}</Text>
</>,
<>
<Icon
name="camera-muted"
scale={20}
style={{ color: 'var(--mds-color-theme-text-error-normal)' }}
/>
<Text>{englishStringContentVariations[1]}</Text>
</>,
];
const germanChildren = [
<>
<Icon name="audio-microphone-on-colored" scale={20} />
<Text>{germanStringContentVariations[0]}</Text>
</>,
<>
<Icon
name="microphone-muted"
scale={20}
style={{ color: 'var(--mds-color-theme-text-error-normal)' }}
/>
<Text>{germanStringContentVariations[1]}</Text>
</>,
];
const buttonPillStyle = { overflow: 'hidden', maxWidth: '12.375rem' };
const [child, setChild] = useState(0);
const handlePress = useCallback(() => {
setChild((prev) => (prev + 1) % englishChildren.length);
}, []);

return (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 15rem)',
gridTemplateRows: 'repeat(3, auto)',
gap: '0.5rem',
}}
>
<ButtonPill
style={{ width: '6rem', justifyContent: 'center' }}
size={30}
onPress={handlePress}
>
Press me
</ButtonPill>
<Text>English</Text>
<Text>German</Text>
<Text>With</Text>
<ButtonGroup round compressed>
<ButtonPill onPress={() => {}} ghost outline style={buttonPillStyle}>
<FixedWidthButtonPillContent
stringContentVariations={englishStringContentVariations}
icon
iconScale={20}
>
{englishChildren[child]}
</FixedWidthButtonPillContent>
</ButtonPill>
<ButtonCircle outline ghost size={40}>
<Icon data-test="arrow-icon" name="arrow-down" scale={12} weight="filled" />
</ButtonCircle>
</ButtonGroup>
<ButtonGroup round compressed>
<ButtonPill onPress={() => {}} ghost outline style={buttonPillStyle}>
<FixedWidthButtonPillContent
stringContentVariations={germanStringContentVariations}
icon
iconScale={12}
>
{germanChildren[child]}
</FixedWidthButtonPillContent>
</ButtonPill>
<ButtonCircle outline ghost size={40}>
<Icon data-test="arrow-icon" name="arrow-down" scale={12} weight="filled" />
</ButtonCircle>
</ButtonGroup>
<Text>Without</Text>
<ButtonGroup round compressed>
<ButtonPill onPress={() => {}} ghost outline style={buttonPillStyle}>
{englishChildren[child]}
</ButtonPill>
<ButtonCircle outline ghost size={40}>
<Icon data-test="arrow-icon" name="arrow-down" scale={12} weight="filled" />
</ButtonCircle>
</ButtonGroup>
<ButtonGroup round compressed>
<ButtonPill onPress={() => {}} ghost outline style={buttonPillStyle}>
{germanChildren[child]}
</ButtonPill>
<ButtonCircle outline ghost size={40}>
<Icon data-test="arrow-icon" name="arrow-down" scale={12} weight="filled" />
</ButtonCircle>
</ButtonGroup>
</div>
);
}).bind({});

Common.argTypes = { ...argTypes };
delete Common.argTypes.children;
delete Common.argTypes.stringContentVariations;
delete Common.argTypes.icon;
delete Common.argTypes.iconScale;
delete Common.argTypes.buttonPillSize;

const Example = Template<FixedWidthButtonPillContentProps>((args) => {
const { icon, iconScale, buttonPillSize } = args as FixedWidthButtonPillContentProps;
const stringContentVariations = ['Long', 'Longer', 'Longest'];
const [currentChildIndex, setCurrentChildIndex] = useState(0);
const handlePress = useCallback(() => {
setCurrentChildIndex((prev) => (prev + 1) % stringContentVariations.length);
}, []);
const buttonPillStyle = { overflow: 'hidden', maxWidth: '12.375rem' };
const textType = getTextTypeFromButtonPillSize(buttonPillSize);

return (
<>
<ButtonPill
style={{ width: '6rem', justifyContent: 'center' }}
size={30}
onPress={handlePress}
>
Press me
</ButtonPill>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(2, 15rem)',
gridTemplateRows: 'repeat(2, auto)',
gap: '0.5rem',
marginTop: '0.5rem',
}}
>
<Text>With</Text>
<div>
<ButtonPill
onPress={handlePress}
ghost
outline
size={buttonPillSize}
style={buttonPillStyle}
>
<FixedWidthButtonPillContent
stringContentVariations={stringContentVariations}
icon={icon}
iconScale={iconScale}
buttonPillSize={buttonPillSize}
>
{icon && <Icon name="placeholder" scale={iconScale} />}
<Text type={textType}>{stringContentVariations[currentChildIndex]}</Text>
</FixedWidthButtonPillContent>
</ButtonPill>
</div>
<Text>Without</Text>
<div>
<ButtonPill
onPress={handlePress}
ghost
outline
size={buttonPillSize}
style={buttonPillStyle}
>
{icon && <Icon name="placeholder" scale={iconScale} />}
<Text type={textType}>{stringContentVariations[currentChildIndex]}</Text>
</ButtonPill>
</div>
</div>
</>
);
}).bind({});

Example.argTypes = { ...argTypes };
delete Example.argTypes.children;
delete Example.argTypes.stringContentVariations;

Example.args = {
icon: false,
iconScale: 20,
buttonPillSize: 40,
};

export { Example, Common };
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
$button-pill-border-width: 0.0625rem;

.md-fixed-width-button-pill-content-wrapper {
display: flex;
max-width: 100%;
flex-direction: column;
align-self: flex-end;

.md-fixed-width-button-pill-content-button-content {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
gap: 0.375rem;

&[data-size='40'] {
font-size: 1rem;
height: 2.5rem - $button-pill-border-width;
line-height: 2.5rem;
}

&[data-size='32'] {
font-size: 1rem;
height: 2rem - $button-pill-border-width;
line-height: 2rem;
}

&[data-size='28'] {
font-size: 0.875rem;
height: 1.75rem - $button-pill-border-width;
line-height: 1.75rem;
}

&[data-size='24'] {
font-size: 0.75rem;
height: 1.5rem - $button-pill-border-width;
line-height: 1.5rem;
}

&[data-size='20'] {
font-size: 0.75rem;
height: 1.25rem - $button-pill-border-width;
line-height: 1.125rem;
}
}

&[data-hidden='true'] {
visibility: hidden;
}
}
Loading
Loading