diff --git a/.storybook/main.ts b/.storybook/main.ts index 6195028..8f578b3 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,4 +1,5 @@ import type { StorybookConfig } from '@storybook/react-vite'; +import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'; const config: StorybookConfig = { stories: ['../src/**/*.stories.tsx'], @@ -10,6 +11,17 @@ const config: StorybookConfig = { docs: { autodocs: false, }, + async viteFinal(config) { + const { mergeConfig } = await import('vite'); + + return mergeConfig(config, { + plugins: [ + vanillaExtractPlugin({ + identifiers: 'debug', + }), + ], + }); + }, }; export default config; diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 0000000..1efdde3 --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,4 @@ + diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index b87aa22..4f20f0f 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,13 +1,12 @@ +import '../src/global.css'; import React from 'react'; import type { Preview } from '@storybook/react'; -import { GlobalStyle } from '../src/styles/global-styles'; import { HistoryContextProvider } from '../src/context/HistoryContext'; const preview: Preview = { decorators: [ (Story) => ( - ), diff --git a/package.json b/package.json index 50f25a2..8739f81 100644 --- a/package.json +++ b/package.json @@ -43,11 +43,14 @@ "singleQuote": true }, "resolutions": { + "jackspeak": "2.1.1", "strip-ansi": "6.0.1" }, "dependencies": { + "@vanilla-extract/css": "^1.14.2", "body-scroll-lock": "^3.1.5", "clipboard-polyfill": "^3.0.2", + "clsx": "^2.1.0", "debounce": "^2.0.0", "focus-trap": "^7.5.4", "fuse.js": "^7.0.0", @@ -62,7 +65,6 @@ "react-dom": "18.2.0", "react-md-spinner": "^1.0.0", "react-transition-group": "^4.4.5", - "styled-components": "6.1.8", "unstated-next": "^1.1.0", "use-intersection": "^0.2.1", "x-img-diff-js": "^0.3.5" @@ -82,6 +84,7 @@ "@types/react-dom": "^18.2.24", "@types/react-measure": "^2.0.12", "@types/react-transition-group": "^4.4.10", + "@vanilla-extract/vite-plugin": "^4.0.7", "@vitejs/plugin-react": "^4.2.1", "cross-env": "^7.0.3", "eslint": "8.57.0", diff --git a/src/App/App.css.ts b/src/App/App.css.ts new file mode 100644 index 0000000..272ec0c --- /dev/null +++ b/src/App/App.css.ts @@ -0,0 +1,29 @@ +import { style } from '@vanilla-extract/css'; +import { Space, tokens } from '../styles/variables.css'; + +export const brand = style({ + position: 'absolute', + top: Space * 3, + right: Space * 3, +}); + +export const layout = style({ + display: 'flex', + height: '100%', + isolation: 'isolate', +}); + +export const content = style({ + flex: '1 0 auto', + maxWidth: '100%', +}); + +export const help = style({ + position: 'fixed', + right: Space * 3, + bottom: Space * 3, + borderRadius: '50%', + background: tokens.color.brandSecondary, + boxShadow: tokens.shadow.lv2, + zIndex: 10, +}); diff --git a/src/App/App.tsx b/src/App/App.tsx index 0e93b16..8312020 100644 --- a/src/App/App.tsx +++ b/src/App/App.tsx @@ -1,47 +1,19 @@ -import React, { useState, useCallback, useRef } from 'react'; -import styled from 'styled-components'; -import { Sidebar } from '../components/Sidebar'; -import { GlobalStyle } from '../styles/global-styles'; +import React, { useCallback, useRef, useState } from 'react'; +import { Footer } from '../components/Footer'; +import { HelpDialog } from '../components/HelpDialog'; +import { IconButton } from '../components/IconButton'; import { Logo } from '../components/Logo'; -import { Space, Shadow, Color } from '../styles/variables'; import { Main } from '../components/Main'; -import { Footer } from '../components/Footer'; -import { Viewer } from '../components/Viewer'; import { Notification } from '../components/Notification'; -import { IconButton } from '../components/IconButton'; +import { Sidebar } from '../components/Sidebar'; +import { Viewer } from '../components/Viewer'; import { HelpIcon } from '../components/icons/HelpIcon'; -import { HelpDialog } from '../components/HelpDialog'; +import { EntityContainer } from '../containers/entity/EntityContainer'; import { SidebarContainer } from '../containers/sidebar/SidebarContainer'; -import { findFirstFocusable } from '../utils/selector'; import { useMousetrap } from '../hooks/useMousetrap'; -import { EntityContainer } from '../containers/entity/EntityContainer'; - -const Layout = styled.main` - display: flex; - height: 100%; - isolation: isolate; -`; - -const Content = styled.div` - flex: 1 0 auto; - max-width: 100%; -`; - -const Brand = styled.span` - position: absolute; - top: ${Space * 3}px; - right: ${Space * 3}px; -`; - -const Help = styled.span` - position: fixed; - right: ${Space * 3}px; - bottom: ${Space * 3}px; - border-radius: 50%; - background: ${Color.BRAND_SECONDARY}; - box-shadow: ${Shadow.LEVEL2}; - z-index: 10; -`; +import { findFirstFocusable } from '../utils/selector'; +import { Color } from '../styles/variables.css'; +import * as styles from './App.css'; export type Props = {}; @@ -146,25 +118,23 @@ export const App = () => { return ( <> - - - + - + - +
- +
- - +
+
- + - + diff --git a/src/components/Card/Card.css.ts b/src/components/Card/Card.css.ts new file mode 100644 index 0000000..00a8541 --- /dev/null +++ b/src/components/Card/Card.css.ts @@ -0,0 +1,66 @@ +import { style } from '@vanilla-extract/css'; +import { BreakPoint, Space, tokens } from '../../styles/variables.css'; + +export const wrapper = style({ + position: 'relative', +}); + +export const inner = style({ + display: 'block', + width: '100%', + borderWidth: 0, + borderRadius: 6, + background: tokens.color.white, + boxShadow: tokens.shadow.lv3, + color: tokens.color.textBase, + fontSize: 'inherit', + textDecoration: 'none', + cursor: 'pointer', + transition: `box-shadow ${tokens.duration.fadeIn} ${tokens.easing.standard}`, + ':hover': { + boxShadow: tokens.shadow.lv1, + }, + ':focus-visible': { + boxShadow: tokens.state.focus, + }, +}); + +export const sign = style({ + position: 'absolute', + top: Space * 1, + left: Space * 1, + zIndex: 10, +}); + +export const image = style({ + position: 'relative', + overflow: 'hidden', + height: '260px', + borderRadius: '6px 6px 0 0', +}); + +export const imageInner = style({ + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + zIndex: 2, +}); + +export const title = style({ + padding: Space * 2, + textAlign: 'left', + '@media': { + [`(min-width: ${BreakPoint.MEDIUM}px)`]: { + padding: Space * 3, + }, + }, +}); + +export const menu = style({ + position: 'absolute', + top: Space * 0.5, + right: Space * 0.5, + zIndex: 10, +}); diff --git a/src/components/Card/Card.stories.tsx b/src/components/Card/Card.stories.tsx index 5ded4f1..5a01bc3 100644 --- a/src/components/Card/Card.stories.tsx +++ b/src/components/Card/Card.stories.tsx @@ -27,6 +27,15 @@ export default { export const Overview: Story = {}; +export const WithLongName: Story = { + args: { + entity: { + ...defaultEntity, + name: 'abcdef'.repeat(20), + }, + }, +}; + export const WithNew: Story = { args: { entity: { diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index e260761..c3cc939 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -1,95 +1,16 @@ -import React, { useCallback } from 'react'; -import styled from 'styled-components'; import * as clipboard from 'clipboard-polyfill'; -import { - Space, - Shadow, - Focus, - BreakPoint, - Typography, - Color, - Duration, - Easing, -} from '../../styles/variables'; +import React, { useCallback, useRef, useState } from 'react'; import type { RegEntity } from '../../types/reg'; -import { Image } from '../Image'; -import { Ellipsis } from '../internal/Ellipsis'; import { IconButton } from '../IconButton'; -import { MoreIcon } from '../icons/MoreIcon'; -import { Sign } from '../Sign'; +import { Image } from '../Image'; import { Menu } from '../Menu'; -import { Transparent } from '../internal/Transparent'; +import { Sign } from '../Sign'; +import { MoreIcon } from '../icons/MoreIcon'; import { BaseButton } from '../internal/BaseButton'; - -const Wrapper = styled.div` - position: relative; -`; - -const Inner = styled(BaseButton)` - display: block; - width: 100%; - border: none; - border-radius: 6px; - background: ${Color.WHITE}; - box-shadow: ${Shadow.LEVEL3}; - color: ${Color.TEXT_BASE}; - font-size: inherit; - text-decoration: none; - cursor: pointer; - transition: box-shadow ${Duration.FADE_IN}ms ${Easing.STANDARD}; - - &:hover { - box-shadow: ${Shadow.LEVEL1}; - } - - &:focus { - outline: none; - } - - &:focus-visible { - box-shadow: ${Focus}; - } -`; - -const CardSign = styled.div` - position: absolute; - top: ${Space * 1}px; - left: ${Space * 1}px; - z-index: 10; -`; - -const CardImage = styled.div` - position: relative; - overflow: hidden; - height: 260px; - border-radius: 6px 6px 0 0; -`; - -const CardImageInner = styled.span` - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 2; -`; - -const CardText = styled.div` - ${Typography.SUBTITLE3}; - padding: ${Space * 2}px; - text-align: left; - - @media (min-width: ${BreakPoint.MEDIUM}px) { - padding: ${Space * 3}px; - } -`; - -const CardMenu = styled.div` - position: absolute; - top: ${Space * 0.5}px; - right: ${Space * 0.5}px; - z-index: 10; -`; +import { Ellipsis } from '../internal/Ellipsis'; +import { Transparent } from '../internal/Transparent'; +import { Color } from '../../styles/variables.css'; +import * as styles from './Card.css'; const imageSrc = (entity: RegEntity) => { switch (entity.variant) { @@ -111,8 +32,8 @@ export type Props = { }; export const Card = ({ href, entity, menus, onCopy }: Props) => { - const anchor = React.useRef(null); - const [open, setOpen] = React.useState(false); + const anchor = useRef(null); + const [open, setOpen] = useState(false); const handleMenuOpen = useCallback( (e: React.MouseEvent) => { @@ -144,14 +65,18 @@ export const Card = ({ href, entity, menus, onCopy }: Props) => { ); return ( - - - +
+ +
- +
- - +
+ { height="100%" fit="scale-down" /> - + - +
- +
{entity.name} - - +
+
- +
+ { Open + Copy Link + {menus.map(({ label, href }, i) => ( {label} ))} - - +
+
); }; diff --git a/src/components/ChoiceGroup/ChoiceGroup.css.ts b/src/components/ChoiceGroup/ChoiceGroup.css.ts new file mode 100644 index 0000000..5af1dd8 --- /dev/null +++ b/src/components/ChoiceGroup/ChoiceGroup.css.ts @@ -0,0 +1,19 @@ +import { style } from '@vanilla-extract/css'; +import { BreakPoint, Space, tokens } from '../../styles/variables.css'; + +export const wrapper = style({ + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(48px, 1fr))', + gridGap: Space * 0.5, + margin: 0, + padding: Space * 0.5, + borderRadius: '26px', + background: tokens.color.white, + boxShadow: tokens.shadow.lv2, + listStyle: 'none', + '@media': { + [`(min-width: ${BreakPoint.SMALL}px)`]: { + gridTemplateColumns: 'repeat(auto-fill, minmax(80px, 1fr))', + }, + }, +}); diff --git a/src/components/ChoiceGroup/ChoiceGroup.tsx b/src/components/ChoiceGroup/ChoiceGroup.tsx index cf1c266..cb12eec 100644 --- a/src/components/ChoiceGroup/ChoiceGroup.tsx +++ b/src/components/ChoiceGroup/ChoiceGroup.tsx @@ -1,27 +1,15 @@ -import React, { createRef, useEffect, useMemo, useRef } from 'react'; -import styled from 'styled-components'; +import React, { + createRef, + useCallback, + useEffect, + useMemo, + useRef, +} from 'react'; import { useMousetrap } from '../../hooks/useMousetrap'; -import { Space, Shadow, Color, BreakPoint } from '../../styles/variables'; +import type { Modify } from '../../utils/types'; +import * as styles from './ChoiceGroup.css'; import { ChoiceButton } from './internal/ChoiceButton'; -const List = styled.ul` - display: grid; - grid-template-columns: repeat(auto-fit, minmax(48px, 1fr)); - grid-gap: ${Space * 0.5}px; - margin: 0; - padding: ${Space * 0.5}px; - border-radius: 26px; - background: ${Color.WHITE}; - box-shadow: ${Shadow.LEVEL2}; - list-style: none; - - @media (min-width: ${BreakPoint.SMALL}px) { - grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); - } -`; - -const ListItem = styled.li``; - const getValueIndex = (options: ChoiceOption[], value: string) => { return options.findIndex((opts) => opts.value === value); }; @@ -31,11 +19,14 @@ export type ChoiceOption = { label: React.ReactNode; }; -export type Props = Omit, 'onChange'> & { - value: string; - options: ChoiceOption[]; - onChange: (value: string, index: number) => void; -}; +export type Props = Modify< + React.ComponentPropsWithoutRef<'ul'>, + { + value: string; + options: ChoiceOption[]; + onChange: (value: string, index: number) => void; + } +>; export const ChoiceGroup = ({ value, options, onChange, ...rest }: Props) => { const rootRef = useRef(null); @@ -47,7 +38,7 @@ export const ChoiceGroup = ({ value, options, onChange, ...rest }: Props) => { ); }, [options]); - const handleItemClick = React.useCallback( + const handleItemClick = useCallback( (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); @@ -110,9 +101,9 @@ export const ChoiceGroup = ({ value, options, onChange, ...rest }: Props) => { ); return ( - +
    {options.map((opts, index) => ( - +
  • { > {opts.label} - +
  • ))} - +
); }; diff --git a/src/components/ChoiceGroup/internal/ChoiceButton/ChoiceButton.css.ts b/src/components/ChoiceGroup/internal/ChoiceButton/ChoiceButton.css.ts new file mode 100644 index 0000000..4d69918 --- /dev/null +++ b/src/components/ChoiceGroup/internal/ChoiceButton/ChoiceButton.css.ts @@ -0,0 +1,73 @@ +import { style, styleVariants } from '@vanilla-extract/css'; +import { + BreakPoint, + Space, + tokens, + typography, +} from '../../../../styles/variables.css'; + +const SIZE = 44; + +const wrapperBase = style([ + typography.button, + { + position: 'relative', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '100%', + height: `${SIZE}px`, + padding: `0 ${Space * 1}px`, + borderWidth: 0, + borderRadius: `${SIZE}px`, + background: 'transparent', + textAlign: 'center', + color: tokens.color.textBase, + cursor: 'pointer', + userSelect: 'auto', + '::before': { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + zIndex: 0, + display: 'block', + borderRadius: `${SIZE}px`, + background: tokens.color.brandPrimary, + content: '""', + opacity: 0, + transition: `all ${tokens.duration.smallOut} ${tokens.easing.back}`, + transform: 'scale(0.9)', + }, + ':hover': { + background: tokens.color.hoverBlack, + }, + '@media': { + [`(min-width: ${BreakPoint.SMALL}px)`]: { + padding: `0 ${Space * 2}px`, + }, + }, + }, +]); + +export const wrapper = styleVariants({ + default: [wrapperBase, {}], + active: [ + wrapperBase, + { + color: tokens.color.white, + cursor: 'default', + userSelect: 'none', + '::before': { + opacity: 1, + transform: 'scale(1)', + }, + }, + ], +}); + +export const inner = style({ + position: 'relative', + zIndex: 1, +}); diff --git a/src/components/ChoiceGroup/internal/ChoiceButton/ChoiceButton.tsx b/src/components/ChoiceGroup/internal/ChoiceButton/ChoiceButton.tsx index ddbef8c..407c91a 100644 --- a/src/components/ChoiceGroup/internal/ChoiceButton/ChoiceButton.tsx +++ b/src/components/ChoiceGroup/internal/ChoiceButton/ChoiceButton.tsx @@ -1,78 +1,29 @@ +import { clsx } from 'clsx'; import React, { forwardRef } from 'react'; -import styled from 'styled-components'; -import { - Space, - Duration, - Easing, - Typography, - Color, - BreakPoint, -} from '../../../../styles/variables'; +import type { Modify } from '../../../../utils/types'; import type { Props as BaseButtonProps } from '../../../internal/BaseButton'; import { BaseButton } from '../../../internal/BaseButton'; +import * as styles from './ChoiceButton.css'; -const SIZE = 44; - -const Wrapper = styled(BaseButton)<{ active: boolean }>` - ${Typography.BUTTON}; - position: relative; - display: flex; - justify-content: center; - align-items: center; - width: 100%; - height: ${SIZE}px; - padding: 0 ${Space * 1}px; - border: none; - border-radius: ${SIZE}px; - background: transparent; - color: ${({ active }) => (active ? Color.WHITE : Color.TEXT_BASE)}; - text-align: center; - cursor: ${({ active }) => (active ? 'default' : 'pointer')}; - user-select: ${({ active }) => (active ? 'none' : 'auto')}; - - & > span { - position: relative; - z-index: 1; - } - - &::before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 0; - display: block; - border-radius: ${SIZE}px; - background: ${Color.BRAND_PRIMARY}; - opacity: ${({ active }) => (active ? '1' : '0')}; - transition: all ${Duration.SMALL_OUT}ms ${Easing.BACK}; - transform: ${({ active }) => (active ? 'scale(1)' : 'scale(0.9)')}; - content: ''; - } - - &:hover { - background-color: ${Color.HOVER_BLACK}; - } - - @media (min-width: ${BreakPoint.SMALL}px) { - padding: 0 ${Space * 2}px; +export type Props = Modify< + BaseButtonProps, + { + active?: boolean; } -`; - -export type Props = BaseButtonProps & { - active?: boolean; -}; +>; export const ChoiceButton = forwardRef< HTMLButtonElement | HTMLAnchorElement, Props ->(({ active, children, ...rest }, ref) => ( - - {children} - +>(({ active = false, children, ...rest }, ref) => ( + + {children} + )); - -ChoiceButton.defaultProps = { - active: false, -}; diff --git a/src/components/Container/Container.css.ts b/src/components/Container/Container.css.ts new file mode 100644 index 0000000..f5ab24f --- /dev/null +++ b/src/components/Container/Container.css.ts @@ -0,0 +1,13 @@ +import { style } from '@vanilla-extract/css'; +import { BreakPoint, Space } from '../../styles/variables.css'; + +export const wrapper = style({ + paddingRight: `${Space * 3}px`, + paddingLeft: `${Space * 3}px`, + '@media': { + [`(min-width: ${BreakPoint.MEDIUM}px)`]: { + paddingRight: `${Space * 5}px`, + paddingLeft: `${Space * 5}px`, + }, + }, +}); diff --git a/src/components/Container/Container.tsx b/src/components/Container/Container.tsx index f5c5481..fd999ed 100644 --- a/src/components/Container/Container.tsx +++ b/src/components/Container/Container.tsx @@ -1,21 +1,16 @@ import React from 'react'; -import styled from 'styled-components'; -import { Space, BreakPoint } from '../../styles/variables'; +import type { Modify } from '../../utils/types'; +import * as styles from './Container.css'; -const Wrapper = styled.div` - padding-right: ${Space * 3}px; - padding-left: ${Space * 3}px; - - @media (min-width: ${BreakPoint.MEDIUM}px) { - padding-right: ${Space * 5}px; - padding-left: ${Space * 5}px; +export type Props = Modify< + React.HTMLAttributes, + { + children: React.ReactNode; } -`; - -export type Props = React.HTMLAttributes & { - children: React.ReactNode; -}; +>; export const Container = ({ children, ...rest }: Props) => ( - {children} +
+ {children} +
); diff --git a/src/components/Dialog/Dialog.css.ts b/src/components/Dialog/Dialog.css.ts new file mode 100644 index 0000000..77163a9 --- /dev/null +++ b/src/components/Dialog/Dialog.css.ts @@ -0,0 +1,138 @@ +import { globalStyle, style } from '@vanilla-extract/css'; +import { BreakPoint, Space, tokens } from '../../styles/variables.css'; + +export const delay = { + enter: 120, + exit: 160, +}; + +export const wrapper = style({ + position: 'fixed', + top: 0, + right: 0, + bottom: 0, + left: 0, + zIndex: tokens.depth.dialog, + transitionProperty: 'opacity', + transitionTimingFunction: 'ease-out', + selectors: { + '&.dialog-enter': { + opacity: 0, + }, + '&.dialog-enter-active': { + transitionDuration: tokens.duration.fadeIn, + opacity: 1, + }, + '&.dialog-exit': { + opacity: 1, + }, + '&.dialog-exit-active': { + transitionDuration: tokens.duration.fadeOut, + transitionDelay: `${delay.exit}ms`, + opacity: 0, + }, + }, +}); + +export const body = style({ + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + zIndex: 2, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + overflowX: 'hidden', + overflowY: 'auto', + WebkitOverflowScrolling: 'touch', + padding: '60px 0 30px', + backfaceVisibility: 'hidden', +}); + +export const inner = style({ + position: 'relative', + zIndex: 2, + width: '100%', + margin: `auto ${Space * 2}px`, + '@media': { + [`(min-width: ${BreakPoint.MEDIUM}px)`]: { + minWidth: 600, + maxWidth: 1000, + width: 'auto', + }, + }, +}); + +export const content = style({ + position: 'relative', + marginBottom: Space * 3, + padding: Space * 5, + borderRadius: 4, + boxShadow: tokens.shadow.lv2, + transitionProperty: 'opacity, transform', + WebkitTapHighlightColor: 'transparent', + ':focus': { + outline: 'none', + }, + ':focus-visible': { + boxShadow: tokens.state.focus, + }, + selectors: { + '.dialog-enter &': { + transform: 'scale(1.03)', + opacity: 0, + }, + '.dialog-enter-active &': { + transitionDuration: tokens.duration.fadeIn, + transitionDelay: `${delay.enter}ms`, + transform: 'scale(1)', + opacity: 1, + }, + '.dialog-exit &': { + opacity: 1, + }, + '.dialog-exit-active &': { + transitionDuration: tokens.duration.fadeOut, + opacity: 0, + }, + }, +}); + +globalStyle(`${content} > *`, { + WebkitTapHighlightColor: 'initial', +}); + +globalStyle(`${content} > div > *:last-child`, { + marginBottom: '0 !important', +}); + +export const heading = style({ + margin: `0 0 ${Space * 5}px`, +}); + +export const close = style({ + position: 'absolute', + top: Space * 2, + right: Space * 2, +}); + +export const backdrop = style({ + position: 'fixed', + top: 0, + right: 0, + bottom: 0, + left: 0, + zIndex: 1, + display: 'block', + margin: 0, + padding: 0, + width: '100%', + height: '100%', + borderWidth: 0, + background: 'rgba(255, 255, 255, 0.9)', + willChange: 'opacity', + transitionTimingFunction: tokens.easing.standard, + transitionProperty: 'opacity', +}); diff --git a/src/components/Dialog/Dialog.stories.tsx b/src/components/Dialog/Dialog.stories.tsx index b6b1fe2..4d677d8 100644 --- a/src/components/Dialog/Dialog.stories.tsx +++ b/src/components/Dialog/Dialog.stories.tsx @@ -37,7 +37,7 @@ export const Overview: Story = { setOpen(false); }} > - Dialog content +

Dialog content

); diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index e246bda..8e87469 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -1,176 +1,27 @@ -import React, { useRef, useCallback, useEffect, useState } from 'react'; -import { CSSTransition } from 'react-transition-group'; -import styled from 'styled-components'; -import { enableBodyScroll, disableBodyScroll } from 'body-scroll-lock'; +import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock'; import type { FocusTrap } from 'focus-trap'; import { createFocusTrap } from 'focus-trap'; -import { - Space, - Duration, - Depth, - Easing, - Focus, - BreakPoint, - Shadow, - Color, -} from '../../styles/variables'; -import { Portal } from '../internal/Portal'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { CSSTransition } from 'react-transition-group'; +import { useMousetrap } from '../../hooks/useMousetrap'; +import type { Modify } from '../../utils/types'; import { IconButton } from '../IconButton'; import { CloseIcon } from '../icons/CloseIcon'; -import { useMousetrap } from '../../hooks/useMousetrap'; - -const Delay = { - ENTER: 120, - EXIT: 160, -}; +import { Portal } from '../internal/Portal'; +import { Color, Duration } from '../../styles/variables.css'; +import * as styles from './Dialog.css'; const allowOutsideClick = () => true; -const Wrapper = styled.div` - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: ${Depth.DIALOG}; - transition-property: opacity; - transition-timing-function: ease-out; - - &.dialog-enter { - opacity: 0; - transition-duration: ${Duration.FADE_IN}ms; - } - - &.dialog-enter-active { - opacity: 1; - } - - &.dialog-exit { - opacity: 1; - transition-duration: ${Duration.FADE_OUT}ms; - transition-delay: ${Delay.EXIT}ms; - } - - &.dialog-exit-active { - opacity: 0; - } -`; - -const Body = styled.div` - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 2; - display: flex; - justify-content: center; - align-items: center; - overflow-x: hidden; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - padding: 60px 0 30px; - backface-visibility: hidden; -`; - -const Inner = styled.div` - position: relative; - z-index: 2; - width: 100%; - margin: auto ${Space * 2}px; - - @media (min-width: ${BreakPoint.MEDIUM}px) { - min-width: 600px; - max-width: 1000px; - width: auto; - } -`; - -const Content = styled.div` - position: relative; - margin-bottom: ${Space * 3}px; - padding: ${Space * 5}px; - background: ${Color.WHITE}; - border-radius: 4px; - box-shadow: ${Shadow.LEVEL2}; - transition-property: opacity, transform; - -webkit-tap-highlight-color: transparent; - - &:focus { - outline: none; - } - - &:focus-visible { - box-shadow: ${Focus}; +export type Props = Modify< + React.ComponentPropsWithoutRef<'div'>, + { + id: string; + open: boolean; + title: React.ReactNode; + onRequestClose: () => void; } - - & > * { - -webkit-tap-highlight-color: initial; - } - - & > div { - & > h2:first-child { - margin: 0 0 ${Space * 5}px; - } - - & > *:last-child { - margin-bottom: 0 !important; - } - } - - .dialog-enter & { - opacity: 0; - transform: scale(1.03); - transition-duration: ${Duration.FADE_IN}ms; - transition-delay: ${Delay.ENTER}ms; - } - - .dialog-enter-active & { - opacity: 1; - transform: scale(1); - } - - .dialog-exit & { - opacity: 1; - transition-duration: ${Duration.FADE_OUT}ms; - } - - .dialog-exit-active & { - opacity: 0; - } -`; - -const Close = styled.div` - position: absolute; - top: ${Space * 2}px; - right: ${Space * 2}px; -`; - -const Backdrop = styled.button` - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - display: block; - margin: 0; - padding: 0; - width: 100%; - height: 100%; - border: none; - background: rgba(255, 255, 255, 0.9); - will-change: opacity; - transition-timing-function: ${Easing.STANDARD}; - transition-property: opacity; -`; - -export type Props = Omit, 'id'> & { - id: string; - open: boolean; - title: React.ReactNode; - onRequestClose: () => void; -}; +>; export const Dialog = ({ id, @@ -251,22 +102,23 @@ export const Dialog = ({ return ( - - - - +
+
+ +
- - - +
+
); diff --git a/src/components/Footer/Footer.css.ts b/src/components/Footer/Footer.css.ts new file mode 100644 index 0000000..b14ba62 --- /dev/null +++ b/src/components/Footer/Footer.css.ts @@ -0,0 +1,22 @@ +import { style } from '@vanilla-extract/css'; +import { Space, tokens, typography } from '../../styles/variables.css'; + +export const wrapper = style({ + padding: `${Space * 18}px 0 ${Space * 15}px`, + textAlign: 'center', +}); + +export const label = style([ + typography.body2, + { + margin: `${Space * 2}px 0 0`, + }, +]); + +export const link = style({ + color: tokens.color.textLink, + textDecoration: 'none', + ':hover': { + textDecoration: 'underline', + }, +}); diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx index 91f6b3e..943c3aa 100644 --- a/src/components/Footer/Footer.tsx +++ b/src/components/Footer/Footer.tsx @@ -1,37 +1,20 @@ import React from 'react'; -import styled from 'styled-components'; -import { Space, Typography, Color } from '../../styles/variables'; -import { Logo } from '../Logo'; import { Container } from '../Container'; - -const Wrapper = styled.footer` - padding: ${Space * 18}px 0 ${Space * 15}px; - text-align: center; - - & p { - ${Typography.BODY2}; - margin: ${Space * 2}px 0 0; - } - - & a { - color: ${Color.TEXT_LINK}; - text-decoration: none; - - &:hover { - text-decoration: underline; - } - } -`; +import { Logo } from '../Logo'; +import * as styles from './Footer.css'; export type Props = {}; export const Footer = () => ( - + ); diff --git a/src/components/Header/Header.css.ts b/src/components/Header/Header.css.ts new file mode 100644 index 0000000..f116bf0 --- /dev/null +++ b/src/components/Header/Header.css.ts @@ -0,0 +1,77 @@ +import { style } from '@vanilla-extract/css'; +import { + BreakPoint, + Space, + tokens, + typography, +} from '../../styles/variables.css'; + +export const wrapper = style({ + display: 'grid', + gridTemplateColumns: '1fr auto', + gridGap: Space * 1, + height: tokens.size.headerHeight, + background: tokens.color.white, + boxShadow: tokens.shadow.lv1, + '@media': { + [`(min-width: ${BreakPoint.SMALL}px)`]: { + gridTemplateColumns: '2fr 1fr 2fr', + }, + }, +}); + +export const left = style({ + alignSelf: 'center', + overflow: 'hidden', + paddingLeft: Space * 2, +}); + +export const center = style([ + typography.body2, + { + display: 'none', + alignSelf: 'center', + textAlign: 'center', + '@media': { + [`(min-width: ${BreakPoint.SMALL}px)`]: { + display: 'block', + }, + }, + }, +]); + +export const right = style({ + display: 'flex', + flexDirection: 'row-reverse', + justifyContent: 'flex-start', + alignItems: 'center', + alignSelf: 'center', + paddingRight: Space * 2, + textAlign: 'right', +}); + +export const title = style([ + typography.subTitle2, + { + display: 'flex', + alignItems: 'center', + margin: 0, + }, +]); + +export const titleSign = style({ + flex: '0 0 auto', + marginLeft: Space * 1, +}); + +export const titleText = style({ + display: 'block', + flex: '0 1 auto', + overflow: 'hidden', + marginLeft: Space * 1, +}); + +export const markersToggele = style({ + marginRight: Space * 1, + lineHeight: 0, +}); diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index c5acbf1..5915752 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,84 +1,13 @@ import React, { useCallback } from 'react'; -import styled from 'styled-components'; -import { - Space, - Shadow, - Size, - BreakPoint, - Typography, - Color, -} from '../../styles/variables'; -import { CloseIcon } from '../icons/CloseIcon'; -import { IconButton } from '../IconButton'; +import { useMedia } from '../../hooks/useMedia'; import type { RegVariant } from '../../types/reg'; +import { IconButton } from '../IconButton'; import { Sign } from '../Sign'; -import { Ellipsis } from '../internal/Ellipsis'; import { Switch } from '../Switch'; -import { useMedia } from '../../hooks/useMedia'; - -const Wrapper = styled.header` - display: grid; - grid-template-columns: 1fr auto; - grid-gap: ${Space * 1}px; - height: ${Size.HEADER_HEIGHT}px; - background: ${Color.WHITE}; - box-shadow: ${Shadow.LEVEL1}; - - @media (min-width: ${BreakPoint.SMALL}px) { - grid-template-columns: 2fr 1fr 2fr; - } -`; - -const Left = styled.div` - align-self: center; - overflow: hidden; - padding-left: ${Space * 2}px; -`; - -const Center = styled.div` - display: none; - - @media (min-width: ${BreakPoint.SMALL}px) { - ${Typography.BODY2}; - display: block; - align-self: center; - text-align: center; - } -`; - -const Right = styled.div` - display: flex; - flex-direction: row-reverse; - justify-content: flex-start; - align-items: center; - align-self: center; - padding-right: ${Space * 2}px; - text-align: right; -`; - -const Title = styled.h2` - ${Typography.SUBTITLE2}; - display: flex; - align-items: center; - margin: 0; -`; - -const TitleSign = styled.span` - flex: 0 0 auto; - margin-left: ${Space * 1}px; -`; - -const TitleText = styled.span` - display: block; - flex: 0 1 auto; - overflow: hidden; - margin-left: ${Space * 1}px; -`; - -const MarkersToggle = styled.div` - margin-right: ${Space * 1}px; - line-height: 0; -`; +import { CloseIcon } from '../icons/CloseIcon'; +import { Ellipsis } from '../internal/Ellipsis'; +import { BreakPoint, Color } from '../../styles/variables.css'; +import * as styles from './Header.css'; export type Props = { variant: RegVariant; @@ -117,36 +46,36 @@ export const Header = ({ ); return ( - - - - <TitleSign> + <header className={styles.wrapper}> + <div className={styles.left}> + <h2 className={styles.title} title={title}> + <span className={styles.titleSign}> <Sign variant={variant} /> - </TitleSign> - <TitleText> + </span> + <span className={styles.titleText}> <Ellipsis line={2}>{title}</Ellipsis> - </TitleText> - - + + + -
+
{current} / {max} -
+ - +
- +
- - - +
+
+ ); }; diff --git a/src/components/HelpDialog/HelpDialog.css.ts b/src/components/HelpDialog/HelpDialog.css.ts new file mode 100644 index 0000000..86f63a4 --- /dev/null +++ b/src/components/HelpDialog/HelpDialog.css.ts @@ -0,0 +1,50 @@ +import { style } from '@vanilla-extract/css'; +import { Space, tokens, typography } from '../../styles/variables.css'; + +export const wrapper = style({ + paddingBottom: Space * 1, +}); + +export const table = style({ + width: '100%', + borderCollapse: 'collapse', +}); + +export const headerCell = style([ + typography.subTitle2, + { + padding: `${Space * 4}px 0 ${Space * 1}px`, + textAlign: 'left', + selectors: { + 'tr:first-child &': { + paddingTop: 0, + }, + }, + }, +]); + +export const dataCell = style([ + typography.body2, + { + padding: `${Space * 1}px 0`, + borderBottom: `1px solid ${tokens.color.border}`, + textAlign: 'left', + }, +]); + +export const key = style({ + display: 'inline-block', + padding: '8px 14px', + borderRadius: '3px', + border: '1px solid #f4f4f4', + background: 'linear-gradient(180deg, #f3f3f3 0%, #ececec 100%)', + boxShadow: '0px 2px 0px rgba(0, 0, 0, 0.25)', + fontWeight: 'bold', + fontFamily: tokens.fontFamily.monospace, + lineHeight: 1, + selectors: { + '& + &': { + marginLeft: '0.5em', + }, + }, +}); diff --git a/src/components/HelpDialog/HelpDialog.tsx b/src/components/HelpDialog/HelpDialog.tsx index 78324f3..8ff077a 100644 --- a/src/components/HelpDialog/HelpDialog.tsx +++ b/src/components/HelpDialog/HelpDialog.tsx @@ -1,54 +1,13 @@ import React from 'react'; -import styled from 'styled-components'; -import { Space, FontFamily, Typography, Color } from '../../styles/variables'; import type { Props as DialogProps } from '../Dialog'; import { Dialog } from '../Dialog'; - -const Wrapper = styled.div` - padding-bottom: ${Space * 1}px; - - & table { - width: 100%; - border-collapse: collapse; - - & tr:first-child th { - padding-top: 0; - } - - & th { - ${Typography.SUBTITLE2}; - padding: ${Space * 4}px 0 ${Space * 1}px; - text-align: left; - } - - & td { - ${Typography.BODY2}; - padding: ${Space * 1}px 0; - border-bottom: 1px solid ${Color.BORDER}; - text-align: left; - } - } - - & kbd { - display: inline-block; - padding: 8px 14px; - border-radius: 3px; - border: 1px solid #f4f4f4; - background: linear-gradient(180deg, #f3f3f3 0%, #ececec 100%); - box-shadow: 0px 2px 0px rgba(0, 0, 0, 0.25); - font-weight: bold; - font-family: ${FontFamily.MONOSPACE}; - line-height: 1; - - & + kbd { - margin-left: 0.5em; - } - } -`; +import * as styles from './HelpDialog.css'; const Title = ({ children }: React.PropsWithChildren) => ( - {children} + + {children} + ); @@ -61,13 +20,13 @@ const Item = ({ }) => { return ( - {description} - + {description} + {shortcuts.map((key, i) => { if (Array.isArray(key)) { return key.map((k) => ( - {k} + {k} )); } @@ -75,7 +34,7 @@ const Item = ({ return ( {i > 0 && or } - {key} + {key} ); })} @@ -88,8 +47,8 @@ export type Props = Omit; export const HelpDialog = ({ ...rest }: Props) => ( - - +
+
Application @@ -109,6 +68,6 @@ export const HelpDialog = ({ ...rest }: Props) => (
-
+
); diff --git a/src/components/IconButton/IconButton.css.ts b/src/components/IconButton/IconButton.css.ts new file mode 100644 index 0000000..dac22c0 --- /dev/null +++ b/src/components/IconButton/IconButton.css.ts @@ -0,0 +1,32 @@ +import { style } from '@vanilla-extract/css'; +import { tokens } from '../../styles/variables.css'; + +const SIZE = 40; + +export const wrapper = style({ + display: 'inline-flex', + justifyContent: 'center', + alignItems: 'center', + width: SIZE, + height: SIZE, + margin: 0, + padding: 0, + borderWidth: 0, + borderRadius: '50%', + fontSize: 0, + lineHeight: 0, +}); + +export const wrapperLight = style({ + background: 'transparent', + ':hover': { + background: tokens.color.hoverBlack, + }, +}); + +export const wrapperDark = style({ + background: 'transparent', + ':hover': { + background: tokens.color.hoverWhite, + }, +}); diff --git a/src/components/IconButton/IconButton.stories.tsx b/src/components/IconButton/IconButton.stories.tsx index 8b3ae16..dfd877d 100644 --- a/src/components/IconButton/IconButton.stories.tsx +++ b/src/components/IconButton/IconButton.stories.tsx @@ -1,9 +1,9 @@ import type { Meta, StoryObj } from '@storybook/react'; import React from 'react'; -import { Color } from '../../styles/variables'; import { CloseIcon } from '../icons/CloseIcon'; import { HelpIcon } from '../icons/HelpIcon'; import { MoreIcon } from '../icons/MoreIcon'; +import { Color } from '../../styles/variables.css'; import { IconButton } from './'; type Component = typeof IconButton; diff --git a/src/components/IconButton/IconButton.tsx b/src/components/IconButton/IconButton.tsx index 28ef7e2..6d2e3aa 100644 --- a/src/components/IconButton/IconButton.tsx +++ b/src/components/IconButton/IconButton.tsx @@ -1,56 +1,29 @@ import React, { forwardRef } from 'react'; -import styled from 'styled-components'; -import { Color } from '../../styles/variables'; +import { clsx } from 'clsx'; +import type { Modify } from '../../utils/types'; import type { Props as BaseButtonProps } from '../internal/BaseButton'; import { BaseButton } from '../internal/BaseButton'; +import * as styles from './IconButton.css'; -const SIZE = 40; - -const variants = { - light: { - normal: 'transparent', - hover: Color.HOVER_BLACK, - }, - dark: { - normal: 'transparent', - hover: Color.HOVER_WHITE, - }, -}; - -type Variant = keyof typeof variants; - -const Wrapper = styled(BaseButton)<{ normal: string; hover: string }>` - display: inline-flex; - justify-content: center; - align-items: center; - width: ${SIZE}px; - height: ${SIZE}px; - margin: 0; - padding: 0; - border: none; - border-radius: 50%; - background: ${({ normal }) => normal}; - font-size: 0; - line-height: 0; - - &:hover { - background-color: ${({ hover }) => hover}; +export type Props = Modify< + BaseButtonProps, + { + variant?: 'light' | 'dark'; } -`; - -export type Props = BaseButtonProps & { - variant?: Variant; -}; +>; export const IconButton = forwardRef< HTMLButtonElement | HTMLAnchorElement, Props ->(({ variant, children, ...rest }, ref) => ( - +>(({ className, variant = 'light', children, ...rest }, ref) => ( + {children} - + )); - -IconButton.defaultProps = { - variant: 'light', -}; diff --git a/src/components/Image/Image.css.ts b/src/components/Image/Image.css.ts new file mode 100644 index 0000000..b57b946 --- /dev/null +++ b/src/components/Image/Image.css.ts @@ -0,0 +1,35 @@ +import { style, styleVariants } from '@vanilla-extract/css'; + +export const wrapper = style({ + position: 'relative', + display: 'grid', + justifyContent: 'center', + alignItems: 'center', +}); + +export const loading = style({ + position: 'absolute', + top: '50%', + left: '50%', + zIndex: 2, + lineHeight: 0, + transform: 'translate(-50%, -50%)', +}); + +const imageBase = style({ + position: 'relative', + zIndex: 1, + maxWidth: '100%', + verticalAlign: 'bottom', +}); + +export const image = styleVariants({ + default: [imageBase, {}], + full: [ + imageBase, + { + width: '100%', + height: '100%', + }, + ], +}); diff --git a/src/components/Image/Image.tsx b/src/components/Image/Image.tsx index 32d23a1..20968ee 100644 --- a/src/components/Image/Image.tsx +++ b/src/components/Image/Image.tsx @@ -1,8 +1,9 @@ -import React, { useState, useEffect, forwardRef, useRef } from 'react'; -import styled from 'styled-components'; +import { clsx } from 'clsx'; +import React, { forwardRef, useEffect, useRef, useState } from 'react'; import { useIntersection } from 'use-intersection'; -import { Spinner } from '../Spinner'; import { supportsLoading } from '../../supports'; +import { Spinner } from '../Spinner'; +import * as styles from './Image.css'; const srcCache = new Set(); @@ -18,37 +19,6 @@ const size2str = (size: SizeValue | undefined) => { return size; }; -const Wrapper = styled.span` - position: relative; - display: flex; - justify-content: center; - align-items: center; -`; - -const Loading = styled.span` - position: absolute; - top: 50%; - left: 50%; - z-index: 2; - line-height: 0; - transform: translate(-50%, -50%); -`; - -// FIXME Remove patch when `loading` is added to the type definition. -const Img = styled.img<{ - fit: ObjectFitValue | undefined; - full: boolean; - loading?: string; -}>` - position: relative; - z-index: 1; - max-width: 100%; - width: ${({ full }) => (full ? '100%' : undefined)}; - height: ${({ full }) => (full ? '100%' : undefined)}; - vertical-align: bottom; - object-fit: ${({ fit }) => fit}; -`; - type SizeValue = number | string; type ObjectFitValue = 'contain' | 'cover' | 'fill' | 'none' | 'scale-down'; @@ -89,29 +59,37 @@ const ImmediatelyImage = forwardRef( }; }, [loaded, src]); + const full = width != null && height != null; + return ( - - {!loaded && ( - + - + )} - + ); }, ); @@ -131,14 +109,10 @@ const LazyImage = forwardRef((props, ref) => { }); export const Image = forwardRef( - ({ lazy, ...rest }, ref) => + ({ lazy = false, ...rest }, ref) => lazy && !supportsLoading ? ( ) : ( ), ); - -Image.defaultProps = { - lazy: false, -}; diff --git a/src/components/List/Expandable.css.ts b/src/components/List/Expandable.css.ts new file mode 100644 index 0000000..fa6aed0 --- /dev/null +++ b/src/components/List/Expandable.css.ts @@ -0,0 +1,61 @@ +import { style, styleVariants } from '@vanilla-extract/css'; +import { Space, tokens, typography } from '../../styles/variables.css'; + +const buttonBase = style({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '100%', + height: 44, + paddingLeft: `calc(${Space * 2}px + ${Space * 2}px * var(--expandable-depth))`, + paddingRight: Space * 2, + background: 'transparent', + borderWidth: 0, + color: tokens.color.textBase, + textAlign: 'left', + ':hover': { + backgroundColor: tokens.color.hoverBlack, + }, +}); + +export const button = styleVariants({ + default: [buttonBase, typography.subTitle3, {}], + large: [buttonBase, typography.subTitle2, {}], +}); + +const arrowIconBase = style({ + marginRight: Space * 1, + lineHeight: 0, + transition: `transform ${tokens.duration.smallIn} ${tokens.easing.standard}`, +}); + +export const arrowIcon = styleVariants({ + close: [arrowIconBase, { transform: 'rotate(-180deg)' }], + open: [arrowIconBase, { transform: 'rotate(0deg)' }], +}); + +export const label = style({ + display: 'block', + flex: '1 1 auto', + overflow: 'hidden', +}); + +export const meta = style([ + typography.subHead, + { + marginLeft: Space * 1, + color: tokens.color.textSub, + whiteSpace: 'nowrap', + }, +]); + +export const icon = style({ + marginLeft: Space * 1, + lineHeight: 0, +}); + +export const innerList = style({ + margin: 0, + padding: 0, + listStyle: 'none', +}); diff --git a/src/components/List/Expandable.tsx b/src/components/List/Expandable.tsx index 8c3ff21..304cef8 100644 --- a/src/components/List/Expandable.tsx +++ b/src/components/List/Expandable.tsx @@ -1,66 +1,11 @@ -import React from 'react'; -import styled from 'styled-components'; -import { - Space, - Duration, - Easing, - Typography, - Color, -} from '../../styles/variables'; +import { clsx } from 'clsx'; +import React, { useCallback, useEffect, useState } from 'react'; +import { ArrowUpIcon } from '../icons/ArrowUpIcon'; import { BaseButton } from '../internal/BaseButton'; import { Collapse } from '../internal/Collapse'; -import { ArrowUpIcon } from '../icons/ArrowUpIcon'; import { Ellipsis } from '../internal/Ellipsis'; - -const Button = styled(BaseButton)<{ large: boolean; depth: number }>` - ${({ large }) => (large ? Typography.SUBTITLE2 : Typography.SUBTITLE3)}; - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - height: 44px; - padding-left: ${({ depth }) => Space * 2 + Space * 2 * depth}px; - padding-right: ${Space * 2}px; - background: transparent; - border: 0; - color: ${Color.TEXT_BASE}; - text-align: left; - - &:hover { - background-color: ${Color.HOVER_BLACK}; - } -`; - -const ArrowIcon = styled('span')<{ open: boolean }>` - margin-right: ${Space}px; - line-height: 0; - transition: transform ${Duration.SMALL_IN}ms ${Easing.STANDARD}; - transform: rotate(${({ open }) => (open ? '0deg' : '-180deg')}); -`; - -const Label = styled.span` - display: block; - flex: 1 1 auto; - overflow: hidden; -`; - -const Meta = styled.span` - ${Typography.SUBHEAD}; - margin-left: ${Space}px; - color: ${Color.TEXT_SUB}; - white-space: nowrap; -`; - -const Icon = styled.span` - margin-left: ${Space}px; - line-height: 0; -`; - -const InnerList = styled.ul` - margin: 0; - padding: 0; - list-style: none; -`; +import { Color, Duration } from '../../styles/variables.css'; +import * as styles from './Expandable.css'; export type Props = React.PropsWithChildren<{ large?: boolean; @@ -73,67 +18,77 @@ export type Props = React.PropsWithChildren<{ onChange?: (open: boolean) => void; }>; -type State = { - open: boolean; -}; - -export class Expandable extends React.Component { - public static defaultProps = { - large: false, - depth: 0, - }; - - public state = { - open: this.props.defaultOpen === true, - }; - - public static getDerivedStateFromProps(nextProps: Props, prevState: State) { - if (nextProps.open != null && nextProps.open !== prevState.open) { - return { open: nextProps.open }; +export const Expandable = ({ + open: openProp, + defaultOpen, + large, + depth = 0, + label, + meta, + icon, + children, + onChange, +}: Props) => { + const [open, setOpen] = useState(defaultOpen ?? false); + + const handleClick = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + + if (openProp == null) { + setOpen((prev) => !prev); + } + + if (onChange != null) { + onChange(!openProp); + } + }, + [openProp, onChange], + ); + + useEffect(() => { + if (openProp != null && openProp != open) { + setOpen(openProp); } - - return null; - } - - public render() { - const { large, depth, label, meta, icon, children } = this.props; - const { open } = this.state; - - return ( -
  • - - + + - {children} - -
  • - ); - } - - private handleClick = (e: React.MouseEvent) => { - e.preventDefault(); - - if (this.props.open == null) { - this.setState({ open: !this.state.open }); - } - - if (this.props.onChange != null) { - this.props.onChange(!this.props.open); - } - }; -} + + + + + {label} + + + {meta && {meta}} + {icon && {icon}} + + + +
      {children}
    +
    + + ); +}; diff --git a/src/components/List/Item.css.ts b/src/components/List/Item.css.ts new file mode 100644 index 0000000..0dc2418 --- /dev/null +++ b/src/components/List/Item.css.ts @@ -0,0 +1,19 @@ +import { style } from '@vanilla-extract/css'; +import { Space, tokens, typography } from '../../styles/variables.css'; + +export const link = style([ + typography.body2, + { + display: 'flex', + alignItems: 'center', + width: '100%', + height: 44, + paddingLeft: `calc(${Space * 2}px + ${Space * 2}px * var(--item-depth))`, + paddingRight: Space * 2, + color: tokens.color.textBase, + textAlign: 'left', + ':hover': { + backgroundColor: tokens.color.hoverBlack, + }, + }, +]); diff --git a/src/components/List/Item.tsx b/src/components/List/Item.tsx index 749dd30..607a910 100644 --- a/src/components/List/Item.tsx +++ b/src/components/List/Item.tsx @@ -1,38 +1,26 @@ import React from 'react'; -import styled from 'styled-components'; -import { Space, Typography, Color } from '../../styles/variables'; import type { Props as BaseButtonProps } from '../internal/BaseButton'; import { BaseButton } from '../internal/BaseButton'; import { Ellipsis } from '../internal/Ellipsis'; - -const LinkButton = styled(BaseButton)<{ depth: number }>` - ${Typography.BODY2}; - display: flex; - align-items: center; - width: 100%; - height: 44px; - padding-left: ${({ depth }) => Space * 2 + Space * 2 * depth}px; - padding-right: ${Space * 2}px; - color: ${Color.TEXT_BASE}; - text-align: left; - - &:hover { - background-color: ${Color.HOVER_BLACK}; - } -`; +import * as styles from './Item.css'; export type Props = BaseButtonProps & { depth?: number; }; -export const Item = ({ depth, href, children, ...rest }: Props) => ( +export const Item = ({ depth = 0, href, children, ...rest }: Props) => (
  • - + {children} - +
  • ); - -Item.defaultProps = { - depth: 0, -}; diff --git a/src/components/List/List.css.ts b/src/components/List/List.css.ts new file mode 100644 index 0000000..b16252e --- /dev/null +++ b/src/components/List/List.css.ts @@ -0,0 +1,17 @@ +import { style } from '@vanilla-extract/css'; +import { Space, tokens, typography } from '../../styles/variables.css'; + +export const list = style({ + margin: 0, + padding: 0, + listStyle: 'none', +}); + +export const header = style([ + typography.subHead, + { + marginBottom: Space * 0.5, + padding: `0 ${Space * 2}px`, + color: tokens.color.textSub, + }, +]); diff --git a/src/components/List/List.stories.tsx b/src/components/List/List.stories.tsx index ddef5b5..385f6e8 100644 --- a/src/components/List/List.stories.tsx +++ b/src/components/List/List.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; -import { Color } from '../../styles/variables'; +import { Color } from '../../styles/variables.css'; import { SignChangedIcon } from '../icons/SignChangedIcon'; import { List } from './'; diff --git a/src/components/List/List.tsx b/src/components/List/List.tsx index b284958..e5a2c06 100644 --- a/src/components/List/List.tsx +++ b/src/components/List/List.tsx @@ -1,33 +1,17 @@ import React from 'react'; -import styled from 'styled-components'; -import { Space, Typography, Color } from '../../styles/variables'; -import { Item } from './Item'; import { Expandable } from './Expandable'; - -const Wrapper = styled.div` - & > ul { - margin: 0; - padding: 0; - list-style: none; - } -`; - -const Header = styled.div` - ${Typography.SUBHEAD}; - margin-bottom: ${Space / 2}px; - padding: 0 ${Space * 2}px; - color: ${Color.TEXT_SUB}; -`; +import { Item } from './Item'; +import * as styles from './List.css'; export type Props = React.PropsWithChildren<{ header?: React.ReactNode; }>; export const List = ({ header, children, ...rest }: Props) => ( - - {header == null ? null :
    {header}
    } -
      {children}
    -
    +
    + {header == null ? null :
    {header}
    } +
      {children}
    +
    ); List.Item = Item; diff --git a/src/components/Logo/Logo.css.ts b/src/components/Logo/Logo.css.ts new file mode 100644 index 0000000..ea12078 --- /dev/null +++ b/src/components/Logo/Logo.css.ts @@ -0,0 +1,7 @@ +import { style } from '@vanilla-extract/css'; + +export const wrapper = style({ + display: 'inline-block', + fontSize: 0, + verticalAlign: 'bottom', +}); diff --git a/src/components/Logo/Logo.tsx b/src/components/Logo/Logo.tsx index 86bf0e7..7ccd0f1 100644 --- a/src/components/Logo/Logo.tsx +++ b/src/components/Logo/Logo.tsx @@ -1,18 +1,12 @@ import React from 'react'; -import styled from 'styled-components'; - -const Wrapper = styled.span` - display: inline-block; - font-size: 0; - vertical-align: bottom; -`; +import * as styles from './Logo.css'; export type Props = React.ComponentProps<'svg'> & { size?: number; }; -export const Logo = ({ size, ...rest }: Props) => ( - +export const Logo = ({ size = 24, ...rest }: Props) => ( + ( fill="#FF4438" /> - + ); - -Logo.defaultProps = { - size: 24, -}; diff --git a/src/components/Main/Main.css.ts b/src/components/Main/Main.css.ts new file mode 100644 index 0000000..642f514 --- /dev/null +++ b/src/components/Main/Main.css.ts @@ -0,0 +1,19 @@ +import { style } from '@vanilla-extract/css'; +import { Space, typography } from '../../styles/variables.css'; + +export const title = style([ + typography.title1, + { + margin: `124px 0 ${Space * 3}px`, + }, +]); + +export const sectionTitle = style([ + typography.title2, + { + margin: `${Space * 12}px 0 ${Space * 3}px`, + ':first-of-type': { + marginTop: Space * 8, + }, + }, +]); diff --git a/src/components/Main/Main.tsx b/src/components/Main/Main.tsx index 93198a2..1930567 100644 --- a/src/components/Main/Main.tsx +++ b/src/components/Main/Main.tsx @@ -1,11 +1,12 @@ import React, { useCallback } from 'react'; -import { Space, BreakPoint, Size } from '../../styles/variables'; -import type { RegVariant, RegEntity } from '../../types/reg'; -import { Container } from '../Container'; -import { Card } from '../Card'; import { EntityContainer } from '../../containers/entity/EntityContainer'; import { NotificationContainer } from '../../containers/notification/NotificationContainer'; +import { BreakPoint, Size, Space } from '../../styles/variables.css'; +import type { RegEntity, RegVariant } from '../../types/reg'; +import { Card } from '../Card'; +import { Container } from '../Container'; import { VGrid } from '../VGrid'; +import * as styles from './Main.css'; const titles: { [K in RegVariant]: string } = { new: 'NEW ITEMS', @@ -59,7 +60,9 @@ const Content = ({ return ( <> -

    {title}

    +

    + {title} +

    { return ( -

    REPORT DETAIL

    +

    REPORT DETAIL

    + {entities.filtering && entities.allItems.length === 0 ? ( <> -

    Not found

    +

    Not found

    No items found that match the text entered.
    diff --git a/src/components/Menu/Item.css.ts b/src/components/Menu/Item.css.ts new file mode 100644 index 0000000..bbee89d --- /dev/null +++ b/src/components/Menu/Item.css.ts @@ -0,0 +1,23 @@ +import { style } from '@vanilla-extract/css'; +import { Space, tokens, typography } from '../../styles/variables.css'; + +export const inner = style([ + typography.body2, + { + display: 'flex', + alignItems: 'center', + position: 'relative', + width: '100%', + padding: `${Space * 1}px ${Space * 2}px`, + borderWidth: 0, + background: tokens.color.white, + color: tokens.color.textBase, + textAlign: 'left', + ':hover': { + background: tokens.color.hoverBlack, + }, + ':focus': { + zIndex: 2, + }, + }, +]); diff --git a/src/components/Menu/Item.tsx b/src/components/Menu/Item.tsx index 3c13042..ce6b869 100644 --- a/src/components/Menu/Item.tsx +++ b/src/components/Menu/Item.tsx @@ -1,39 +1,17 @@ import React from 'react'; -import styled from 'styled-components'; import type { Props as BaseButtonProps } from '../internal/BaseButton'; import { BaseButton } from '../internal/BaseButton'; -import { Space, Typography, Color } from '../../styles/variables'; import { Ellipsis } from '../internal/Ellipsis'; - -const Inner = styled(BaseButton)` - ${Typography.BODY2}; - display: flex; - align-items: center; - position: relative; - width: 100%; - padding: ${Space * 1}px ${Space * 2}px; - border: 0; - background: ${Color.WHITE}; - color: ${Color.TEXT_BASE}; - text-align: left; - - &:hover { - background-color: ${Color.HOVER_BLACK}; - } - - &:focus { - z-index: 2; - } -`; +import * as styles from './Item.css'; export type Props = BaseButtonProps; export const Item = ({ children, ...rest }: Props) => { return (

  • - + {children} - +
  • ); }; diff --git a/src/components/Menu/Menu.css.ts b/src/components/Menu/Menu.css.ts new file mode 100644 index 0000000..75d581f --- /dev/null +++ b/src/components/Menu/Menu.css.ts @@ -0,0 +1,154 @@ +import { style, styleVariants } from '@vanilla-extract/css'; +import { Duration, Space, tokens } from '../../styles/variables.css'; + +export const placementOrigin = styleVariants({ + 'top-left': { + transformOrigin: 'bottom right', + }, + top: { + transformOrigin: 'bottom center', + }, + 'top-right': { + transformOrigin: 'bottom left', + }, + right: { + transformOrigin: 'center left', + }, + 'bottom-right': { + transformOrigin: 'top left', + }, + bottom: { + transformOrigin: 'top center', + }, + 'bottom-left': { + transformOrigin: 'top right', + }, + left: { + transformOrigin: 'center right', + }, +}); + +export const placementTranslate = styleVariants({ + 'top-left': { + transform: `translateY(${Space * 0.5}px)`, + }, + top: { + transform: `translateY(${Space * 0.5}px)`, + }, + 'top-right': { + transform: `translateY(${Space * 0.5}px)`, + }, + right: { + transform: `translateY(${Space * -0.5}px)`, + }, + 'bottom-right': { + transform: `translateY(${Space * -0.5}px)`, + }, + bottom: { + transform: `translateY(${Space * -0.5}px)`, + }, + 'bottom-left': { + transform: `translateY(${Space * -0.5}px)`, + }, + left: { + transform: `translateY(${Space * 0.5}px)`, + }, +}); + +export const wrapper = style({ + position: 'absolute', + zIndex: tokens.depth.menu, + transitionProperty: 'transform, opacity', + transitionTimingFunction: tokens.easing.standard, + selectors: { + '&.menu-enter': { + transform: 'scale(0.98)', + opacity: 0, + }, + '&.menu-enter-active': { + transitionDuration: tokens.duration.mediumIn, + transform: 'scale(1)', + opacity: 1, + }, + '&.menu-exit': { + transform: 'scale(1)', + opacity: 1, + }, + '&.menu-exit-active': { + transitionDuration: tokens.duration.mediumOut, + transform: 'scale(0.98)', + opacity: 0, + }, + }, +}); + +export const scale = style({ + minWidth: 200, + maxWidth: 320, + padding: `${Space * 1}px 0`, + borderRadius: 3, + background: tokens.color.white, + boxShadow: tokens.shadow.lv2, + transitionProperty: 'transform', + selectors: { + '.menu-enter &': { + transform: 'scale(0.6, 0.7)', + }, + '.menu-enter-active &': { + transitionDuration: `${Duration.MEDIUM_IN - Duration.SMALL_IN}ms`, + transform: 'scale(1, 1)', + }, + '.menu-exit &': { + transform: 'scale(1, 1)', + }, + '.menu-exit-active &': { + transitionDuration: `${Duration.MEDIUM_OUT - Duration.SMALL_OUT}ms`, + transform: 'scale(0.99, 0.97)', + }, + }, +}); + +export const opacity = style({ + transitionProperty: 'transform, opacity', + transitionTimingFunction: tokens.easing.standard, + selectors: { + '.menu-enter &': { + opacity: 0, + }, + '.menu-enter-active &': { + transitionDuration: `${Duration.MEDIUM_IN - Duration.SMALL_IN}ms`, + transitionDelay: `${(Duration.MEDIUM_IN - Duration.SMALL_IN) / 2}ms`, + transform: 'translateY(0)', + opacity: 1, + }, + '.menu-enter-done &': { + transform: 'translateY(0)', + }, + '.menu-exit &': { + opacity: 1, + transform: 'translateY(0)', + }, + '.menu-exit-active &': { + transitionDuration: `${Duration.MEDIUM_OUT - Duration.SMALL_OUT}ms`, + transform: 'translateY(0)', + opacity: 0, + }, + }, +}); + +export const list = style({ + margin: 0, + padding: 0, + listStyle: 'none', +}); + +export const backdrop = style({ + position: 'fixed', + zIndex: `calc(${tokens.depth.menu} - 1)`, + top: 0, + left: 0, + width: '100%', + height: '100%', + borderWidth: 0, + background: 'transparent', +}); diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index b89ca1c..4bb6afe 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -1,169 +1,18 @@ -import React, { useCallback, useEffect, useMemo, useRef } from 'react'; -import * as ReactDOM from 'react-dom'; -import styled from 'styled-components'; -import keycode from 'keycode'; -import CSSTransition from 'react-transition-group/CSSTransition'; +import { clsx } from 'clsx'; import debounce from 'debounce'; -import { Portal } from '../internal/Portal'; -import { - Space, - Duration, - Easing, - Depth, - Shadow, - Color, -} from '../../styles/variables'; -import { findFocusable } from '../../utils/selector'; +import keycode from 'keycode'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; +import { findDOMNode } from 'react-dom'; +import { CSSTransition } from 'react-transition-group'; +import { Duration } from '../../styles/variables.css'; import { tryFocus } from '../../utils/focus'; +import { findFocusable } from '../../utils/selector'; +import { Portal } from '../internal/Portal'; import { Item } from './Item'; +import * as styles from './Menu.css'; const RESIZE_DEBOUNCE_MS = 192; -const animations = { - 'top-left': { - origin: 'bottom right', - translate: `translateY(${Space * 0.5}px)`, - scale: 'scale(0.6, 0.7)', - }, - top: { - origin: 'bottom center', - translate: `translateY(${Space * 0.5}px)`, - scale: 'scale(0.6, 0.7)', - }, - 'top-right': { - origin: 'bottom left', - translate: `translateY(${Space * 0.5}px)`, - scale: 'scale(0.6, 0.7)', - }, - right: { - origin: 'center left', - translate: `translateX(${Space * 0.5 * -1}px)`, - scale: 'scale(0.6, 0.7)', - }, - 'bottom-right': { - origin: 'top left', - translate: `translateY(${Space * 0.5 * -1}px)`, - scale: 'scale(0.6, 0.7)', - }, - bottom: { - origin: 'top center', - translate: `translateY(${Space * 0.5 * -1}px)`, - scale: 'scale(0.6, 0.7)', - }, - 'bottom-left': { - origin: 'top right', - translate: `translateY(${Space * 0.5 * -1}px)`, - scale: 'scale(0.6, 0.7)', - }, - left: { - origin: 'center right', - translate: `translateX(${Space * 0.5}px)`, - scale: 'scale(0.6, 0.7)', - }, -}; - -const Wrapper = styled.div<{ placement: Placement }>` - position: absolute; - z-index: ${Depth.MENU}; - transition-property: transform, opacity; - transition-timing-function: ${Easing.STANDARD}; - transform-origin: ${({ placement }) => animations[placement].origin}; - - &.menu-enter { - transform: scale(0.98); - opacity: 0; - } - - &.menu-enter-active { - transition-duration: ${Duration.SMALL_IN}ms; - transform: scale(1); - opacity: 1; - } - - &.menu-exit { - opacity: 1; - } - - &.menu-exit-active { - transition-duration: ${Duration.SMALL_OUT}ms; - opacity: 0; - } -`; - -const Scale = styled.div<{ placement: Placement }>` - min-width: 200px; - max-width: 320px; - padding: ${Space * 1}px 0; - border-radius: 3px; - background: ${Color.WHITE}; - box-shadow: ${Shadow.LEVEL2}; - transition-property: transform; - transition-timing-function: ${Easing.STANDARD}; - transform-origin: ${({ placement }) => animations[placement].origin}; - - .menu-enter & { - transform: ${({ placement }) => animations[placement].scale}; - } - - .menu-enter-active & { - transition-duration: ${Duration.MEDIUM_IN - Duration.SMALL_IN}ms; - transform: scale(1, 1); - } - - .menu-exit & { - transform: scale(1, 1); - } - - .menu-exit-active & { - transition-duration: ${Duration.MEDIUM_OUT - Duration.SMALL_OUT}ms; - transform: scale(0.99, 0.97); - } -`; - -const Opacity = styled.div<{ placement: Placement }>` - transition-property: transform, opacity; - transition-timing-function: ${Easing.STANDARD}; - - .menu-enter & { - transform: ${({ placement }) => animations[placement].translate}; - opacity: 0; - } - - .menu-enter-active & { - transition-duration: ${Duration.MEDIUM_IN - Duration.SMALL_IN}ms; - transition-delay: ${(Duration.MEDIUM_IN - Duration.SMALL_IN) / 2}ms; - transform: translate(0, 0); - opacity: 1; - } - - .menu-exit & { - opacity: 1; - } - - .menu-exit-active & { - transition-duration: ${Duration.MEDIUM_OUT - Duration.SMALL_OUT}ms; - opacity: 0; - } -`; - -const List = styled.ul` - margin: 0; - padding: 0; - list-style: none; -`; - -const Backdrop = styled.button` - display: block; - position: fixed; - z-index: ${Depth.MENU - 1}; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: none; - background: transparent; -`; - export type Placement = | 'top-left' | 'top' @@ -210,9 +59,8 @@ export const Menu = ({ return; } - /* eslint-disable-next-line react/no-find-dom-node */ - const target = ReactDOM.findDOMNode(anchor.current) as HTMLElement | null; - if (target == null) { + const target = findDOMNode(anchor.current); + if (!(target instanceof HTMLElement)) { return; } @@ -222,21 +70,21 @@ export const Menu = ({ switch (placement) { case 'top-left': - top = window.pageYOffset + rect.bottom - wrapper.clientHeight; + top = window.scrollY + rect.bottom - wrapper.clientHeight; left = rect.left + target.clientWidth - wrapper.clientWidth; break; case 'top': - top = window.pageYOffset + rect.bottom - wrapper.clientHeight; + top = window.scrollY + rect.bottom - wrapper.clientHeight; left = rect.left + target.clientWidth / 2 - wrapper.clientWidth / 2; break; case 'top-right': - top = window.pageYOffset + rect.bottom - wrapper.clientHeight; + top = window.scrollY + rect.bottom - wrapper.clientHeight; left = rect.left; break; case 'right': top = - window.pageYOffset + + window.scrollY + rect.top + target.clientHeight / 2 - wrapper.clientHeight / 2; @@ -244,21 +92,21 @@ export const Menu = ({ break; case 'bottom-right': - top = window.pageYOffset + rect.top; + top = window.scrollY + rect.top; left = rect.left; break; case 'bottom': - top = window.pageYOffset + rect.top; + top = window.scrollY + rect.top; left = rect.left + target.clientWidth / 2 - wrapper.clientWidth / 2; break; case 'bottom-left': - top = window.pageYOffset + rect.top; + top = window.scrollY + rect.top; left = rect.left + target.clientWidth - wrapper.clientWidth; break; case 'left': top = - window.pageYOffset + + window.scrollY + rect.top + target.clientHeight / 2 - wrapper.clientHeight / 2; @@ -409,31 +257,41 @@ export const Menu = ({
    - - - - {children} - - - +
    +
    +
      {children}
    +
    +
    +
    {open && ( - { return ( - +
    { exit: Duration.FADE_OUT, }} > - {show && {message}} +
    + {show && {message}} +
    - +
    ); }; diff --git a/src/components/PoweredBy/PoweredBy.css.ts b/src/components/PoweredBy/PoweredBy.css.ts new file mode 100644 index 0000000..660e259 --- /dev/null +++ b/src/components/PoweredBy/PoweredBy.css.ts @@ -0,0 +1,33 @@ +import { style } from '@vanilla-extract/css'; +import { Space, tokens, typography } from '../../styles/variables.css'; + +export const button = style({ + display: 'flex', + alignItems: 'center', + padding: `${Space * 3}px ${Space * 2}px`, + minWidth: 280, + background: 'transparent', + ':hover': { + backgroundColor: tokens.color.hoverBlack, + }, +}); + +export const icon = style({ + marginRight: Space * 1, +}); + +export const title = style([ + typography.subTitle3, + { + margin: 0, + color: tokens.color.textBase, + }, +]); + +export const url = style([ + typography.body3, + { + margin: 0, + color: tokens.color.textSub, + }, +]); diff --git a/src/components/PoweredBy/PoweredBy.tsx b/src/components/PoweredBy/PoweredBy.tsx index 74eb7df..813b435 100644 --- a/src/components/PoweredBy/PoweredBy.tsx +++ b/src/components/PoweredBy/PoweredBy.tsx @@ -1,53 +1,22 @@ import React from 'react'; -import styled from 'styled-components'; -import { Space, Typography, Color } from '../../styles/variables'; import { Logo } from '../Logo'; import { BaseButton } from '../internal/BaseButton'; +import * as styles from './PoweredBy.css'; const REG_VIS_URL = 'https://github.com/reg-viz'; -const Button = styled(BaseButton)` - display: flex; - align-items: center; - padding: ${Space * 3}px ${Space * 2}px; - min-width: 280px; - background: transparent; - - &:hover { - background-color: ${Color.HOVER_BLACK}; - } -`; - -const Icon = styled.span` - margin-right: ${Space * 1}px; -`; - -const Text = styled.span` - & h3 { - ${Typography.SUBTITLE3}; - margin: 0; - color: ${Color.TEXT_BASE}; - } - - & p { - ${Typography.BODY3}; - margin: 0; - color: ${Color.TEXT_SUB}; - } -`; - export type Props = {}; export const PoweredBy = () => ( ); diff --git a/src/components/SearchBox/SearchBox.css.ts b/src/components/SearchBox/SearchBox.css.ts new file mode 100644 index 0000000..108ea4c --- /dev/null +++ b/src/components/SearchBox/SearchBox.css.ts @@ -0,0 +1,35 @@ +import { style } from '@vanilla-extract/css'; +import { Space, tokens } from '../../styles/variables.css'; + +export const wrapper = style({ + position: 'relative', +}); + +export const icon = style({ + position: 'absolute', + top: '50%', + left: 8, + display: 'block', + transform: 'translateY(-50%)', + lineHeight: 0, +}); + +export const input = style({ + position: 'relative', + display: 'block', + width: '100%', + height: 60, + padding: `${Space * 1}px ${Space * 1}px ${Space * 1}px ${Space * 7}px`, + borderWidth: 0, + borderBottom: `1px solid ${tokens.color.border}`, + background: 'transparent', + fontSize: 'inherit', + fontFamily: 'inherit', + transition: `border ${tokens.duration.smallOut} ${tokens.easing.standard}`, + ':focus': { + outline: 'none', + }, + ':focus-visible': { + borderBottomColor: tokens.color.brandPrimary, + }, +}); diff --git a/src/components/SearchBox/SearchBox.tsx b/src/components/SearchBox/SearchBox.tsx index 182ced2..d7114fa 100644 --- a/src/components/SearchBox/SearchBox.tsx +++ b/src/components/SearchBox/SearchBox.tsx @@ -1,43 +1,7 @@ import React from 'react'; -import styled from 'styled-components'; -import { Space, Duration, Easing, Color } from '../../styles/variables'; +import { Color } from '../../styles/variables.css'; import { SearchIcon } from '../icons/SearchIcon'; - -const Wrapper = styled.div` - position: relative; -`; - -const Icon = styled.span` - position: absolute; - top: 50%; - left: ${Space * 2}px; - display: block; - transform: translateY(-50%); - line-height: 0; -`; - -const TextField = styled.input` - position: relative; - display: block; - width: 100%; - height: 60px; - padding: ${Space * 1}px ${Space * 1}px ${Space * 1}px ${Space * 7}px; - border: none; - border-bottom: 1px solid ${Color.BORDER}; - border-radius: 0; - background: transparent; - font-size: inherit; - font-family: inherit; - transition: border ${Duration.SMALL_OUT}ms ${Easing.STANDARD}; - - &:focus { - outline: none; - } - - &:focus-visible { - border-bottom-color: ${Color.BRAND_PRIMARY}; - } -`; +import * as styles from './SearchBox.css'; export type Props = React.ComponentPropsWithoutRef<'input'> & { inputRef?: React.Ref; @@ -45,11 +9,11 @@ export type Props = React.ComponentPropsWithoutRef<'input'> & { export const SearchBox = ({ inputRef, children, ...rest }: Props) => { return ( - -