Skip to content

Commit

Permalink
Merge branch 'main' into TET-895-Toggle-Component
Browse files Browse the repository at this point in the history
  • Loading branch information
tpiechaczek authored Sep 9, 2024
2 parents 24996b5 + 7250e0a commit c255c85
Show file tree
Hide file tree
Showing 31 changed files with 1,480 additions and 3 deletions.
9 changes: 9 additions & 0 deletions src/components/ButtonGroup/ButtonGroup.props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { HTMLAttributes } from 'react';

import { ButtonGroupConfig } from './ButtonGroup.styles';

export type ButtonGroupSize = 'medium' | 'small';
export type ButtonGroupProps = {
size?: ButtonGroupSize;
custom?: ButtonGroupConfig;
} & Omit<HTMLAttributes<HTMLSpanElement>, 'color'>;
47 changes: 47 additions & 0 deletions src/components/ButtonGroup/ButtonGroup.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { Meta, StoryObj } from '@storybook/react';

import { ButtonGroup } from './ButtonGroup';
import { Button } from '../Button/Button';

import { ButtonGroupDocs } from '@/docs-components/ButtonGroupDocs';
import { TetDocs } from '@/docs-components/TetDocs';

const meta = {
title: 'ButtonGroup',
component: ButtonGroup,
tags: ['autodocs'],
argTypes: {
size: {
options: ['small', 'medium'],
defaultValue: 'medium',
control: { type: 'radio' },
},
},
parameters: {
docs: {
description: {
component:
'A set of related buttons that are visually and functionally grouped. Button Group acts as a cohesive unit, providing users with clear options for actions or navigation.',
},
page: () => (
<TetDocs docs="https://docs.tetrisly.com/components/list/buttongroup">
<ButtonGroupDocs />
</TetDocs>
),
},
},
} satisfies Meta<typeof ButtonGroup>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
children: [
<Button label="button" />,
<Button label="button" />,
<Button label="button" />,
<Button label="button" />,
],
},
};
50 changes: 50 additions & 0 deletions src/components/ButtonGroup/ButtonGroup.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { BaseProps } from '@/types';

export type ButtonGroupConfig = {
button: {
size: {
medium: BaseProps;
small: BaseProps;
};
} & BaseProps;
} & BaseProps;

export const defaultConfig = {
display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
button: {
color: {
_: '$color-action-neutral-normal',
hover: '$color-action-neutral-hover',
},
backgroundColor: {
_: '$color-action-inverted-normal',
active: '$color-action-ghost-active',
hover: '$color-action-ghost-hover',
selected: '$color-action-ghost-selected',
},
ringColor: '$color-action-outline-normal',
size: {
// TODO think if it can be done by passing size prop to a button component
medium: {
h: '$size-medium',
px: '$space-component-padding-large',
},
small: {
h: '$size-small',
px: '$space-component-padding-medium',
},
},
borderRadius: {
first: `$border-radius-large 0px 0px $border-radius-large`,
last: `0px $border-radius-large $border-radius-large 0px`,
},
transition: true,
transitionDuration: 200,
},
} as const satisfies ButtonGroupConfig;

export const buttonGroupStyles = {
defaultConfig,
};
41 changes: 41 additions & 0 deletions src/components/ButtonGroup/ButtonGroup.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { render } from '../../tests/render';

import { ButtonGroup } from '.';

import { customPropTester } from '@/tests/customPropTester';

// TODO

const getButtonGroup = (jsx: JSX.Element) => {
const { getByTestId } = render(jsx);
return {
buttonGroup: getByTestId('button-group'),
};
};
describe('ButtonGroup', () => {
it('should render the ButtonGroup ', () => {
const { buttonGroup } = getButtonGroup(<ButtonGroup />);
expect(buttonGroup).toBeInTheDocument();
});

it('should render correct number of children', () => {
const { buttonGroup } = getButtonGroup(
<ButtonGroup>
<ButtonGroup.Item label="label" />
<ButtonGroup.Item label="label" />
<ButtonGroup.Item label="label" />
<ButtonGroup.Item label="label" />
<ButtonGroup.Item label="label" />
</ButtonGroup>,
);

expect(buttonGroup?.children.length).toEqual(5);
});

customPropTester(<ButtonGroup />, {
containerId: 'button-group',
props: {
options: ['small', 'medium'],
},
});
});
58 changes: 58 additions & 0 deletions src/components/ButtonGroup/ButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { MarginProps } from '@xstyled/styled-components';
import {
Children,
cloneElement,
isValidElement,
PropsWithChildren,
useMemo,
type FC,
} from 'react';

import { ButtonGroupProps } from './ButtonGroup.props';
import { stylesBuilder } from './stylesBuilder';
import { Button, ButtonProps } from '../Button';

import { tet } from '@/tetrisly';

type Props = FC<PropsWithChildren<ButtonGroupProps & MarginProps>> & {
Item: FC<ButtonProps & MarginProps>;
};

export const ButtonGroup: Props = ({
size = 'medium',
children,
custom,
...rest
}) => {
const styles = useMemo(
() =>
stylesBuilder({
size,
custom,
}),
[custom, size],
);

Children.forEach(children, (child) => {
if (isValidElement(child) && child?.type !== ButtonGroup.Item) {
console.error(
'You should use only ButtonGroup.Item as a child of a CheckboxGroup component.',
);
}
});

const childrenWithProps = Children.map(children, (child) => {
if (isValidElement(child)) {
return cloneElement(child, { ...styles.button });
}
return child;
});

return (
<tet.span data-testid="button-group" {...styles.container} {...rest}>
{childrenWithProps}
</tet.span>
);
};

ButtonGroup.Item = Button;
3 changes: 3 additions & 0 deletions src/components/ButtonGroup/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { ButtonGroup } from './ButtonGroup';
export type { ButtonGroupProps } from './ButtonGroup.props';
export { buttonGroupStyles } from './ButtonGroup.styles';
31 changes: 31 additions & 0 deletions src/components/ButtonGroup/stylesBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { ButtonGroupSize } from './ButtonGroup.props';
import { ButtonGroupConfig, defaultConfig } from './ButtonGroup.styles';

import { mergeConfigWithCustom } from '@/services';
import { BaseProps } from '@/types/BaseProps';

type ButtonGroupStyleBuilder = {
container: BaseProps;
button: BaseProps;
};

type ButtonGroupStyleBuilderInput = {
size: ButtonGroupSize;
custom?: ButtonGroupConfig;
};

export const stylesBuilder = ({
size,
custom,
}: ButtonGroupStyleBuilderInput): ButtonGroupStyleBuilder => {
const { button, ...container } = mergeConfigWithCustom({
defaultConfig,
custom,
});
const buttonStyles = { ...button, ...button.size[size] };

return {
container,
button: buttonStyles,
};
};
11 changes: 11 additions & 0 deletions src/components/InlineMetrics/InlineMetrics.props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { InlineMetricsConfig } from './InlineMetrics.styles';

export type TrendType = 'None' | 'Positive' | 'Negative';

export type InlineMetricsProps = {
metrics?: string;
label?: string;
trend?: TrendType;
trendValue?: string;
custom?: InlineMetricsConfig;
};
39 changes: 39 additions & 0 deletions src/components/InlineMetrics/InlineMetrics.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Meta, StoryObj } from '@storybook/react';

import { InlineMetrics } from './InlineMetrics';

import { InlineMetricsDocs } from '@/docs-components/InlineMetricsDocs';
import { TetDocs } from '@/docs-components/TetDocs';

const meta = {
title: 'Metrics / InlineMetrics',
component: InlineMetrics,
tags: ['autodocs'],
args: {},
parameters: {
backgrounds: {},
docs: {
description: {
component:
'A set of several grouped components that displays numerical data, such as, for example, key performance indicators (KPIs). Metrics provide users with a clear, visual representation of essential statistics or progress.',
},
page: () => (
<TetDocs docs="https://docs.tetrisly.com/components/in-progress/metrics">
<InlineMetricsDocs />
</TetDocs>
),
},
},
} satisfies Meta<typeof InlineMetrics>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
trend: 'Negative',
trendValue: '+24%',
metrics: '$123.12',
label: 'Total Earnings',
},
};
76 changes: 76 additions & 0 deletions src/components/InlineMetrics/InlineMetrics.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { TrendType } from './InlineMetrics.props';

import type { BaseProps } from '@/types/BaseProps';
import { IconName } from '@/utility-types/IconName';

export type InlineMetricsConfig = {
innerElements: {
label: BaseProps;
metric: BaseProps;
trendContainer: BaseProps;
trend: { trend: Partial<Record<TrendType, BaseProps>> } & BaseProps;
icon: BaseProps;
trendValue: BaseProps;
};
} & BaseProps;

export const defaultConfig = {
w: 'fill',
h: '',
display: 'flex',
flexDirection: 'column',
innerElements: {
trendContainer: {
display: 'flex',
color: '$color-content-primary',
gap: '$space-component-gap-medium',
},
label: {
color: '$color-content-secondary',
text: '$typo-body-medium',
marginBottom: '$space-component-gap-medium',
},
metric: {
text: '$typo-header-4xLarge',
color: '$color-content-primary',
},
trend: {
gap: '$space-component-gap-small',
padding: '$space-component-padding-xSmall 0',
display: 'flex',
alignItems: 'center',
alignSelf: 'flex-end',
trend: {
None: {},
Positive: {
color: '$color-content-positive-secondary',
},
Negative: {
color: '$color-content-negative-secondary',
},
},
},
icon: {
display: 'flex',
},
trendValue: {
text: '$typo-body-strong-medium',
display: 'flex',
alignItems: 'end',
},
},
} satisfies InlineMetricsConfig;

export const inlineMetricsStyles = {
defaultConfig,
};

export const resolveIconName = (trend: TrendType) => {
const iconConfig = {
None: '20-minus',
Positive: '20-trend-up',
Negative: '20-trend-down',
} satisfies Record<TrendType, IconName<20>>;

return iconConfig[trend];
};
Loading

0 comments on commit c255c85

Please sign in to comment.