Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TagItem and TagList components #212

Merged
merged 4 commits into from
Nov 19, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"prettier": "^2.0.5",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"seedrandom": "^3.0.5",
"sort-package-json": "^1.44.0",
"ts-loader": "^7.0.5",
"typescript": "^3.9.5"
Expand Down
2 changes: 2 additions & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ export * from './CodeSnippets';
export * from './header/Header';
export * from './header/NavLink';
export { NavItem } from './header/NavItem';
export * from './tag/TagItem';
export * from './tag/TagList';
15 changes: 15 additions & 0 deletions src/components/tag/TagItem.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
// @ts-ignore
import seedrandom from 'seedrandom';
import { TagItem } from './TagItem';

seedrandom('chromatic testing', { global: true });

export default {
component: TagItem,
title: 'Design System/Tag/TagItem',
};

export const Default = () => <TagItem>⚛️ React</TagItem>;

export const Loading = () => <TagItem isLoading />;
47 changes: 47 additions & 0 deletions src/components/tag/TagItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import styled, { css } from 'styled-components';
import { color, typography, background, spacing } from '../shared/styles';
// @ts-ignore
import { inlineGlow } from '../shared/animation';

export type TagItemProps = {
isLoading?: boolean;
};

function randomString(min: number, max: number) {
const length = Math.random() * (max - min) + min;
return Math.round(36 ** length + 1 - Math.random() * 36 ** length)
.toString(36)
.slice(1);
}

export const TagItem = styled.div.attrs<TagItemProps>(({ isLoading, children }) => ({
children: isLoading ? randomString(5, 12) : children,
...(isLoading && { 'aria-label': 'Loading tag' }),
}))<TagItemProps>`
display: inline-block;
background: ${background.app};
border-radius: ${spacing.borderRadius.small}px;
padding: 5px 10px;
font-size: ${typography.size.s2}px;
line-height: ${typography.size.m2}px;
position: relative;
color: ${color.darkest};
border-width: 1px;
border-style: solid;
border-color: transparent;
white-space: nowrap;

${(props) =>
props.isLoading &&
css`
cursor: progress !important;
${inlineGlow};
&:hover {
color: transparent;
}
`}
`;

TagItem.defaultProps = {
isLoading: false,
};
23 changes: 23 additions & 0 deletions src/components/tag/TagLink.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
// @ts-ignore
import seedrandom from 'seedrandom';
import { TagLink } from './TagLink';
// @ts-ignore
import { StoryLinkWrapper } from '../StoryLinkWrapper';

seedrandom('chromatic testing', { global: true });

export default {
component: TagLink,
title: 'Design System/Tag/TagLink',
};

export const Default = () => <TagLink href="https://chromatic.com">⚛️ React</TagLink>;

export const WithLinkWrapper = () => (
<TagLink to="https://chromatic.com" LinkWrapper={StoryLinkWrapper as React.FC<{ to: string }>}>
⚛️ React
</TagLink>
);

export const Loading = () => <TagLink isLoading />;
45 changes: 45 additions & 0 deletions src/components/tag/TagLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import styled, { css } from 'styled-components';
import { rgba } from 'polished';
import { color, typography, background, spacing } from '../shared/styles';
// @ts-ignore
import { inlineGlow } from '../shared/animation';
import { Link } from '../Link';
import { TagItem } from './TagItem';

export interface TagLinkProps extends React.ComponentProps<typeof Link> {
isLoading?: boolean;
}

export const TagLink = styled(TagItem).attrs<TagLinkProps>(({ isLoading, children }) => ({
children: isLoading ? 'Loading tag' : children,
as: Link,
}))<TagLinkProps>`
&:after {
content: '';
position: absolute;
z-index: -1;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-shadow: rgba(0, 0, 0, 0.08) 0 3px 10px 0;
opacity: 0;
transition: opacity 0.3s ease-in-out;
}

${(props) =>
!props.isLoading &&
css`
&:hover {
border-color: ${rgba(color.secondary, 0.5)};

&:after {
opacity: 1;
}
}
`}
`;

TagLink.defaultProps = {
isLoading: false,
};
133 changes: 133 additions & 0 deletions src/components/tag/TagList.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React from 'react';
// @ts-ignore
import seedrandom from 'seedrandom';
import { TagList } from './TagList';
import { TagItem } from './TagItem';
import { TagLink } from './TagLink';

seedrandom('chromatic testing', { global: true });

export default {
component: TagList,
excludeStories: /.*Data$/,
title: 'Design System/Tag/TagList',
};

export const mockTagsData = [
{
link: '/ui',
name: '📲 UI',
},
{
link: '/react',
name: '⚛️ React',
},
{
link: '/components',
name: '🧩 Components',
},
{
link: '/front-end-development',
name: '👩🏽‍💻 Front-end development',
},
{
link: '/graphql',
name: '🕸 GraphQL',
},
{
link: '/storybook',
name: '📕 Storybook',
},
{
link: '/component-libraries',
name: '🏗 Component libraries',
},
{
link: '/design',
name: '🎨 Design',
},
{
link: '/open-source',
name: 'Open-source',
},
{
link: '/startup',
name: 'Startup',
},
{
link: '/ux',
name: 'UX',
},
{
link: '/design-system',
name: '🗃 Design systems',
},
{
link: '/api',
name: 'API',
},
{
link: '/entrepreneurship',
name: 'Entrepreneurship',
},
{
link: '/testing',
name: '✅ Testing',
},
{
link: '/remote-work',
name: 'Remote work',
},
{
link: '/venture-capital',
name: 'Venture capital',
},
{
link: '/company-culture',
name: 'Company culture',
},
{
link: '/dev-tools',
name: 'Dev tools',
},
{
link: '/mongodb',
name: 'MongoDB',
},
{
link: '/animation',
name: '📽 Animation',
},
];

const Template = (args: { tags: any[]; limit: number; isLoading: boolean }) => (
<TagList
isLoading={args.isLoading}
limit={args.limit}
tags={args.tags.map((tag: any) => (
<TagItem key={tag.link}>{tag.name}</TagItem>
))}
/>
);

export const Default = Template.bind({});
Default.args = { tags: mockTagsData.slice(0, 4) };

export const WithMoreTags = Template.bind({});
WithMoreTags.args = { tags: mockTagsData };

export const WithCustomLimit = Template.bind({});
WithCustomLimit.args = { tags: mockTagsData, limit: 6 };

export const Loading = Template.bind({});
Loading.args = { tags: [], isLoading: true };

export const AsLinks = () => (
<TagList
tags={mockTagsData.map((tag: any) => (
<TagLink key={tag.link} href={tag.link}>
{tag.name}
</TagLink>
))}
/>
);
69 changes: 69 additions & 0 deletions src/components/tag/TagList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { useState, forwardRef } from 'react';
import styled from 'styled-components';
import { typography } from '../shared/styles';
import { Link } from '../Link';
import { TagItem } from './TagItem';

const TagListWrapper = styled.div`
display: flex;
flex-wrap: wrap;

> * {
margin-right: 10px;
margin-bottom: 10px;
}

margin-bottom: -10px;
`;

const MoreTagsButton = styled(Link)`
font-size: ${typography.size.s2}px;
padding-left: 5px;
padding-right: 5px;

&:focus {
outline: auto;
}
`;

export type TagListProps = {
tags: React.ReactNode[];
isLoading?: boolean;
limit?: number;
};

export const TagList = forwardRef<HTMLDivElement, TagListProps>(
({ tags = [], limit = 4, isLoading = false, ...props }: TagListProps, ref) => {
const primaryTags = tags.slice(0, limit);
const moreTags = tags.slice(limit);

const [moreTagsVisible, setMoreTagsVisible] = useState(false);

const tagContent = (
<>
{primaryTags}
{moreTagsVisible && moreTags}
{moreTags.length > 0 && !moreTagsVisible && (
<MoreTagsButton isButton appearance="primary" onClick={() => setMoreTagsVisible(true)}>
{`+ ${moreTags.length} more`}
</MoreTagsButton>
)}
</>
);

return (
<TagListWrapper {...props} ref={ref}>
{isLoading ? (
<>
<TagItem isLoading />
<TagItem isLoading />
<TagItem isLoading />
<TagItem isLoading />
</>
) : (
tagContent
)}
</TagListWrapper>
);
}
);
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12957,6 +12957,11 @@ schema-utils@^2.6.5, schema-utils@^2.6.6:
ajv "^6.12.0"
ajv-keywords "^3.4.1"

seedrandom@^3.0.5:
version "3.0.5"
resolved "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7"
integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==

select@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
Expand Down