Skip to content

Commit

Permalink
feat(suite): Add Flex and HotkeyBadge components (#12135)
Browse files Browse the repository at this point in the history
* feat(suite): Add Flex and HotkeyBadge components

* fix(suite): Improve HotkeyBadge
  • Loading branch information
jvaclavik authored Apr 25, 2024
1 parent 7a6babb commit 3804ff7
Show file tree
Hide file tree
Showing 8 changed files with 368 additions and 3 deletions.
83 changes: 83 additions & 0 deletions packages/components/src/components/Flex/Flex.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Meta, StoryObj } from '@storybook/react';
import {
Flex as FlexComponent,
FlexProps,
flexAlignItems,
flexDirection,
flexJustifyContent,
} from './Flex';
import { spacings } from '@trezor/theme';
import styled from 'styled-components';

const Container = styled.div`
width: 100%;
`;

const Box = styled.div<{ $color: string }>`
background: ${({ $color }) => $color};
padding: 10px 20px;
border-radius: 8px;
font-size: 20px;
font-weight: 900;
`;

const meta: Meta = {
title: 'Misc/Flex',
component: FlexComponent,
} as Meta;
export default meta;

export const Flex: StoryObj<FlexProps> = {
render: args => (
<Container>
<FlexComponent {...args} />
</Container>
),
args: {
children: [
<Box key="box-a" $color="salmon">
A
</Box>,
<Box key="box-b" $color="green">
B
</Box>,
<Box key="box-c" $color="royalblue">
C
</Box>,
],
direction: 'row',
gap: 8,
margin: { top: undefined, right: undefined, bottom: undefined, left: undefined },
},
argTypes: {
direction: {
options: flexDirection,
control: {
type: 'radio',
},
},
justifyContent: {
options: flexJustifyContent,
control: {
type: 'select',
},
},
alignItems: {
options: flexAlignItems,
control: {
type: 'select',
},
},
gap: {
options: Object.values(spacings),
control: {
type: 'select',
},
},
margin: {
table: {
category: 'Frame props',
},
},
},
};
91 changes: 91 additions & 0 deletions packages/components/src/components/Flex/Flex.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { SpacingValues } from '@trezor/theme';
import styled from 'styled-components';
import { FrameProps, TransientFrameProps, withFrameProps } from '../common/frameProps';
import { makePropsTransient } from '../../utils/transientProps';

export const flexDirection = ['column-reverse', 'column', 'row-reverse', 'row'] as const;

export const flexJustifyContent = [
'center',
'end',
'flex-end',
'flex-start',
'left',
'right',
'space-around',
'space-between',
'space-evenly',
'start',
'stretch',
] as const;

export const flexAlignItems = [
'baseline',
'center',
'end',
'first baseline',
'flex-end',
'flex-start',
'last baseline',
'self-end',
'self-start',
'start',
'stretch',
] as const;

export type FlexDirection = (typeof flexDirection)[number];
export type FlexJustifyContent = (typeof flexJustifyContent)[number];
export type FlexAlignItems = (typeof flexAlignItems)[number];

const Container = styled.div<
TransientFrameProps & {
$gap: SpacingValues;
$justifyContent: FlexJustifyContent;
$alignItems: FlexAlignItems;
$direction: FlexDirection;
}
>`
display: flex;
flex-direction: ${({ $direction }) => $direction};
gap: ${({ $gap }) => $gap}px;
justify-content: ${({ $justifyContent }) => $justifyContent};
align-items: ${({ $alignItems }) => $alignItems};
${withFrameProps}
`;

export type FlexProps = FrameProps & {
gap?: SpacingValues;
justifyContent?: FlexJustifyContent;
alignItems?: FlexAlignItems;
children: React.ReactNode;
direction?: FlexDirection;
};

export const Flex = ({
gap = 0,
justifyContent = 'flex-start',
alignItems = 'center',
children,
direction = 'row',
margin,
}: FlexProps) => {
const frameProps = {
margin,
};

return (
<Container
$gap={gap}
$justifyContent={justifyContent}
$alignItems={alignItems}
$direction={direction}
{...makePropsTransient(frameProps)}
>
{children}
</Container>
);
};

export const Rows = (props: FlexProps) => <Flex {...props} direction="column" />;
export const Columns = (props: FlexProps) => <Flex {...props} direction="row" />;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Meta, StoryObj } from '@storybook/react';
import { HotkeyBadge as HotkeyBadgeComponent, HotkeyBadgeProps } from './HotkeyBadge';

const meta: Meta = {
title: 'Form/HotkeyBadge',
component: HotkeyBadgeComponent,
} as Meta;
export default meta;

const Component = ({ ...args }: HotkeyBadgeProps) => {
return <HotkeyBadgeComponent {...args} />;
};

export const HotkeyBadge: StoryObj<HotkeyBadgeProps> = {
render: Component,
args: {
hotkey: ['CTRL', 'KEY_P'],
isActive: true,
},
};
64 changes: 64 additions & 0 deletions packages/components/src/components/HotkeyBadge/HotkeyBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
Elevation,
borders,
mapElevationToBackground,
spacingsPx,
typography,
} from '@trezor/theme';
import styled from 'styled-components';
import { ElevationDown, useElevation } from '../ElevationContext/ElevationContext';
import { Keys, keyboardKeys } from './keyboardKeys';

export const Container = styled.div<{ $elevation: Elevation; $isActive: boolean }>`
display: flex;
gap: ${spacingsPx.xxs};
align-items: center;
justify-content: center;
background: ${mapElevationToBackground};
border-radius: ${borders.radii.xs};
color: ${({ theme }) => theme.textSubdued};
opacity: ${({ $isActive }) => ($isActive ? 1 : 0.5)};
user-select: none;
padding: 0 ${spacingsPx.xxs};
${typography.label}
position: relative;
`;

export interface HotkeyBadgeProps {
isActive?: boolean;
hotkey: Array<Keys>;
}

const Plus = () => <span>+</span>;

const Component = ({ isActive = true, hotkey }: HotkeyBadgeProps) => {
const { elevation } = useElevation();

const isMac = navigator.userAgent.includes('Macintosh');

return (
<Container $elevation={elevation} $isActive={isActive}>
{hotkey.map((key, index) => {
const keyObject = keyboardKeys[key];
const macValue = 'valueMac' in keyObject ? keyObject.valueMac : keyObject.value;
const isNotLast = index < hotkey.length - 1;
const value = isMac ? macValue : keyObject.value;

return (
<>
<span key={`hotkey-${key}-${index}`}>{value.toUpperCase()}</span>
{isNotLast && <Plus />}
</>
);
})}
</Container>
);
};

export const HotkeyBadge = (props: HotkeyBadgeProps) => {
return (
<ElevationDown>
<Component {...props} />
</ElevationDown>
);
};
105 changes: 105 additions & 0 deletions packages/components/src/components/HotkeyBadge/keyboardKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
export const keyboardKeys = {
BACKSPACE: { code: 8, value: 'Backspace' },
TAB: { code: 9, value: 'Tab' },
ENTER: { code: 13, value: 'Enter' },
SHIFT: { code: 16, value: 'Shift' },
CTRL: { code: 17, value: 'Ctrl', valueMac: '⌘' },
CMD: { code: 17, value: '⌘' },
ALT: { code: 18, value: 'Alt', valueMac: 'Option' },
PAUSE: { code: 19, value: 'Pause' },
CAPS_LOCK: { code: 20, value: 'Caps Lock' },
ESCAPE: { code: 27, value: 'Escape' },
SPACE: { code: 32, value: 'Space' },
PAGE_UP: { code: 33, value: 'Page Up' },
PAGE_DOWN: { code: 34, value: 'Page Down' },
END: { code: 35, value: 'End' },
HOME: { code: 36, value: 'Home' },
LEFT_ARROW: { code: 37, value: '←' },
UP_ARROW: { code: 38, value: '↑' },
RIGHT_ARROW: { code: 39, value: '→' },
DOWN_ARROW: { code: 40, value: '↓' },
INSERT: { code: 45, value: 'Insert' },
DELETE: { code: 46, value: 'Delete' },
KEY_0: { code: 48, value: '0' },
KEY_1: { code: 49, value: '1' },
KEY_2: { code: 50, value: '2' },
KEY_3: { code: 51, value: '3' },
KEY_4: { code: 52, value: '4' },
KEY_5: { code: 53, value: '5' },
KEY_6: { code: 54, value: '6' },
KEY_7: { code: 55, value: '7' },
KEY_8: { code: 56, value: '8' },
KEY_9: { code: 57, value: '9' },
KEY_A: { code: 65, value: 'A' },
KEY_B: { code: 66, value: 'B' },
KEY_C: { code: 67, value: 'C' },
KEY_D: { code: 68, value: 'D' },
KEY_E: { code: 69, value: 'E' },
KEY_F: { code: 70, value: 'F' },
KEY_G: { code: 71, value: 'G' },
KEY_H: { code: 72, value: 'H' },
KEY_I: { code: 73, value: 'I' },
KEY_J: { code: 74, value: 'J' },
KEY_K: { code: 75, value: 'K' },
KEY_L: { code: 76, value: 'L' },
KEY_M: { code: 77, value: 'M' },
KEY_N: { code: 78, value: 'N' },
KEY_O: { code: 79, value: 'O' },
KEY_P: { code: 80, value: 'P' },
KEY_Q: { code: 81, value: 'Q' },
KEY_R: { code: 82, value: 'R' },
KEY_S: { code: 83, value: 'S' },
KEY_T: { code: 84, value: 'T' },
KEY_U: { code: 85, value: 'U' },
KEY_V: { code: 86, value: 'V' },
KEY_W: { code: 87, value: 'W' },
KEY_X: { code: 88, value: 'X' },
KEY_Y: { code: 89, value: 'Y' },
KEY_Z: { code: 90, value: 'Z' },
LEFT_META: { code: 91, value: 'Left Meta' },
RIGHT_META: { code: 92, value: 'Right Meta' },
SELECT: { code: 93, value: 'Select' },
NUMPAD_0: { code: 96, value: 'Numpad 0' },
NUMPAD_1: { code: 97, value: 'Numpad 1' },
NUMPAD_2: { code: 98, value: 'Numpad 2' },
NUMPAD_3: { code: 99, value: 'Numpad 3' },
NUMPAD_4: { code: 100, value: 'Numpad 4' },
NUMPAD_5: { code: 101, value: 'Numpad 5' },
NUMPAD_6: { code: 102, value: 'Numpad 6' },
NUMPAD_7: { code: 103, value: 'Numpad 7' },
NUMPAD_8: { code: 104, value: 'Numpad 8' },
NUMPAD_9: { code: 105, value: 'Numpad 9' },
MULTIPLY: { code: 106, value: 'Multiply' },
ADD: { code: 107, value: 'Add' },
SUBTRACT: { code: 109, value: 'Subtract' },
DECIMAL: { code: 110, value: 'Decimal' },
DIVIDE: { code: 111, value: 'Divide' },
F1: { code: 112, value: 'F1' },
F2: { code: 113, value: 'F2' },
F3: { code: 114, value: 'F3' },
F4: { code: 115, value: 'F4' },
F5: { code: 116, value: 'F5' },
F6: { code: 117, value: 'F6' },
F7: { code: 118, value: 'F7' },
F8: { code: 119, value: 'F8' },
F9: { code: 120, value: 'F9' },
F10: { code: 121, value: 'F10' },
F11: { code: 122, value: 'F11' },
F12: { code: 123, value: 'F12' },
NUM_LOCK: { code: 144, value: 'Num Lock' },
SCROLL_LOCK: { code: 145, value: 'Scroll Lock' },
SEMICOLON: { code: 186, value: ';' },
EQUALS: { code: 187, value: '=' },
COMMA: { code: 188, value: ',' },
DASH: { code: 189, value: '-' },
PERIOD: { code: 190, value: '.' },
FORWARD_SLASH: { code: 191, value: '/' },
GRAVE_ACCENT: { code: 192, value: '`' },
OPEN_BRACKET: { code: 219, value: '[' },
BACK_SLASH: { code: 220, value: '\\' },
CLOSE_BRACKET: { code: 221, value: ']' },
SINGLE_QUOTE: { code: 222, value: "'" },
};

export type KeyboardKeys = typeof keyboardKeys;
export type Keys = keyof typeof keyboardKeys;
4 changes: 1 addition & 3 deletions packages/components/src/components/common/frameProps.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Spacing, Spacings } from '@trezor/theme';
import { css } from 'styled-components';
import { TransientProps } from '../../utils/transientProps';

type SpacingValues = Spacings[Spacing];
import { SpacingValues } from '@trezor/theme';

type Margin = {
top?: SpacingValues;
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export * from './components/DataAnalytics';
export * from './components/Divider/Divider';
export * from './components/Dropdown/Dropdown';
export * from './components/ElevationContext/ElevationContext';
export * from './components/Flex/Flex';
export * from './components/HotkeyBadge/HotkeyBadge';
export * from './components/form/Input/Input';
export * from './components/form/InputStyles';
export * from './components/form/Radio/Radio';
Expand Down
Loading

0 comments on commit 3804ff7

Please sign in to comment.