mirror of
https://github.com/mattermost/mattermost.git
synced 2026-05-28 04:35:04 -04:00
MM-68654 Add Button to the Component Library (#36412)
* Add Button to Component Library * Fix key warnings in Component Library * Add grid of Buttons to match Figma * Fix trailingIcon default
This commit is contained in:
parent
7f161bb24c
commit
c1ddd77481
5 changed files with 272 additions and 42 deletions
200
webapp/channels/src/components/component_library/button.cl.tsx
Normal file
200
webapp/channels/src/components/component_library/button.cl.tsx
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, {useMemo} from 'react';
|
||||
|
||||
import glyphMap from '@mattermost/compass-icons/components';
|
||||
import {Button} from '@mattermost/shared/components/button';
|
||||
|
||||
import {useBooleanProp, useDropdownProp, useStringProp} from './hooks';
|
||||
import {buildComponent} from './utils';
|
||||
|
||||
const propPossibilities = {};
|
||||
|
||||
const iconValues = [''].concat(Object.keys(glyphMap));
|
||||
|
||||
const emphasisValues = ['primary', 'secondary', 'tertiary', 'quaternary'];
|
||||
const sizeValues = ['xs', 'sm', 'md', 'lg'];
|
||||
const variantValues = ['', 'destructive'];
|
||||
|
||||
type Props = {
|
||||
backgroundClass: string;
|
||||
};
|
||||
|
||||
export default function ButtonComponentLibrary({backgroundClass}: Props) {
|
||||
const [label, labelSelector] = useStringProp('label', 'Label', false);
|
||||
|
||||
const [leadingIcon, leadingIconPossibilities, leadingIconSelector] = useDropdownProp('leadingIcon', 'mattermost', iconValues, false);
|
||||
const [trailingIcon, trailingIconPossibilities, trailingIconSelector] = useDropdownProp('trailingIcon', '', iconValues, false);
|
||||
|
||||
const [emphasis, emphasisPossibilities, emphasisSelector] = useDropdownProp('emphasis', 'primary', emphasisValues, true);
|
||||
const [size, sizePossibilities, sizeSelector] = useDropdownProp('size', 'md', sizeValues, true);
|
||||
const [variant, variantPossibilities, variantSelector] = useDropdownProp('variant', '', variantValues, true);
|
||||
|
||||
const [disabled, disabledSelector] = useBooleanProp('disabled', false);
|
||||
|
||||
const children = useMemo(() => (
|
||||
<>
|
||||
{leadingIcon?.leadingIcon ? <i className={classNames('icon', `icon-${leadingIcon.leadingIcon}`)}/> : null}
|
||||
{label.label}
|
||||
{trailingIcon?.trailingIcon ? <i className={classNames('icon', `icon-${trailingIcon.trailingIcon}`)}/> : null}
|
||||
</>
|
||||
), [label, leadingIcon, trailingIcon]);
|
||||
|
||||
const components = useMemo(
|
||||
() => buildComponent(
|
||||
Button,
|
||||
propPossibilities,
|
||||
[
|
||||
emphasisPossibilities,
|
||||
leadingIconPossibilities,
|
||||
sizePossibilities,
|
||||
trailingIconPossibilities,
|
||||
variantPossibilities,
|
||||
], [
|
||||
{children},
|
||||
emphasis,
|
||||
size,
|
||||
variant,
|
||||
disabled,
|
||||
],
|
||||
),
|
||||
[
|
||||
children,
|
||||
disabled,
|
||||
emphasis,
|
||||
emphasisPossibilities,
|
||||
leadingIconPossibilities,
|
||||
size,
|
||||
sizePossibilities,
|
||||
trailingIconPossibilities,
|
||||
variant,
|
||||
variantPossibilities,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{labelSelector}
|
||||
{leadingIconSelector}
|
||||
{trailingIconSelector}
|
||||
<hr/>
|
||||
{emphasisSelector}
|
||||
{sizeSelector}
|
||||
{variantSelector}
|
||||
<hr/>
|
||||
{disabledSelector}
|
||||
<div className={classNames('clWrapper', backgroundClass)}>{components}</div>
|
||||
<ButtonGrid/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ButtonGrid() {
|
||||
const sizes = ['md', 'xs', 'sm', 'lg'] as const;
|
||||
const variants = ['', 'destructive', 'inverted'] as const;
|
||||
const states = ['default', 'hover', 'active', 'focus', 'disabled'] as const;
|
||||
|
||||
const emphasisLevels = ['primary', 'secondary', 'tertiary', 'quaternary'] as const;
|
||||
|
||||
const rows = [];
|
||||
for (const size of sizes) {
|
||||
for (const variant of variants) {
|
||||
for (const state of states) {
|
||||
const row = [];
|
||||
|
||||
if (variant === '' && state === 'default') {
|
||||
const sizeLabels = {md: 'medium', xs: 'x-small', sm: 'small', lg: 'large'} as const;
|
||||
row.push(
|
||||
<th
|
||||
key='size'
|
||||
scope='row'
|
||||
>
|
||||
{sizeLabels[size]}
|
||||
</th>,
|
||||
);
|
||||
} else {
|
||||
row.push(
|
||||
<th key='size'/>,
|
||||
);
|
||||
}
|
||||
|
||||
if (state === 'default') {
|
||||
row.push(
|
||||
<th
|
||||
key='variant'
|
||||
scope='row'
|
||||
>
|
||||
{variant}
|
||||
</th>,
|
||||
);
|
||||
} else {
|
||||
row.push(
|
||||
<th key='variant'/>,
|
||||
);
|
||||
}
|
||||
|
||||
row.push(
|
||||
<th
|
||||
key='state'
|
||||
scope='row'
|
||||
>
|
||||
{state}
|
||||
</th>,
|
||||
);
|
||||
|
||||
let stateClassName = '';
|
||||
if (state === 'hover' || state === 'active' || state === 'focus') {
|
||||
stateClassName = `btn-force-${state}`;
|
||||
}
|
||||
|
||||
for (const emphasis of emphasisLevels) {
|
||||
row.push(
|
||||
<td
|
||||
key={emphasis}
|
||||
className={classNames({inverted: variant === 'inverted'})}
|
||||
>
|
||||
<Button
|
||||
emphasis={emphasis}
|
||||
size={size}
|
||||
variant={variant}
|
||||
className={stateClassName}
|
||||
disabled={state === 'disabled'}
|
||||
>
|
||||
{'Button'}
|
||||
</Button>
|
||||
</td>,
|
||||
);
|
||||
}
|
||||
|
||||
rows.push(
|
||||
<tr key={`${size}-${variant}-${state}`} >
|
||||
{row}
|
||||
</tr>,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<table className='clWrapper clTable'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={3}/>
|
||||
{emphasisLevels.map((emphasis) => (
|
||||
<th
|
||||
key={emphasis}
|
||||
scope='col'
|
||||
>
|
||||
{emphasis}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
.clWrapper {
|
||||
width: auto;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.clCenterBackground {
|
||||
|
|
@ -15,3 +16,26 @@
|
|||
.clSidebarBackground {
|
||||
background-color: var(--sidebar-bg);
|
||||
}
|
||||
|
||||
.clTable {
|
||||
width: 100%;
|
||||
max-width: 970px;
|
||||
background-color: var(--center-channel-bg);
|
||||
|
||||
th {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
tr {
|
||||
background-color: var(--center-channel-bg);
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 12px 10px;
|
||||
text-align: center;
|
||||
|
||||
&.inverted {
|
||||
background-color: var(--sidebar-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ import {Preferences} from 'mattermost-redux/constants';
|
|||
|
||||
import {applyTheme} from 'utils/utils';
|
||||
|
||||
import ButtonComponentLibrary from './button.cl';
|
||||
import SectionNoticeComponentLibrary from './section_notice.cl';
|
||||
|
||||
import './component_library.scss';
|
||||
|
||||
const componentMap = {
|
||||
Button: ButtonComponentLibrary,
|
||||
'Section Notice': SectionNoticeComponentLibrary,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -29,13 +29,17 @@ function buildPropString(inputProps: {[x: string]: any}) {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const result = [(<>{'PROPS: '}</>)];
|
||||
const result = [(<React.Fragment key='propsTitle'>{'PROPS: '}</React.Fragment>)];
|
||||
propKeys.forEach((v) => {
|
||||
result.push((<><b>{v}</b>{`: ${inputProps[v]}, `}</>));
|
||||
result.push((<React.Fragment key={v}><b>{v}</b>{`: ${inputProps[v]}, `}</React.Fragment >));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function buildPropValueKey(inputProps: {[x: string]: any}) {
|
||||
return Object.entries(inputProps).map(([key, value]) => `${key}:${value}`).join('-');
|
||||
}
|
||||
|
||||
export function buildComponent(
|
||||
Component: React.ComponentType<any>,
|
||||
propPossibilities: {[x: string]: any[]},
|
||||
|
|
@ -66,13 +70,13 @@ export function buildComponent(
|
|||
propsVariations.forEach((v) => {
|
||||
const propString = buildPropString(v);
|
||||
res.push(
|
||||
<>
|
||||
<React.Fragment key={buildPropValueKey(v)}>
|
||||
{Boolean(propString) && <p>{propString}</p>}
|
||||
<Component
|
||||
{...builtSetProps}
|
||||
{...v}
|
||||
/>
|
||||
</>,
|
||||
</React.Fragment>,
|
||||
);
|
||||
});
|
||||
return res;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
border: none;
|
||||
background: transparent;
|
||||
|
||||
&:focus {
|
||||
&:focus, &.btn-force-focus {
|
||||
outline: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
&:active, &.btn-force-active {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -62,12 +62,12 @@ button {
|
|||
background-color: transparent;
|
||||
color: rgba(var(--center-channel-color-rgb), var(--icon-opacity));
|
||||
|
||||
&:hover {
|
||||
&:hover, &.btn-force-hover {
|
||||
background-color: rgba(var(--center-channel-color-rgb), 0.08);
|
||||
color: rgba(var(--center-channel-color-rgb), var(--icon-opacity-hover));
|
||||
}
|
||||
|
||||
&:active {
|
||||
&:active, &.btn-force-active {
|
||||
background-color: rgba(var(--button-bg-rgb), 0.08);
|
||||
color: rgba(var(--button-bg-rgb), 1);
|
||||
}
|
||||
|
|
@ -135,7 +135,7 @@ button {
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
&:active, &.btn-force-active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
|
@ -203,9 +203,9 @@ button {
|
|||
background: transparent;
|
||||
color: rgba(var(--button-bg-rgb), 1);
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
&:hover, &.btn-force-hover,
|
||||
&:focus, &.btn-force-focus,
|
||||
&:active, &.btn-force-active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
|
@ -217,16 +217,16 @@ button {
|
|||
color: rgb(var(--button-color-rgb)) !important;
|
||||
|
||||
// These hover and active values are for things outside the app__body, the correct theme styles for the primary button are applied in utils.jsx
|
||||
&:hover {
|
||||
&:hover, &.btn-force-hover {
|
||||
background-color: #1a51c8;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
&:active, &.btn-force-active,
|
||||
&:focus, &.btn-force-focus {
|
||||
background-color: #184ab6;
|
||||
}
|
||||
|
||||
&:disabled,
|
||||
&:disabled, &.btn-force-disabled,
|
||||
&:disabled:hover,
|
||||
&:disabled:active {
|
||||
background: rgba(var(--center-channel-color-rgb), 0.08);
|
||||
|
|
@ -238,11 +238,11 @@ button {
|
|||
background-color: var(--online-indicator);
|
||||
color: var(--button-color-rgb);
|
||||
|
||||
&:hover {
|
||||
&:hover, &.btn-force-hover {
|
||||
background-color: var(--online-indicator);
|
||||
}
|
||||
|
||||
&:active {
|
||||
&:active, &.btn-force-active {
|
||||
background-color: var(--online-indicator);
|
||||
}
|
||||
}
|
||||
|
|
@ -258,21 +258,21 @@ button {
|
|||
background: transparent;
|
||||
color: var(--error-text);
|
||||
|
||||
&:hover {
|
||||
&:hover, &.btn-force-hover {
|
||||
border-color: currentColor;
|
||||
background-color: rgba(var(--error-text-color-rgb), 0.08);
|
||||
color: var(--error-text);
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
&:active, &.btn-force-active,
|
||||
&:focus, &.btn-force-focus {
|
||||
border-color: currentColor;
|
||||
background-color: rgba(var(--error-text-color-rgb), 0.16);
|
||||
color: var(--error-text);
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled,
|
||||
&:disabled, &.btn-force-disabled,
|
||||
&:disabled:hover,
|
||||
&:disabled:active {
|
||||
border-color: rgba(var(--center-channel-color-rgb), 0.32);
|
||||
|
|
@ -280,11 +280,11 @@ button {
|
|||
color: rgba(var(--center-channel-color-rgb), 0.32) !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover, &.btn-force-hover {
|
||||
background-color: rgb(var(--button-bg-rgb), 0.08);
|
||||
}
|
||||
|
||||
&:active {
|
||||
&:active, &.btn-force-active {
|
||||
background-color: rgb(var(--button-bg-rgb), 0.16);
|
||||
}
|
||||
}
|
||||
|
|
@ -293,16 +293,16 @@ button {
|
|||
background: rgba(var(--button-bg-rgb), 0.08);
|
||||
color: rgb(var(--button-bg-rgb));
|
||||
|
||||
&:hover {
|
||||
&:hover, &.btn-force-hover {
|
||||
background-color: rgb(var(--button-bg-rgb), 0.12);
|
||||
}
|
||||
|
||||
&:active {
|
||||
&:active, &.btn-force-active {
|
||||
background-color: rgb(var(--button-bg-rgb), 0.16);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:disabled,
|
||||
&:disabled, &.btn-force-disabled,
|
||||
&:disabled:hover,
|
||||
&:disabled:active {
|
||||
background: rgba(var(--center-channel-color-rgb), 0.08);
|
||||
|
|
@ -314,13 +314,13 @@ button {
|
|||
background-color: rgba(var(--error-text-color-rgb), 0.08);
|
||||
color: var(--error-text);
|
||||
|
||||
&:hover {
|
||||
&:hover, &.btn-force-hover {
|
||||
background-color: rgba(var(--error-text-color-rgb), 0.12);
|
||||
color: var(--error-text);
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
&:active, &.btn-force-active,
|
||||
&:focus, &.btn-force-focus {
|
||||
background-color: rgba(var(--error-text-color-rgb), 0.16);
|
||||
color: var(--error-text);
|
||||
}
|
||||
|
|
@ -330,11 +330,11 @@ button {
|
|||
background-color: rgba(var(--sidebar-text-rgb), 0.12);
|
||||
color: rgba(var(--sidebar-text-rgb), 1);
|
||||
|
||||
&:hover {
|
||||
&:hover, &.btn-force-hover {
|
||||
background-color: rgb(var(--sidebar-text-rgb), 0.16);
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:active, &.btn-force-active,
|
||||
&[aria-expanded="true"][aria-haspopup="true"] {
|
||||
background-color: rgb(var(--sidebar-text-rgb), 0.24);
|
||||
outline: none;
|
||||
|
|
@ -345,22 +345,22 @@ button {
|
|||
background: transparent;
|
||||
color: rgb(var(--button-bg-rgb));
|
||||
|
||||
&:hover {
|
||||
&:hover, &.btn-force-hover {
|
||||
background: rgba(var(--button-bg-rgb), 0.08);
|
||||
}
|
||||
|
||||
&:active {
|
||||
&:active, &.btn-force-active {
|
||||
background-color: rgb(var(--button-bg-rgb), 0.12);
|
||||
}
|
||||
|
||||
&.btn-inverted {
|
||||
color: rgb(var(--button-color-rgb));
|
||||
|
||||
&:hover {
|
||||
&:hover, &.btn-force-hover {
|
||||
background: rgba(var(--button-color-rgb), 0.12);
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:active, &.btn-force-active,
|
||||
&[aria-expanded="true"][aria-haspopup="true"] {
|
||||
background-color: rgb(var(--button-color-rgb), 0.16);
|
||||
}
|
||||
|
|
@ -374,16 +374,16 @@ button {
|
|||
.app__body & {
|
||||
color: variables.$white;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
&:hover, &.btn-force-hover,
|
||||
&:focus, &.btn-force-focus,
|
||||
&:active, &.btn-force-active {
|
||||
color: variables.$white;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
&:hover, &.btn-force-hover,
|
||||
&:focus, &.btn-force-focus,
|
||||
&:active, &.btn-force-active {
|
||||
color: variables.$white;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue