From b0efbd2a8f06242f078232f0b2f5a523e07bf487 Mon Sep 17 00:00:00 2001 From: winkerVSbecks Date: Wed, 14 Oct 2020 20:23:05 -0400 Subject: [PATCH 1/4] Added TagItem and TagList components --- src/components/index.js | 2 + src/components/tag/TagItem.stories.js | 12 +++ src/components/tag/TagItem.tsx | 52 +++++++++++ src/components/tag/TagList.stories.js | 127 ++++++++++++++++++++++++++ src/components/tag/TagList.tsx | 57 ++++++++++++ 5 files changed, 250 insertions(+) create mode 100644 src/components/tag/TagItem.stories.js create mode 100644 src/components/tag/TagItem.tsx create mode 100644 src/components/tag/TagList.stories.js create mode 100644 src/components/tag/TagList.tsx diff --git a/src/components/index.js b/src/components/index.js index cfdff00c..d550fd49 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -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'; diff --git a/src/components/tag/TagItem.stories.js b/src/components/tag/TagItem.stories.js new file mode 100644 index 00000000..a5ba1e52 --- /dev/null +++ b/src/components/tag/TagItem.stories.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { TagItem } from './TagItem'; +import { StoryLinkWrapper } from '../StoryLinkWrapper'; + +export default { + component: TagItem, + title: 'Design System/Tag/TagItem', +}; + +export const Default = () => ⚛️ React; + +export const WithLinkWrapper = () => ⚛️ React; diff --git a/src/components/tag/TagItem.tsx b/src/components/tag/TagItem.tsx new file mode 100644 index 00000000..c54a51c5 --- /dev/null +++ b/src/components/tag/TagItem.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import styled from 'styled-components'; +import { rgba } from 'polished'; +import { color, typography, background, spacing } from '../shared/styles'; +// @ts-ignore +import { Link } from '../Link'; + +type TagItemProps = { + containsIcon?: boolean; + inverse?: boolean; + isButton?: boolean; + LinkWrapper?: React.ComponentType | (() => React.ReactNode); + nochrome?: boolean; + secondary?: boolean; + tertiary?: boolean; + withArrow?: boolean; +}; + +export const TagItem = styled(Link)` + 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; + + &: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; + } + + &:hover { + border-color: ${rgba(color.secondary, 0.5)}; + + &:after { + opacity: 1; + } + } +`; diff --git a/src/components/tag/TagList.stories.js b/src/components/tag/TagList.stories.js new file mode 100644 index 00000000..32e43a39 --- /dev/null +++ b/src/components/tag/TagList.stories.js @@ -0,0 +1,127 @@ +import React from 'react'; +import { TagList } from './TagList'; +import { TagItem } from './TagItem'; + +export default { + component: TagList, + excludeStories: /.*Data$/, + title: 'Design System/Tag/TagList', +}; + +export const Default = () => ( + ( + + {tag.name} + + ))} + /> +); + +export const WithMoreTags = () => ( + ( + + {tag.name} + + ))} + /> +); + +export const WithCustomLimit = () => ( + ( + + {tag.name} + + ))} + limit={6} + /> +); + +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', + }, +]; diff --git a/src/components/tag/TagList.tsx b/src/components/tag/TagList.tsx new file mode 100644 index 00000000..a43c4f0b --- /dev/null +++ b/src/components/tag/TagList.tsx @@ -0,0 +1,57 @@ +import React, { useState, forwardRef } from 'react'; +import styled from 'styled-components'; +import { typography } from '../shared/styles'; +// @ts-ignore +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; + } +`; + +type TagListTypes = { + tags: typeof TagItem[]; + limit: number; +}; + +export const TagList = forwardRef( + ({ tags, limit, ...props }: TagListTypes, ref) => { + const primaryTags = tags.slice(0, limit); + const moreTags = tags.slice(limit); + const [moreTagsVisible, setMoreTagsVisible] = useState(false); + + return ( + + {primaryTags} + {moreTagsVisible && moreTags} + {moreTags.length > 0 && !moreTagsVisible && ( + setMoreTagsVisible(true)}> + {`+ ${moreTags.length} more`} + + )} + + ); + } +); + +TagList.defaultProps = { + limit: 4, +}; From 6897fbe576de44c0d18a47eb380d3e84bea6c63f Mon Sep 17 00:00:00 2001 From: winkerVSbecks Date: Mon, 19 Oct 2020 20:29:48 -0400 Subject: [PATCH 2/4] Add loading state for tags and split TagItem into Item and Link --- ...TagItem.stories.js => TagItem.stories.tsx} | 5 +- src/components/tag/TagItem.tsx | 54 ++++++--------- src/components/tag/TagLink.stories.tsx | 19 ++++++ src/components/tag/TagLink.tsx | 66 +++++++++++++++++++ ...TagList.stories.js => TagList.stories.tsx} | 64 +++++++++--------- src/components/tag/TagList.tsx | 36 ++++++---- 6 files changed, 166 insertions(+), 78 deletions(-) rename src/components/tag/{TagItem.stories.js => TagItem.stories.tsx} (57%) create mode 100644 src/components/tag/TagLink.stories.tsx create mode 100644 src/components/tag/TagLink.tsx rename src/components/tag/{TagList.stories.js => TagList.stories.tsx} (67%) diff --git a/src/components/tag/TagItem.stories.js b/src/components/tag/TagItem.stories.tsx similarity index 57% rename from src/components/tag/TagItem.stories.js rename to src/components/tag/TagItem.stories.tsx index a5ba1e52..c480ddce 100644 --- a/src/components/tag/TagItem.stories.js +++ b/src/components/tag/TagItem.stories.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { TagItem } from './TagItem'; -import { StoryLinkWrapper } from '../StoryLinkWrapper'; export default { component: TagItem, @@ -9,4 +8,6 @@ export default { export const Default = () => ⚛️ React; -export const WithLinkWrapper = () => ⚛️ React; +export const WithLinkWrapper = () => ⚛️ React; + +export const Loading = () => ; diff --git a/src/components/tag/TagItem.tsx b/src/components/tag/TagItem.tsx index c54a51c5..582cfd9a 100644 --- a/src/components/tag/TagItem.tsx +++ b/src/components/tag/TagItem.tsx @@ -1,22 +1,17 @@ -import React from 'react'; -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; import { rgba } from 'polished'; import { color, typography, background, spacing } from '../shared/styles'; // @ts-ignore -import { Link } from '../Link'; +import { inlineGlow } from '../shared/animation'; -type TagItemProps = { - containsIcon?: boolean; - inverse?: boolean; - isButton?: boolean; - LinkWrapper?: React.ComponentType | (() => React.ReactNode); - nochrome?: boolean; - secondary?: boolean; - tertiary?: boolean; - withArrow?: boolean; +export type TagItemProps = { + isLoading?: boolean; }; -export const TagItem = styled(Link)` +export const TagItem = styled.div.attrs(({ isLoading, children }) => ({ + children: isLoading ? 'Loading tag' : children, +}))` + display: inline-block; background: ${background.app}; border-radius: ${spacing.borderRadius.small}px; padding: 5px 10px; @@ -29,24 +24,17 @@ export const TagItem = styled(Link)` border-color: transparent; white-space: nowrap; - &: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; - } - - &:hover { - border-color: ${rgba(color.secondary, 0.5)}; - - &:after { - opacity: 1; - } - } + ${(props) => + props.isLoading && + css` + cursor: progress !important; + ${inlineGlow}; + &:hover { + color: transparent; + } + `} `; + +TagItem.defaultProps = { + isLoading: false, +}; diff --git a/src/components/tag/TagLink.stories.tsx b/src/components/tag/TagLink.stories.tsx new file mode 100644 index 00000000..9a7eaa38 --- /dev/null +++ b/src/components/tag/TagLink.stories.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { TagLink } from './TagLink'; +// @ts-ignore +import { StoryLinkWrapper } from '../StoryLinkWrapper'; + +export default { + component: TagLink, + title: 'Design System/Tag/TagLink', +}; + +export const Default = () => ⚛️ React; + +export const WithLinkWrapper = () => ( + }> + ⚛️ React + +); + +export const Loading = () => ; diff --git a/src/components/tag/TagLink.tsx b/src/components/tag/TagLink.tsx new file mode 100644 index 00000000..6400d9b2 --- /dev/null +++ b/src/components/tag/TagLink.tsx @@ -0,0 +1,66 @@ +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'; + +export interface TagLinkProps extends React.ComponentProps { + isLoading?: boolean; +} + +export const TagLink = styled(Link).attrs(({ isLoading, children }) => ({ + children: isLoading ? 'Loading tag' : children, +}))` + 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; + + &: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; + } + } + `} + + ${(props) => + props.isLoading && + css` + cursor: progress !important; + ${inlineGlow}; + &:hover { + color: transparent; + } + `} +`; + +TagLink.defaultProps = { + isLoading: false, +}; diff --git a/src/components/tag/TagList.stories.js b/src/components/tag/TagList.stories.tsx similarity index 67% rename from src/components/tag/TagList.stories.js rename to src/components/tag/TagList.stories.tsx index 32e43a39..f8c374aa 100644 --- a/src/components/tag/TagList.stories.js +++ b/src/components/tag/TagList.stories.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { TagList } from './TagList'; import { TagItem } from './TagItem'; +import { TagLink } from './TagLink'; export default { component: TagList, @@ -8,37 +9,6 @@ export default { title: 'Design System/Tag/TagList', }; -export const Default = () => ( - ( - - {tag.name} - - ))} - /> -); - -export const WithMoreTags = () => ( - ( - - {tag.name} - - ))} - /> -); - -export const WithCustomLimit = () => ( - ( - - {tag.name} - - ))} - limit={6} - /> -); - export const mockTagsData = [ { link: '/ui', @@ -125,3 +95,35 @@ export const mockTagsData = [ name: '📽 Animation', }, ]; + +const Template = (args: { tags: any[]; limit: number; isLoading }) => ( + ( + {tag.name} + ))} + /> +); + +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 = () => ( + ( + + {tag.name} + + ))} + /> +); diff --git a/src/components/tag/TagList.tsx b/src/components/tag/TagList.tsx index a43c4f0b..6bdd23b8 100644 --- a/src/components/tag/TagList.tsx +++ b/src/components/tag/TagList.tsx @@ -1,7 +1,6 @@ import React, { useState, forwardRef } from 'react'; import styled from 'styled-components'; import { typography } from '../shared/styles'; -// @ts-ignore import { Link } from '../Link'; import { TagItem } from './TagItem'; @@ -27,19 +26,21 @@ const MoreTagsButton = styled(Link)` } `; -type TagListTypes = { - tags: typeof TagItem[]; - limit: number; +export type TagListProps = { + tags: React.ReactNode[]; + isLoading?: boolean; + limit?: number; }; -export const TagList = forwardRef( - ({ tags, limit, ...props }: TagListTypes, ref) => { +export const TagList = forwardRef( + ({ tags = [], limit = 4, isLoading = false, ...props }: TagListProps, ref) => { const primaryTags = tags.slice(0, limit); const moreTags = tags.slice(limit); + const [moreTagsVisible, setMoreTagsVisible] = useState(false); - return ( - + const tagContent = ( + <> {primaryTags} {moreTagsVisible && moreTags} {moreTags.length > 0 && !moreTagsVisible && ( @@ -47,11 +48,22 @@ export const TagList = forwardRef( {`+ ${moreTags.length} more`} )} + + ); + + return ( + + {isLoading ? ( + <> + + + + + + ) : ( + tagContent + )} ); } ); - -TagList.defaultProps = { - limit: 4, -}; From 54b9565ccfa9b5ea48cd5b3ecf0b7dd85bc5db35 Mon Sep 17 00:00:00 2001 From: winkerVSbecks Date: Tue, 17 Nov 2020 14:39:52 -0500 Subject: [PATCH 3/4] extend TagLink from TagItem --- src/components/tag/TagItem.tsx | 1 - src/components/tag/TagLink.tsx | 27 +++----------------------- src/components/tag/TagList.stories.tsx | 2 +- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/src/components/tag/TagItem.tsx b/src/components/tag/TagItem.tsx index 582cfd9a..80885476 100644 --- a/src/components/tag/TagItem.tsx +++ b/src/components/tag/TagItem.tsx @@ -1,5 +1,4 @@ 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'; diff --git a/src/components/tag/TagLink.tsx b/src/components/tag/TagLink.tsx index 6400d9b2..1e3cd4b1 100644 --- a/src/components/tag/TagLink.tsx +++ b/src/components/tag/TagLink.tsx @@ -4,27 +4,16 @@ 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 { isLoading?: boolean; } -export const TagLink = styled(Link).attrs(({ isLoading, children }) => ({ +export const TagLink = styled(TagItem).attrs(({ isLoading, children }) => ({ children: isLoading ? 'Loading tag' : children, + as: Link, }))` - 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; - &:after { content: ''; position: absolute; @@ -49,16 +38,6 @@ export const TagLink = styled(Link).attrs(({ isLoading, children } } } `} - - ${(props) => - props.isLoading && - css` - cursor: progress !important; - ${inlineGlow}; - &:hover { - color: transparent; - } - `} `; TagLink.defaultProps = { diff --git a/src/components/tag/TagList.stories.tsx b/src/components/tag/TagList.stories.tsx index f8c374aa..18251c1e 100644 --- a/src/components/tag/TagList.stories.tsx +++ b/src/components/tag/TagList.stories.tsx @@ -96,7 +96,7 @@ export const mockTagsData = [ }, ]; -const Template = (args: { tags: any[]; limit: number; isLoading }) => ( +const Template = (args: { tags: any[]; limit: number; isLoading: boolean }) => ( Date: Wed, 18 Nov 2020 15:32:16 -0500 Subject: [PATCH 4/4] Add randomness to tag loading state --- package.json | 1 + src/components/tag/TagItem.stories.tsx | 6 ++++-- src/components/tag/TagItem.tsx | 10 +++++++++- src/components/tag/TagLink.stories.tsx | 4 ++++ src/components/tag/TagList.stories.tsx | 4 ++++ yarn.lock | 5 +++++ 6 files changed, 27 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ffc473d9..1bb7d6f3 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/components/tag/TagItem.stories.tsx b/src/components/tag/TagItem.stories.tsx index c480ddce..ab0106b0 100644 --- a/src/components/tag/TagItem.stories.tsx +++ b/src/components/tag/TagItem.stories.tsx @@ -1,6 +1,10 @@ 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', @@ -8,6 +12,4 @@ export default { export const Default = () => ⚛️ React; -export const WithLinkWrapper = () => ⚛️ React; - export const Loading = () => ; diff --git a/src/components/tag/TagItem.tsx b/src/components/tag/TagItem.tsx index 80885476..3677b217 100644 --- a/src/components/tag/TagItem.tsx +++ b/src/components/tag/TagItem.tsx @@ -7,8 +7,16 @@ 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(({ isLoading, children }) => ({ - children: isLoading ? 'Loading tag' : children, + children: isLoading ? randomString(5, 12) : children, + ...(isLoading && { 'aria-label': 'Loading tag' }), }))` display: inline-block; background: ${background.app}; diff --git a/src/components/tag/TagLink.stories.tsx b/src/components/tag/TagLink.stories.tsx index 9a7eaa38..40669193 100644 --- a/src/components/tag/TagLink.stories.tsx +++ b/src/components/tag/TagLink.stories.tsx @@ -1,8 +1,12 @@ 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', diff --git a/src/components/tag/TagList.stories.tsx b/src/components/tag/TagList.stories.tsx index 18251c1e..a650f1c2 100644 --- a/src/components/tag/TagList.stories.tsx +++ b/src/components/tag/TagList.stories.tsx @@ -1,8 +1,12 @@ 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$/, diff --git a/yarn.lock b/yarn.lock index b9c5d5bd..5cad556e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"