Skip to content

Commit 5d31e9b

Browse files
tag-group: adding new component (#5380)
1 parent 3a6a6e2 commit 5d31e9b

File tree

9 files changed

+365
-0
lines changed

9 files changed

+365
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as React from "react";
2+
import { styled } from "baseui";
3+
import { Tag, SUPPORTED_KIND } from "baseui/tag";
4+
import { TagGroup, HIERARCHY } from "baseui/tag-group";
5+
6+
const Container = styled("div", {
7+
display: "flex",
8+
flexDirection: "column",
9+
gap: "32px",
10+
});
11+
12+
export default function Example() {
13+
return (
14+
<Container>
15+
{Object.values(HIERARCHY).map((hierarchy) => (
16+
<TagGroup hierarchy={hierarchy} key={hierarchy}>
17+
{Object.values(SUPPORTED_KIND)
18+
.filter((kind) => kind !== SUPPORTED_KIND.custom)
19+
.map((kind) => (
20+
<Tag key={kind} kind={kind}>
21+
{kind}
22+
</Tag>
23+
))}
24+
</TagGroup>
25+
))}
26+
</Container>
27+
);
28+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import * as React from "react";
2+
import { styled } from "baseui";
3+
import { Tag } from "baseui/tag";
4+
import { TagGroup, SIZE } from "baseui/tag-group";
5+
import { LabelSmall } from "baseui/typography";
6+
7+
const Container = styled("div", {
8+
display: "flex",
9+
flexDirection: "column",
10+
gap: "32px",
11+
});
12+
13+
const Row = styled("div", {
14+
display: "flex",
15+
flexDirection: "column",
16+
gap: "8px",
17+
});
18+
19+
export default function Example() {
20+
return (
21+
<Container>
22+
{Object.values(SIZE).map((size) => (
23+
<Row key={size}>
24+
<LabelSmall>{size}</LabelSmall>
25+
<TagGroup size={size}>
26+
<Tag>gray</Tag>
27+
<Tag kind="red">red</Tag>
28+
</TagGroup>
29+
</Row>
30+
))}
31+
</Container>
32+
);
33+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import * as React from "react";
2+
import { useStyletron } from "baseui";
3+
import { Tag, SUPPORTED_KIND } from "baseui/tag";
4+
import { TagGroup } from "baseui/tag-group";
5+
import { HeadingSmall, ParagraphSmall } from "baseui/typography";
6+
7+
export default function Example() {
8+
const [css] = useStyletron();
9+
return (
10+
<React.Fragment>
11+
<HeadingSmall>Wrap: true (default)</HeadingSmall>
12+
13+
<ParagraphSmall>no width set on container</ParagraphSmall>
14+
<TagGroup>
15+
{Object.values(SUPPORTED_KIND)
16+
.filter((kind) => kind !== SUPPORTED_KIND.custom)
17+
.map((kind) => (
18+
<Tag key={kind} kind={kind}>
19+
{kind}
20+
</Tag>
21+
))}
22+
</TagGroup>
23+
24+
<ParagraphSmall>width set to 300px on container </ParagraphSmall>
25+
<div className={css({ width: "300px" })}>
26+
<TagGroup>
27+
{Object.values(SUPPORTED_KIND)
28+
.filter((kind) => kind !== SUPPORTED_KIND.custom)
29+
.map((kind) => (
30+
<Tag key={kind} kind={kind}>
31+
{kind}
32+
</Tag>
33+
))}
34+
</TagGroup>
35+
</div>
36+
37+
<ParagraphSmall>
38+
width set to 300px on container and tag with long text{" "}
39+
</ParagraphSmall>
40+
<div className={css({ width: "300px" })}>
41+
<TagGroup>
42+
{Object.values(SUPPORTED_KIND)
43+
.filter((kind) => kind !== SUPPORTED_KIND.custom)
44+
.map((kind, index) => (
45+
<Tag key={kind} kind={kind}>
46+
{index === 1
47+
? "A very long tag text to test wrapping behavior"
48+
: kind}
49+
</Tag>
50+
))}
51+
</TagGroup>
52+
</div>
53+
54+
<ParagraphSmall>
55+
width set to 100px(smaller than tag text default max width) on container
56+
and tag with long text - very extreme case
57+
</ParagraphSmall>
58+
<div className={css({ marginBottom: "40px", width: "100px" })}>
59+
<TagGroup>
60+
{Object.values(SUPPORTED_KIND)
61+
.filter((kind) => kind !== SUPPORTED_KIND.custom)
62+
.map((kind, index) => (
63+
<Tag key={kind} kind={kind}>
64+
{index === 1
65+
? "A very long tag text to test wrapping behavior"
66+
: kind}
67+
</Tag>
68+
))}
69+
</TagGroup>
70+
</div>
71+
72+
<HeadingSmall>Wrap: false</HeadingSmall>
73+
<ParagraphSmall>no width set on container</ParagraphSmall>
74+
<TagGroup>
75+
{Object.values(SUPPORTED_KIND)
76+
.filter((kind) => kind !== SUPPORTED_KIND.custom)
77+
.map((kind) => (
78+
<Tag key={kind} kind={kind}>
79+
{kind}
80+
</Tag>
81+
))}
82+
</TagGroup>
83+
84+
<ParagraphSmall>width set to 300px on container </ParagraphSmall>
85+
<div className={css({ width: "300px" })}>
86+
<TagGroup wrap={false}>
87+
{Object.values(SUPPORTED_KIND)
88+
.filter((kind) => kind !== SUPPORTED_KIND.custom)
89+
.map((kind) => (
90+
<Tag key={kind} kind={kind}>
91+
{kind}
92+
</Tag>
93+
))}
94+
</TagGroup>
95+
</div>
96+
97+
<ParagraphSmall>
98+
width set to 300px on container and tag with long text{" "}
99+
</ParagraphSmall>
100+
<div className={css({ marginBottom: "40px", width: "300px" })}>
101+
<TagGroup wrap={false}>
102+
{Object.values(SUPPORTED_KIND)
103+
.filter((kind) => kind !== SUPPORTED_KIND.custom)
104+
.map((kind, index) => (
105+
<Tag key={kind} kind={kind}>
106+
{index === 1
107+
? "A very long tag text to test wrapping behavior"
108+
: kind}
109+
</Tag>
110+
))}
111+
</TagGroup>
112+
</div>
113+
</React.Fragment>
114+
);
115+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import Example from "../../components/example";
2+
import Layout from "../../components/layout";
3+
import Exports from "../../components/exports";
4+
5+
import Hierarchy from "examples/tag-group/hierachy.tsx";
6+
import Size from "examples/tag-group/size.tsx";
7+
import Wrap from "examples/tag-group/wrap.tsx";
8+
9+
import { TagGroup } from "baseui/tag-group";
10+
import * as TagGroupExports from "baseui/tag-group";
11+
12+
export default Layout;
13+
14+
# Tag Group
15+
16+
Groups multiple tags together with shared styling and behavior.
17+
18+
## Examples
19+
20+
<Example title="Tag Group hierarchy" path="tag-group/hierachy.tsx">
21+
<Hierarchy />
22+
</Example>
23+
24+
<Example title="Tag Group size" path="tag-group/size.tsx">
25+
<Size />
26+
</Example>
27+
28+
<Example title="Tag Group wrap" path="tag-group/wrap.tsx">
29+
<Wrap />
30+
</Example>
31+
32+
<Exports
33+
component={TagGroupExports}
34+
title="Tag Group exports"
35+
path="baseui/tag-group"
36+
/>

documentation-site/routes.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,10 @@ const routes = [
275275
title: 'Tag',
276276
itemId: '/components/tag',
277277
},
278+
{
279+
title: 'Tag Group',
280+
itemId: '/components/tag-group',
281+
},
278282
{
279283
title: 'Tile',
280284
itemId: '/components/tile',

src/tag-group/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
Copyright (c) Uber Technologies, Inc.
3+
4+
This source code is licensed under the MIT license found in the
5+
LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import { SIZE, HIERARCHY } from '../tag';
9+
10+
export { SIZE, HIERARCHY };
11+
12+
export * from './types';
13+
14+
export { default as TagGroup } from './tag-group';
15+
16+
export { StyledRoot } from './styled-components';

src/tag-group/styled-components.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
Copyright (c) Uber Technologies, Inc.
3+
4+
This source code is licensed under the MIT license found in the
5+
LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
9+
import { styled, type Theme } from '../styles';
10+
import type { Properties } from 'csstype';
11+
12+
export const StyledRoot = styled<
13+
'div',
14+
{
15+
$wrap?: boolean;
16+
}
17+
>('div', ({ $wrap, $theme }) => {
18+
return {
19+
display: 'flex',
20+
columnGap: $theme.sizing.scale300,
21+
rowGap: $theme.sizing.scale300,
22+
...getWrapStyles({ $wrap, $theme }),
23+
};
24+
});
25+
StyledRoot.displayName = 'StyledRoot';
26+
27+
type WrapStyles = {
28+
flexWrap?: Properties['flexWrap'];
29+
overflowX?: Properties['overflowX'];
30+
scrollbarWidth?: Properties['scrollbarWidth'];
31+
padding?: Properties['padding'];
32+
};
33+
34+
const getWrapStyles = ({ $wrap, $theme }): WrapStyles => {
35+
if (typeof $wrap === 'boolean') {
36+
return $wrap
37+
? {
38+
padding: 0,
39+
flexWrap: 'wrap',
40+
}
41+
: {
42+
overflowX: 'auto',
43+
scrollbarWidth: 'none',
44+
padding: `0 ${$theme.sizing.scale600}`,
45+
};
46+
}
47+
return {};
48+
};

src/tag-group/tag-group.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
Copyright (c) Uber Technologies, Inc.
3+
4+
This source code is licensed under the MIT license found in the
5+
LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import * as React from 'react';
9+
10+
import { KIND, SIZE } from '../tag';
11+
import { getOverrides } from '../helpers/overrides';
12+
13+
import { StyledRoot } from './styled-components';
14+
import type { TagGroupProps } from './types';
15+
16+
const TagGroup = React.forwardRef<HTMLDivElement, TagGroupProps>((props, ref) => {
17+
const {
18+
children,
19+
wrap = true,
20+
hierarchy = KIND.primary,
21+
size = SIZE.small,
22+
overrides = {},
23+
} = props;
24+
25+
const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot);
26+
const styleProps = {
27+
$wrap: wrap,
28+
};
29+
30+
return (
31+
<Root ref={ref} {...styleProps} {...rootProps}>
32+
{React.Children.map(children, (child) => {
33+
if (!React.isValidElement(child)) {
34+
return null;
35+
}
36+
37+
return React.cloneElement(child, {
38+
hierarchy,
39+
size,
40+
// All tags in tag group are display only
41+
onActionClick: undefined,
42+
onActionKeyDown: undefined,
43+
onClick: undefined,
44+
onKeyDown: undefined,
45+
closeable: false,
46+
overrides: {
47+
Root: {
48+
style: {
49+
// Single Tag has default margin, reset it to 0 in TagGroup
50+
margin: 0,
51+
...(wrap ? { maxWidth: '100%' } : {}), // ensure wrapping works correctly even if Tag itself has a custom maxWidth(Tag has a default maxWidth 128px on Text inside)
52+
...child.props.overrides?.Root?.style,
53+
},
54+
...child.props.overrides?.Root,
55+
},
56+
...child.props.overrides,
57+
},
58+
});
59+
})}
60+
</Root>
61+
);
62+
});
63+
64+
TagGroup.displayName = 'TagGroup';
65+
66+
export default TagGroup;

src/tag-group/types.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type * as React from 'react';
2+
import type { Override } from '../helpers/overrides';
3+
import type { HIERARCHY, SIZE } from '../tag';
4+
5+
export type TagGroupProps = {
6+
/** Set of more than one `Button` components */
7+
children: Array<React.ReactNode>;
8+
overrides?: TagGroupOverrides;
9+
/** Determines whether tags should wrap to the next line when they exceed the container width. Defaults to true */
10+
wrap?: boolean;
11+
/** Defines tags look in the tag group. Set it to one of HIERARCHY[key] values. Defaults to HIERARCHY.primary */
12+
hierarchy?: (typeof HIERARCHY)[keyof typeof HIERARCHY];
13+
/** Determines the size of the Tag in the tag group. Defaults to small */
14+
size?: (typeof SIZE)[keyof typeof SIZE];
15+
};
16+
17+
type TagGroupOverrides = {
18+
Root?: Override;
19+
};

0 commit comments

Comments
 (0)