Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Expand Up @@ -60,7 +60,7 @@ export const ColorRampItem: React.FunctionComponent<ColorRampItemProps> = props
justifyContent: 'space-between',
padding: '1.5vh',
background: props.value,
width: '200px',
width: '150px',
boxSizing: 'border-box',
alignItems: 'center',
height: '100%',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react';
import {
Menu,
MenuTrigger,
MenuButton,
MenuPopover,
MenuList,
MenuItemRadio,
MenuGroupHeader,
MenuCheckedValueChangeEvent,
MenuCheckedValueChangeData,
} from '@fluentui/react-components';

import { FilterRegular } from '@fluentui/react-icons';

interface FilterButtonInterface {
checkedValues?: Record<string, string[]>;
onChange: (e: MenuCheckedValueChangeEvent, data: MenuCheckedValueChangeData) => void;
}

const tokensUseCase = {
usage: ['background', 'foreground', 'stencil', 'shadow', 'stroke', 'border'],
state: ['rest', 'hover', 'pressed', 'selected', 'focus', 'disabled'],
styles: ['inverted', 'static', 'transparent', 'alpha', 'link', 'accessible', 'subtle'],
};

export const TokensFilterButton = (props: FilterButtonInterface) => {
const { checkedValues, onChange } = props;

return (
<div>
<Menu>
<MenuTrigger disableButtonEnhancement>
<MenuButton icon={<FilterRegular />} appearance="transparent">
Filter
</MenuButton>
</MenuTrigger>
<MenuPopover>
{Object.entries(tokensUseCase).map(([key, useCases]) => (
<>
<MenuGroupHeader>{key.charAt(0).toUpperCase() + key.slice(1)}</MenuGroupHeader>
<MenuList checkedValues={checkedValues} onCheckedValueChange={onChange}>
{useCases.map((useCase, index) => (
<MenuItemRadio key={index} name="usecase" value={useCase}>
{useCase.charAt(0).toUpperCase() + useCase.slice(1)}
</MenuItemRadio>
))}
</MenuList>
</>
))}
</MenuPopover>
</Menu>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ import {
webLightTheme,
webDarkTheme,
Theme,
Input,
makeStyles,
InputProps,
MenuProps,
} from '@fluentui/react-components';

import { ColorRampItem } from './ColorRamp.stories';
import { TokensFilterButton } from './FilterButton.stories';

// FIXME: hardcoded theme
const theme = {
Expand All @@ -19,125 +24,79 @@ const theme = {
teamsHighContrast: teamsHighContrastTheme,
};

const colorPalette = {
Red: 'colorPaletteRed',
Green: 'colorPaletteGreen',
LightGreen: 'colorPaletteLightGreen',
DarkOrange: 'colorPaletteDarkOrange',
Marigold: 'colorPaletteMarigold',
Yellow: 'colorPaletteYellow',
Berry: 'colorPaletteBerry',

DarkRed: `colorPaletteDarkRed`,
Cranberry: 'colorPaletteCranberry',
Pumpkin: 'colorPalettePumpkin',
Peach: 'colorPalettePeach',
Gold: 'colorPaletteGold',
Brass: 'colorPaletteBrass',
Brown: 'colorPaletteBrown',
Forest: 'colorPaletteForest',
Seafoam: 'colorPaletteSeafoam',
DarkGreen: 'colorPaletteDarkGreen',
LightTeal: 'colorPaletteLightTeal',
Teal: 'colorPaletteTeal',
Steel: 'colorPaletteSteel',
Blue: 'colorPaletteBlue',
RoyalBlue: 'colorPaletteRoyalBlue',
Cornflower: 'colorPaletteCornflower',
Navy: 'colorPaletteNavy',
Lavender: 'colorPaletteLavender',
Purple: 'colorPalettePurple',
Grape: 'colorPaletteGrape',
Lilac: 'colorPaletteLilac',
Pink: 'colorPalettePink',
Magenta: 'colorPaletteMagenta',
Plum: 'colorPalettePlum',
Beige: 'colorPaletteBeige',
Mink: 'colorPaletteMink',
Platinum: 'colorPalettePlatinum',
Anchor: 'colorPaletteAnchor',
} as const;

type GlobalSharedColors = keyof typeof colorPalette;

const buttonStyle = ({ active }: { active: boolean }): React.CSSProperties => ({
position: 'relative',
verticalAlign: 'middle',
padding: 0,
margin: 0,
width: 40,
height: 40,
border: 'none',
boxShadow: active ? '0 0 0 1px white, 0 0 0 2px black' : 'none',
borderRadius: 0,
outline: 'none ',
zIndex: active ? 2 : 1,
const useStyles = makeStyles({
searchContainer: {
display: 'flex',
alignItems: 'center',
},
inputSearch: {
width: '100%',
},
});

const ColorButton: React.FunctionComponent<
{
color: 'neutral' | GlobalSharedColors;
active: boolean;
setPreviewColor: (color: 'neutral' | GlobalSharedColors | null) => void;
setColor: (color: 'neutral' | GlobalSharedColors) => void;
} & React.ButtonHTMLAttributes<HTMLButtonElement>
> = ({ style = {}, color, active, setPreviewColor, setColor, ...rest }) => (
<button
style={{
...style,
...buttonStyle({ active }),
}}
onClick={() => {
setColor(color);
setPreviewColor(null);
}}
onMouseEnter={() => setPreviewColor(color)}
onMouseLeave={() => setPreviewColor(null)}
{...rest}
/>
const tokens: Array<keyof Theme> = (Object.keys(theme.webLight) as Array<keyof Theme>).filter(
tokenName => tokenName.match(/^color(?!Palette).*/) || tokenName.startsWith(`colorPalette`),
);

const neutralTokens = (Object.keys(theme.webLight) as Array<keyof Theme>).filter(tokenName =>
tokenName.match(/^color(?!Palette).*/),
);
// It goes through all the existing tokens and returns the tokens matching the input value
const searchToken = (inputValue: string) => {
const tokensFoundBySearch = tokens.filter(
token =>
token.toLowerCase().includes(inputValue) ||
theme.webLight[token].toString().includes(inputValue) ||
theme.webDark[token].toString().includes(inputValue) ||
theme.teamsLight[token].toString().includes(inputValue) ||
theme.teamsDark[token].toString().includes(inputValue) ||
theme.teamsHighContrast[token].toString().includes(inputValue),
);
return tokensFoundBySearch;
};

export const Colors = () => {
const [color, setColor] = React.useState<'neutral' | GlobalSharedColors>('neutral');
const [previewColor, setPreviewColor] = React.useState<'neutral' | GlobalSharedColors | null>(null);
const activeColor = previewColor || color;
const [tokensSearchResult, setTokensSearchResult] = React.useState<Array<keyof Theme>>(tokens);

// Text typed in the input bar
const [inputValue, setInputValue] = React.useState('');

// Value checked from the filter menu button
const [checkedValue, setCheckedValue] = React.useState<Record<string, string[]>>();

const styles = useStyles();

const onInputChange: InputProps['onChange'] = (_, data) => {
setInputValue(data.value.trim().toLocaleLowerCase());
setCheckedValue(undefined);
};

React.useEffect(() => {
// Trigger the token's search
setTokensSearchResult(searchToken(inputValue));
}, [inputValue]);

const tokens: Array<keyof Theme> =
activeColor === 'neutral'
? neutralTokens
: (Object.keys(theme.webLight) as Array<keyof Theme>).filter(tokenName =>
tokenName.startsWith(`colorPalette${activeColor}`),
);
const applyFilter: MenuProps['onCheckedValueChange'] = (_, { name, checkedItems }) => {
// Filteringchecked items remove the selection and display the full list of tokens
if (checkedItems[0] === checkedValue?.usecase[0]) {
setCheckedValue(undefined);
setTokensSearchResult(tokens);
setInputValue('');
} else {
setCheckedValue(s => ({ ...s, [name]: checkedItems }));
setTokensSearchResult(searchToken(checkedItems[0]));
setInputValue(checkedItems[0]);
}
};

return (
<>
<div>
<h2 style={{ color: previewColor ? '#888' : '#000' }}>{activeColor}</h2>
<ColorButton
color="neutral"
active={color === 'neutral'}
setColor={setColor}
setPreviewColor={setPreviewColor}
style={{
background: theme.webLight.colorNeutralForeground1,
}}
<div className={styles.searchContainer}>
<TokensFilterButton checkedValues={checkedValue} onChange={applyFilter} />
<Input
placeholder={'Search for tokens by name or color'}
size={'large'}
onChange={onInputChange}
value={inputValue}
className={styles.inputSearch}
/>
{(Object.keys(colorPalette) as GlobalSharedColors[]).map(colorName => (
<ColorButton
key={colorName}
color={colorName}
active={color === colorName}
setColor={setColor}
setPreviewColor={setPreviewColor}
style={{
background: theme.webLight[`colorPalette${colorName}BorderActive` as keyof Theme],
}}
/>
))}
</div>

<div
Expand Down Expand Up @@ -166,7 +125,7 @@ export const Colors = () => {
<h3 key="hrHC" style={{ padding: '1em', margin: 0 }}>
Teams High Contrast
</h3>
{tokens.map(name => [
{tokensSearchResult?.map(name => [
<div
key={`${name}Token`}
style={{ padding: '0 1em', fontWeight: 'bold', display: 'flex', alignItems: 'center' }}
Expand Down