diff --git a/webapp/channels/src/components/component_library/button.cl.tsx b/webapp/channels/src/components/component_library/button.cl.tsx new file mode 100644 index 00000000000..faedcbdc05a --- /dev/null +++ b/webapp/channels/src/components/component_library/button.cl.tsx @@ -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 ? : null} + {label.label} + {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} +
+ {emphasisSelector} + {sizeSelector} + {variantSelector} +
+ {disabledSelector} +
{components}
+ + + ); +} + +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( + + {sizeLabels[size]} + , + ); + } else { + row.push( + , + ); + } + + if (state === 'default') { + row.push( + + {variant} + , + ); + } else { + row.push( + , + ); + } + + row.push( + + {state} + , + ); + + let stateClassName = ''; + if (state === 'hover' || state === 'active' || state === 'focus') { + stateClassName = `btn-force-${state}`; + } + + for (const emphasis of emphasisLevels) { + row.push( + + + , + ); + } + + rows.push( + + {row} + , + ); + } + } + } + + return ( + + + + + ))} + + + + {rows} + +
+ {emphasisLevels.map((emphasis) => ( + + {emphasis} +
+ ); +} diff --git a/webapp/channels/src/components/component_library/component_library.scss b/webapp/channels/src/components/component_library/component_library.scss index d15f20b3bb0..5271ee1a40b 100644 --- a/webapp/channels/src/components/component_library/component_library.scss +++ b/webapp/channels/src/components/component_library/component_library.scss @@ -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); + } + } +} diff --git a/webapp/channels/src/components/component_library/index.tsx b/webapp/channels/src/components/component_library/index.tsx index c5376b22f30..d523a64f4d4 100644 --- a/webapp/channels/src/components/component_library/index.tsx +++ b/webapp/channels/src/components/component_library/index.tsx @@ -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, }; diff --git a/webapp/channels/src/components/component_library/utils.tsx b/webapp/channels/src/components/component_library/utils.tsx index 73783e34e8c..85aed41315f 100644 --- a/webapp/channels/src/components/component_library/utils.tsx +++ b/webapp/channels/src/components/component_library/utils.tsx @@ -29,13 +29,17 @@ function buildPropString(inputProps: {[x: string]: any}) { return undefined; } - const result = [(<>{'PROPS: '})]; + const result = [({'PROPS: '})]; propKeys.forEach((v) => { - result.push((<>{v}{`: ${inputProps[v]}, `})); + result.push(({v}{`: ${inputProps[v]}, `})); }); return result; } +function buildPropValueKey(inputProps: {[x: string]: any}) { + return Object.entries(inputProps).map(([key, value]) => `${key}:${value}`).join('-'); +} + export function buildComponent( Component: React.ComponentType, propPossibilities: {[x: string]: any[]}, @@ -66,13 +70,13 @@ export function buildComponent( propsVariations.forEach((v) => { const propString = buildPropString(v); res.push( - <> + {Boolean(propString) &&

{propString}

} - , +
, ); }); return res; diff --git a/webapp/channels/src/sass/components/_buttons.scss b/webapp/channels/src/sass/components/_buttons.scss index 29bda6a9781..1ef91fea675 100644 --- a/webapp/channels/src/sass/components/_buttons.scss +++ b/webapp/channels/src/sass/components/_buttons.scss @@ -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; } }