components#useTheme TypeScript Examples

The following examples show how to use components#useTheme. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: colors.tsx    From geist-ui with MIT License 6 votes vote down vote up
Colors: React.FC<React.PropsWithChildren<unknown>> = () => {
  const theme = useTheme()

  return (
    <div className="colors">
      <Grid.Container gap={1} pl={0} mr="10px">
        {types.map((type, index) => {
          return (
            <Grid xs={12} key={`${type}-${index}`}>
              <Card w="100%" type={type as CardTypes}>
                {type}
              </Card>
            </Grid>
          )
        })}
      </Grid.Container>
      <style jsx>{`
        .colors {
          display: flex;
          flex-wrap: wrap;
        }

        .color-card {
          display: flex;
          width: 9rem;
          margin-right: ${theme.layout.gapHalf};
          margin-bottom: ${theme.layout.gapHalf};
        }
      `}</style>
    </div>
  )
}
Example #2
Source File: playground.tsx    From geist-ui with MIT License 6 votes vote down vote up
Playground: React.FC<PlaygroundProps> = React.memo(
  ({
    title: inputTitle,
    code: inputCode,
    desc,
    scope,
  }: PlaygroundProps & typeof defaultProps) => {
    const theme = useTheme()
    const { isChinese } = useConfigs()
    const code = inputCode.trim()
    const title = inputTitle || (isChinese ? '基础的' : 'General')

    return (
      <>
        <Title title={title} desc={desc} />
        <div className="playground">
          <DynamicLive code={code} scope={scope} />
          <style jsx>{`
            .playground {
              width: 100%;
              border-radius: ${theme.layout.radius};
              border: 1px solid ${theme.palette.accents_2};
            }
          `}</style>
        </div>
      </>
    )
  },
)
Example #3
Source File: layout.tsx    From geist-ui with MIT License 6 votes vote down vote up
CustomizationLayout: React.FC<React.PropsWithChildren<unknown>> = ({
  children,
}) => {
  const theme = useTheme()

  return (
    <div className="layout">
      <Grid.Container>
        <Grid xs={24}>
          <Demo />
          <div className="content">{children}</div>
        </Grid>
        <Grid xs={24}>
          <CustomizationCodes />
        </Grid>
      </Grid.Container>

      <style jsx>{`
        .layout {
          min-height: calc(100vh - 108px);
          max-width: ${theme.layout.pageWidthWithMargin};
          margin: 0 auto;
          padding: 0 ${theme.layout.gap};
          display: flex;
          flex-direction: column;
          box-sizing: border-box;
        }

        .content {
          flex: 1;
          overflow: hidden;
        }
      `}</style>
    </div>
  )
}
Example #4
Source File: dynamic-live.tsx    From geist-ui with MIT License 6 votes vote down vote up
DynamicLive: React.FC<Props> = ({ code, scope }) => {
  const theme = useTheme()
  const codeTheme = makeCodeTheme(theme)
  return (
    <LiveProvider code={code} scope={scope} theme={codeTheme}>
      <div className="wrapper">
        <LivePreview />
        <LiveError className="live-error" />
      </div>
      <Editor code={code} />
      <style jsx>{`
        .wrapper {
          width: 100%;
          padding: ${theme.layout.pageMargin};
          display: flex;
          flex-direction: column;
          box-sizing: border-box;
        }
        .wrapper > :global(div) {
          width: 100%;
          background-color: transparent;
        }
        .wrapper > :global(.live-error) {
          padding: 10px 12px 0 12px;
          margin-bottom: 0;
          border: 2px ${theme.palette.error} dotted;
          border-radius: 10px;
          color: ${theme.palette.errorLight};
          font-size: 13px;
        }
      `}</style>
    </LiveProvider>
  )
}
Example #5
Source File: grid-demo.tsx    From geist-ui with MIT License 6 votes vote down vote up
GridDemo: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
  const theme = useTheme()
  const bgColor = addColorAlpha(theme.palette.accents_2, 0.5)
  return (
    <div className="grid-demo">
      {children}
      <style jsx>{`
        .grid-demo {
          background: transparent;
          background-image: linear-gradient(${bgColor} 1px, transparent 0),
            linear-gradient(90deg, ${bgColor} 1px, transparent 0);
          background-size: 15px 15px, 15px 15px, 75px 75px, 75px 75px;
          border: 2px solid ${bgColor};
          border-radius: 4px;
          overflow: hidden;
          width: 500px;
          height: 150px;
          display: flex;
          flex-direction: column;
          justify-content: center;
          align-items: center;
          margin-bottom: 15px;
        }
        .grid-demo :global(> *) {
          margin-bottom: 15px;
        }
        .grid-demo :global(> *:last-of-type) {
          margin-bottom: 0;
        }
      `}</style>
    </div>
  )
}
Example #6
Source File: home-cell.tsx    From geist-ui with MIT License 5 votes vote down vote up
HomeCell: React.FC<HomeCellProps> = ({ url, title, desc, icon }) => {
  const theme = useTheme()
  return (
    <NextLink href={url} passHref>
      <Link>
        <Card padding="5px" shadow width="100%">
          <h4 className="feature__title">
            <div className="feature__icon">{icon}</div>
            {title}
          </h4>
          <p className="feature__description">{desc}</p>
        </Card>
        <style jsx>{`
          .feature__title {
            display: flex;
            flex-direction: row;
            align-items: center;
          }
          .feature__icon {
            height: 2.5rem;
            width: 2.5rem;
            padding: 0.625rem;
            margin-right: ${theme.layout.gapHalf};
            display: flex;
            align-items: center;
            justify-content: center;
            background: linear-gradient(#3291ff, #0761d1);
            color: #fff;
            border-radius: 2rem;
          }
          .feature__icon :global(svg) {
            width: 100%;
            height: 100%;
          }
          .feature__description {
            color: ${theme.palette.accents_6};
          }
        `}</style>
      </Link>
    </NextLink>
  )
}
Example #7
Source File: index.tsx    From geist-ui with MIT License 5 votes vote down vote up
Home: NextPage<unknown> = () => {
  const theme = useTheme()

  return (
    <>
      <div className="layout">
        <div className="hero">
          <h1 className="title">Geist</h1>
          <h3 className="desc">一个用于构建现代 Web 应用的开源设计系统</h3>
        </div>
        <Grid.Container gap={2} justify="center">
          <Grid xs={24} md={8}>
            <HomeCell
              icon={<PackageIcon />}
              url="/zh-cn/components"
              title="Components"
              desc="Ever-increasing list of concise and aesthetic components."
            />
          </Grid>
          <Grid xs={24} md={8}>
            <HomeCell
              icon={<FeatherIcon />}
              url="/zh-cn/guide/themes"
              title="Customizable"
              desc="Configure sizes, colors, appearances, shapes, and more."
            />
          </Grid>
          <Grid xs={24} md={8}>
            <HomeCell
              icon={<GitHubIcon />}
              url="https://github.com/geist-org/geist-ui"
              title="Open Sourced"
              desc="Geist is open sourced and available free under MIT licence."
            />
          </Grid>
        </Grid.Container>
      </div>
      <style jsx>{`
        .layout {
          min-height: calc(100vh - var(--geist-page-nav-height));
          max-width: ${theme.layout.pageWidthWithMargin};
          margin: 0 auto;
          padding: 0 ${theme.layout.gap} calc(${theme.layout.gap} * 2);
          box-sizing: border-box;
        }
        .hero {
          height: calc(100vh - var(--geist-page-nav-height) - 300px);
          min-height: 30vh;
          max-width: 100%;
          margin: 0 auto;
          text-align: center;
          align-items: center;
          justify-content: center;
          display: flex;
          flex-direction: column;
        }
        .title {
          font-size: 3.75rem;
          font-weight: 700;
          margin: 0;
        }
        .desc {
          color: ${theme.palette.accents_5};
          font-size: 1.5rem;
          font-weight: 500;
          margin: 0 0 ${theme.layout.gap};
        }
      `}</style>
    </>
  )
}
Example #8
Source File: index.tsx    From geist-ui with MIT License 5 votes vote down vote up
Application: NextPage<{}> = () => {
  const theme = useTheme()

  return (
    <>
      <div className="layout">
        <div className="hero">
          <h1 className="title">Geist</h1>
          <h3 className="desc">
            An open source design system for building modern websites and applications.
          </h3>
        </div>

        <Grid.Container gap={2} justify="center">
          <Grid xs={24} md={8}>
            <HomeCell
              icon={<PackageIcon />}
              url="/en-us/components"
              title="Components"
              desc="Ever-increasing list of concise and aesthetic components."
            />
          </Grid>
          <Grid xs={24} md={8}>
            <HomeCell
              icon={<FeatherIcon />}
              url="/en-us/guide/themes"
              title="Customizable"
              desc="Configure sizes, colors, appearances, shapes, and more."
            />
          </Grid>
          <Grid xs={24} md={8}>
            <HomeCell
              icon={<GitHubIcon />}
              url="https://github.com/geist-org/geist-ui"
              title="Open Sourced"
              desc="Geist is open sourced and available free under MIT licence."
            />
          </Grid>
        </Grid.Container>
      </div>
      <style jsx>{`
        .layout {
          min-height: calc(100vh - var(--geist-page-nav-height));
          max-width: ${theme.layout.pageWidthWithMargin};
          margin: 0 auto;
          padding: 0 ${theme.layout.gap} calc(${theme.layout.gap} * 2);
          box-sizing: border-box;
        }
        .hero {
          height: calc(100vh - var(--geist-page-nav-height) - 300px);
          min-height: 30vh;
          max-width: 500px;
          margin: 0 auto;
          text-align: center;
          align-items: center;
          justify-content: center;
          display: flex;
          flex-direction: column;
        }
        .title {
          font-size: 3.75rem;
          font-weight: 700;
          margin: 0;
        }
        .desc {
          color: ${theme.palette.accents_5};
          font-size: 1.5rem;
          font-weight: 500;
          margin: 0 0 ${theme.layout.gap};
        }
      `}</style>
    </>
  )
}
Example #9
Source File: config-provider.tsx    From geist-ui with MIT License 5 votes vote down vote up
ConfigProvider: React.FC<React.PropsWithChildren<ConfigProviderProps>> = React.memo(
  ({
    onThemeChange,
    onThemeTypeChange,
    children,
  }: React.PropsWithChildren<ConfigProviderProps> & typeof defaultProps) => {
    const theme = useTheme()
    const { pathname } = useRouter()
    const [isChinese, setIsChinese] = useState<boolean>(() =>
      pathname.includes(CHINESE_LANGUAGE_IDENT),
    )
    const [scrollHeight, setScrollHeight] = useState<number>(0)
    const [customTheme, setCustomTheme] = useState<GeistUIThemes>(theme)

    const updateSidebarScrollHeight = (height: number) => setScrollHeight(height)
    const updateChineseState = (state: boolean) => setIsChinese(state)
    const updateCustomTheme = (nextTheme: DeepPartial<GeistUIThemes>) => {
      const mergedTheme = Themes.create(theme, { ...nextTheme, type: CUSTOM_THEME_TYPE })
      setCustomTheme(mergedTheme)
      onThemeChange && onThemeChange(mergedTheme)
    }
    const switchTheme = (type: string) => {
      onThemeTypeChange && onThemeTypeChange(type)
    }

    const initialValue = useMemo<Configs>(
      () => ({
        onThemeChange,
        isChinese,
        customTheme,
        switchTheme,
        updateCustomTheme,
        updateChineseState,
        sidebarScrollHeight: scrollHeight,
        updateSidebarScrollHeight,
      }),
      [onThemeChange, scrollHeight, isChinese],
    )

    return (
      <ConfigContext.Provider value={initialValue}>{children}</ConfigContext.Provider>
    )
  },
)
Example #10
Source File: search-item.tsx    From geist-ui with MIT License 5 votes vote down vote up
SearchItem: React.FC<SearchItemProps> = ({
  data,
  onMouseOver,
  onSelect,
  onFocus,
  onBlur = () => {},
}) => {
  const theme = useTheme()
  const selectHandler = () => {
    onSelect(data.url)
  }

  return (
    <li role="option">
      <button
        className="container"
        onClick={selectHandler}
        onMouseOver={onMouseOver}
        onFocus={onFocus}
        onBlur={onBlur}
        data-search-item>
        <SearchIcon data={data} />
        <Text pl="12px" font="14px" className="value" span>
          {data.name}
        </Text>
        <style jsx>{`
          .container {
            width: 100%;
            height: 48px;
            padding: 0 1rem;
            display: flex;
            align-items: center;
            cursor: pointer;
            position: relative;
            transition: color 200ms ease;
            outline: none;
            border: 0;
            color: ${theme.palette.accents_4};
            background-color: transparent;
          }
          .container:focus {
            color: ${theme.palette.foreground};
          }
          .container:global(.value) {
          }
          .container:global(svg) {
            width: 16px;
            height: 16px;
          }
        `}</style>
      </button>
    </li>
  )
}
Example #11
Source File: index.tsx    From geist-ui with MIT License 5 votes vote down vote up
VirtualAnchor: React.FC<React.PropsWithChildren<Props>> = ({ children, pure }) => {
  const theme = useTheme()
  const ref = useRef<HTMLAnchorElement>(null)
  const [id, setId] = useState<string | undefined>()

  useEffect(() => {
    if (!ref.current) return
    setId(virtualAnchorEncode(ref.current.textContent || undefined))
  }, [ref.current])

  return (
    <span className="parent" ref={ref}>
      <Link href={`#${id}`}>{children}</Link>
      <span className="virtual" id={id} />
      {!pure && (
        <span className="icon">
          <AnchorIcon />
        </span>
      )}
      <style jsx>{`
        .parent {
          position: relative;
          color: inherit;
        }

        .parent :global(a) {
          color: inherit;
        }

        .virtual {
          position: absolute;
          top: -65px;
          left: 0;
          opacity: 0;
          pointer-events: none;
          visibility: hidden;
        }

        .icon {
          display: inline-flex;
          justify-content: center;
          align-items: center;
          overflow: hidden;
          left: -1.5em;
          top: 50%;
          transform: translateY(-50%);
          position: absolute;
          opacity: 0;
          visibility: hidden;
          font-size: inherit;
          width: 0.8em;
          height: 0.8em;
          margin-top: 1px;
          color: ${theme.palette.accents_5};
        }

        .parent:hover > .icon {
          opacity: 1;
          visibility: visible;
        }
      `}</style>
    </span>
  )
}
Example #12
Source File: mock-page.tsx    From geist-ui with MIT License 5 votes vote down vote up
MockPage: React.FC<React.PropsWithChildren<Props>> = ({
  visible: customVisible,
  onClose,
  children,
}) => {
  const theme = useTheme()
  const [visible, setVisible] = useState<boolean>(false)

  useEffect(() => {
    if (customVisible !== undefined) {
      setVisible(customVisible)
    }
  }, [customVisible])

  const clickHandler = () => {
    setVisible(false)
    onClose && onClose()
  }
  return (
    <section onClick={clickHandler} className={visible ? 'active' : ''}>
      {children}
      <style jsx>{`
        section {
          position: fixed;
          width: 100vw;
          height: 100vh;
          background-color: ${theme.palette.background};
          z-index: 5000;
          top: -5000px;
          left: -5000px;
          display: none;
        }

        .active {
          top: 0;
          left: 0;
          bottom: 0;
          display: block;
        }
      `}</style>
    </section>
  )
}
Example #13
Source File: icons-cell.tsx    From geist-ui with MIT License 5 votes vote down vote up
IconsCell: React.FC<Props> = ({ component: Component, name, onClick }) => {
  const theme = useTheme()
  return (
    <div className="icon-item" key={name} onClick={() => onClick(name)}>
      <Component />
      <Text type="secondary" small>
        {name}
      </Text>
      <style jsx>{`
        .icon-item {
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: space-evenly;
          flex-grow: 0;
          flex-basis: 125px;
          min-width: 0px;
          height: 95px;
          margin: 12px 5px;
          border-radius: ${theme.layout.radius};
          box-sizing: border-box;
          cursor: pointer;
          user-select: none;
          transition: all 150ms ease-in-out;
        }

        .icon-item > :global(small) {
          display: inline-block;
          width: 90%;
          text-align: center;
          overflow: hidden;
          text-overflow: ellipsis;
        }

        .icon-item:hover {
          box-shadow: ${theme.expressiveness.shadowMedium};
        }

        @media only screen and (max-width: ${theme.layout.breakpointMobile}) {
          .icon-item {
            flex-basis: 30%;
          }
        }
      `}</style>
    </div>
  )
}
Example #14
Source File: editor-input-item.tsx    From geist-ui with MIT License 5 votes vote down vote up
EditorInputItem: React.FC<React.PropsWithChildren<Props>> = ({
  groupName,
  keyName,
}) => {
  const theme = useTheme()
  const { updateCustomTheme } = useConfigs()
  const currentVal = useMemo(() => {
    const group = theme[groupName]
    const key = keyName as keyof typeof group
    return theme[groupName][key]
  }, [theme.expressiveness, keyName])
  const width = useMemo(() => (`${currentVal}`.length > 15 ? '350px' : 'auto'), [])

  const changeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
    updateCustomTheme({
      [groupName]: { [keyName]: event.target.value },
    })
  }

  return (
    <div className="editor-item">
      <Input
        value={currentVal as string}
        label={keyName}
        onChange={changeHandler}
        className="editor-input"
      />
      <style jsx>{`
        .editor-item {
          background-color: transparent;
          width: auto;
          padding: 0;
          line-height: 2rem;
          display: inline-flex;
          align-items: center;
          color: ${theme.palette.accents_5};
          margin-right: 0.75rem;
          margin-bottom: 0.5rem;
          cursor: pointer;
          transition: color 200ms ease;
        }

        .editor-item :global(.editor-input) {
          width: ${width};
        }
      `}</style>
    </div>
  )
}
Example #15
Source File: attributes-title.tsx    From geist-ui with MIT License 5 votes vote down vote up
AttributesTitle: React.FC<React.PropsWithChildren<AttributesTitleProps>> =
  React.memo(({ children, alias }) => {
    const theme = useTheme()
    const { isChinese } = useConfigs()

    return (
      <>
        <h4 className="title">
          <Code>
            <VirtualAnchor pure>{children}</VirtualAnchor>
          </Code>
          {getAlias(!!isChinese, alias)}
        </h4>

        <style jsx>{`
          h4 {
            display: inline-flex;
            align-items: center;
            height: 2rem;
            padding-left: ${theme.layout.gapQuarter};
            padding-right: ${theme.layout.gapHalf};
            background-color: ${theme.palette.accents_1};
            border-radius: ${theme.layout.radius};
            margin-bottom: 0;
          }

          h4 :global(small) {
            font-size: 0.65em;
            padding-left: 0.65rem;
            color: ${theme.palette.accents_4};
            align-self: flex-end;
            line-height: 1.6rem;
          }

          h4 :global(span) {
            color: ${theme.palette.accents_6};
          }
        `}</style>
      </>
    )
  })
Example #16
Source File: index.tsx    From geist-ui with MIT License 5 votes vote down vote up
Colors: React.FC<Props> = ({ type }) => {
  const theme = useTheme()
  const { copy } = useClipboard()
  const { setToast } = useToasts()
  const copyText = (text: string) => {
    copy(text)
    setToast({
      text: (
        <span>
          Copied <Code>{text}</Code>
        </span>
      ),
    })
  }
  const colorItems = useMemo(
    () => getColorItem(type, theme.palette, copyText),
    [type, theme.palette],
  )

  return (
    <div className="colors">
      {colorItems}
      <style jsx>{`
        .colors {
          display: flex;
          flex-direction: column;
          width: 100%;
        }
        .colors :global(.color) {
          padding: ${theme.layout.gap};
          position: relative;
          user-select: none;
        }
        .colors :global(.color:first-child) {
          border-top-left-radius: ${theme.layout.radius};
          border-top-right-radius: ${theme.layout.radius};
        }
        .colors :global(.color:last-child) {
          border-bottom-left-radius: ${theme.layout.radius};
          border-bottom-right-radius: ${theme.layout.radius};
        }
        .colors :global(.color h4) {
          margin: 0;
        }
        .colors :global(.usage) {
          font-size: 1rem;
          padding: 1rem;
          cursor: pointer;
        }
        .colors :global(.value) {
          font-size: 0.875rem;
          text-transform: uppercase;
          padding: 1rem;
          cursor: pointer;
        }
      `}</style>
    </div>
  )
}
Example #17
Source File: active-link.tsx    From geist-ui with MIT License 5 votes vote down vote up
ActiveLink: React.FC<Props> = React.memo(({ href, text }) => {
  const theme = useTheme()
  const { pathname } = useRouter()
  const [title, subtitle] = useMemo(() => {
    if (!/[\u4E00-\u9FA5]/.test(text)) return [text, null]
    if (/zeit|ui|ZEIT|geist|Geist|UI/.test(text)) return [text, null]
    return [text.replace(/[^\u4E00-\u9FA5]/g, ''), text.replace(/[^a-zA-Z]/g, '')]
  }, [text])
  const isActive = pathname === href

  return (
    <>
      <Link href={href}>
        <a className={`link ${isActive ? 'active' : ''}`}>
          {title}
          {subtitle && <span>&nbsp;{subtitle}</span>}
        </a>
      </Link>
      <style jsx>{`
        a {
          font: inherit;
        }

        .link {
          display: flex;
          align-items: baseline;
          font-size: 1rem;
          color: ${theme.palette.accents_6};
          padding: calc(${theme.layout.unit} * 0.375) 0;
          transition: all 200ms ease;
        }

        span {
          font-size: 0.75rem;
          color: ${theme.palette.accents_4};
          font-weight: 400;
        }

        .link.active {
          color: ${theme.palette.success};
          font-weight: 600;
        }

        .link.active span {
          color: ${theme.palette.successLight};
        }
      `}</style>
    </>
  )
})
Example #18
Source File: sidebar.tsx    From geist-ui with MIT License 5 votes vote down vote up
Sidebar: React.FC<Props> = React.memo(() => {
  const theme = useTheme()
  const boxRef = useRef<HTMLDivElement>(null)
  const { sidebarScrollHeight, updateSidebarScrollHeight } = useConfigs()
  const { locale, tabbar } = useLocale()

  const tabbarData = useMemo(() => {
    const allSides = Metadata[locale]
    const currentSide = allSides.find(side => side.name === tabbar)
    return (currentSide?.children || []) as Array<Sides>
  }, [locale, tabbar])

  useEffect(() => {
    Router.events.on('routeChangeStart', () => {
      if (!boxRef.current) return
      updateSidebarScrollHeight(boxRef.current.scrollTop || 0)
    })
  }, [])

  useEffect(() => {
    if (!boxRef.current) return
    boxRef.current.scrollTo({ top: sidebarScrollHeight })
  }, [boxRef.current])

  return (
    <div ref={boxRef} className="sides box">
      <SideItem sides={tabbarData}>
        <SideGroup />
      </SideItem>
      <Spacer />
      <style jsx>{`
        .sides {
          width: 100%;
          padding-bottom: ${theme.layout.gap};
        }
        .box {
          overflow-y: auto;
          overflow-x: hidden;
          height: 100%;
          display: flex;
          flex-direction: column;
          align-items: center;
        }
        .box::-webkit-scrollbar {
          width: 0;
          background-color: transparent;
        }
        .box > :global(.item) {
          margin-bottom: ${theme.layout.gap};
        }
      `}</style>
    </div>
  )
})
Example #19
Source File: menu.tsx    From geist-ui with MIT License 4 votes vote down vote up
Menu: React.FC<unknown> = () => {
  const router = useRouter()
  const theme = useTheme()
  const { isChinese } = useConfigs()
  const { tabbar: currentUrlTabValue, locale } = useLocale()
  const [expanded, setExpanded] = useState<boolean>(false)
  const [, setBodyHidden] = useBodyScroll(null, { delayReset: 300 })
  const isMobile = useMediaQuery('xs', { match: 'down' })
  const allSides = useMemo(() => Metadata[locale], [locale])

  useEffect(() => {
    const prefetch = async () => {
      const urls = isChinese
        ? ['/zh-cn/guide/introduction', '/zh-cn/components/text', '/zh-cn/customization']
        : ['/en-us/guide/introduction', '/en-us/components/text', '/en-us/customization']
      await Promise.all(
        urls.map(async url => {
          await router.prefetch(url)
        }),
      )
    }
    prefetch()
      .then()
      .catch(err => console.log(err))
  }, [isChinese])

  useEffect(() => {
    setBodyHidden(expanded)
  }, [expanded])

  useEffect(() => {
    if (!isMobile) {
      setExpanded(false)
    }
  }, [isMobile])

  useEffect(() => {
    const handleRouteChange = () => {
      setExpanded(false)
    }

    router.events.on('routeChangeComplete', handleRouteChange)
    return () => router.events.off('routeChangeComplete', handleRouteChange)
  }, [router.events])

  const handleTabChange = useCallback(
    (tab: string) => {
      const shouldRedirectDefaultPage = currentUrlTabValue !== tab
      if (!shouldRedirectDefaultPage) return
      const defaultPath = `/${locale}/${tab}`
      router.push(defaultPath)
    },
    [currentUrlTabValue, locale],
  )
  const [isLocked, setIsLocked] = useState<boolean>(false)

  useEffect(() => {
    const handler = () => {
      const isLocked = document.body.style.overflow === 'hidden'
      setIsLocked(last => (last !== isLocked ? isLocked : last))
    }
    const observer = new MutationObserver(mutations => {
      mutations.forEach(function (mutation) {
        if (mutation.type !== 'attributes') return
        handler()
      })
    })

    observer.observe(document.body, {
      attributes: true,
    })
    return () => {
      observer.disconnect()
    }
  }, [])

  return (
    <>
      <div className="menu-wrapper">
        <nav className="menu">
          <div className="content">
            <div className="logo">
              <NextLink href={`/${locale}`}>
                <a aria-label="Go Home">
                  <Image
                    src="/images/logo.png"
                    width="20px"
                    height="20px"
                    mr={0.5}
                    draggable={false}
                    title="Logo"
                  />
                  Geist
                </a>
              </NextLink>
            </div>

            <div className="tabs">
              <Tabs
                value={currentUrlTabValue}
                leftSpace={0}
                activeClassName="current"
                align="center"
                hideDivider
                hideBorder
                onChange={handleTabChange}>
                <Tabs.Item font="14px" label={isChinese ? '主页' : 'Home'} value="" />
                {allSides.map((tab, index) => (
                  <Tabs.Item
                    font="14px"
                    label={tab.localeName || tab.name}
                    value={tab.name}
                    key={`${tab.localeName || tab.name}-${index}`}
                  />
                ))}
              </Tabs>
            </div>

            <div className="controls">
              {isMobile ? (
                <Button
                  className="menu-toggle"
                  auto
                  type="abort"
                  onClick={() => setExpanded(!expanded)}>
                  <MenuIcon size="1.125rem" />
                </Button>
              ) : (
                <Controls />
              )}
            </div>
          </div>
        </nav>
      </div>
      <MenuMobile expanded={expanded} />

      <style jsx>{`
        .menu-wrapper {
          height: var(--geist-page-nav-height);
        }
        .menu {
          position: fixed;
          top: 0;
          left: 0;
          right: 0;
          padding-right: ${isLocked ? 'var(--geist-page-scrollbar-width)' : 0};
          height: var(--geist-page-nav-height);
          //width: 100%;
          backdrop-filter: saturate(180%) blur(5px);
          background-color: ${addColorAlpha(theme.palette.background, 0.8)};
          box-shadow: ${theme.type === 'dark'
            ? '0 0 0 1px #333'
            : '0 0 15px 0 rgba(0, 0, 0, 0.1)'};
          z-index: 999;
        }
        nav .content {
          display: flex;
          align-items: center;
          justify-content: space-between;
          max-width: 1000px;
          height: 100%;
          margin: 0 auto;
          user-select: none;
          padding: 0 ${theme.layout.gap};
        }
        .logo {
          flex: 1 1;
          display: flex;
          align-items: center;
          justify-content: flex-start;
        }
        .logo a {
          display: inline-flex;
          flex-direction: row;
          align-items: center;
          font-size: 1.125rem;
          font-weight: 500;
          color: inherit;
          height: 28px;
        }
        .logo :global(.image) {
          border: 1px solid ${theme.palette.border};
          border-radius: 2rem;
        }
        .tabs {
          flex: 1 1;
          padding: 0 ${theme.layout.gap};
        }
        .tabs :global(.content) {
          display: none;
        }
        @media only screen and (max-width: ${theme.breakpoints.xs.max}) {
          .tabs {
            display: none;
          }
        }
        .controls {
          flex: 1 1;
          display: flex;
          align-items: center;
          justify-content: flex-end;
        }
        .controls :global(.menu-toggle) {
          display: flex;
          align-items: center;
          min-width: 40px;
          height: 40px;
          padding: 0;
        }
      `}</style>
    </>
  )
}
Example #20
Source File: menu-mobile.tsx    From geist-ui with MIT License 4 votes vote down vote up
MenuMobile: React.FC<Props> = ({ expanded }) => {
  const theme = useTheme()
  const { pathname } = useRouter()
  const { isChinese } = useConfigs()
  const { locale } = useLocale()
  const menuData = useMemo(() => Metadata[locale], [locale])
  const [expandedGroupName, setExpandedGroupName] = React.useState<string | null>(null)

  const handleGroupClick = (name: string) => {
    setExpandedGroupName(expandedGroupName === name ? null : name)
  }

  if (!expanded) return null

  return (
    <div className="mobile-menu">
      <div className="content">
        <NextLink href={`/${locale}`}>
          <a className={`menu-item fadein ${pathname === `/${locale}` ? 'active' : ''}`}>
            {isChinese ? '主页' : 'Home'}
          </a>
        </NextLink>

        {menuData.map((group, index) => (
          <div
            key={group.name}
            className="fadein"
            style={{ animationDelay: `${(index + 1) * 50}ms` }}>
            <button
              className={`menu-item ${expandedGroupName === group.name && 'expanded'}`}
              onClick={() => handleGroupClick(group.name)}>
              <ChevronRightIcon
                size="1rem"
                strokeWidth={2}
                color={theme.palette.accents_4}
              />
              {group.name}
            </button>
            {expandedGroupName === group.name && (
              <div className="group">
                {(group.children as Array<Sides>).map(section => (
                  <div key={section.name}>
                    <span className="section-name">{section.name}</span>
                    {(section.children as Array<Sides>).map(item => (
                      <NextLink href={item.url || '/'} key={item.url}>
                        <a
                          className={`section-item ${
                            pathname === item.url ? 'active' : ''
                          }`}>
                          {item.name}
                        </a>
                      </NextLink>
                    ))}
                  </div>
                ))}
              </div>
            )}
          </div>
        ))}
      </div>

      <style jsx>{`
        .mobile-menu {
          position: fixed;
          top: var(--geist-page-nav-height);
          height: calc(100vh - var(--geist-page-nav-height));
          width: 100vw;
          overflow-y: auto;
          z-index: 999;
          box-sizing: border-box;
          background-color: ${theme.palette.background};
          overflow-y: auto;
        }
        .fadein {
          animation: fadeIn 200ms ease;
          animation-fill-mode: forwards;
          opacity: 0;
        }
        .menu-item {
          padding: 0 ${theme.layout.gapHalf};
          margin: 0 ${theme.layout.gap};
          height: 48px;
          width: 100%;
          display: flex;
          align-items: center;
          border: none;
          background: none;
          outline: none;
          border-bottom: 1px solid ${theme.palette.accents_2};
          text-transform: capitalize;
          color: ${theme.palette.accents_6};
          cursor: pointer;
        }
        .menu-item :global(svg) {
          transform: translateX(${theme.layout.gapQuarterNegative});
          transition: transform 250ms ease;
        }
        .menu-item.expanded {
          border-bottom: none;
        }
        .menu-item.expanded :global(svg) {
          transform: rotate(90deg) translateY(${theme.layout.gapQuarter});
        }
        .group {
          background: ${theme.palette.accents_1};
          padding: 0 calc(${theme.layout.gap} * 1.5) ${theme.layout.gap};
          border-top: 1px solid ${theme.palette.accents_2};
        }
        .section-name {
          display: block;
          font-size: 0.75rem;
          text-transform: uppercase;
          color: ${theme.palette.accents_5};
          margin-top: ${theme.layout.gap};
          margin-bottom: ${theme.layout.gapHalf};
        }
        .section-item {
          padding: ${theme.layout.gapQuarter} ${theme.layout.gap};
          margin: 0 ${theme.layout.gapQuarter};
          width: 100%;
          display: flex;
          align-items: center;
          border: none;
          background: none;
          outline: none;
          color: ${theme.palette.accents_6};
          border-left: 1px solid ${theme.palette.accents_2};
        }
        .active {
          color: ${theme.palette.link};
          font-weight: 500;
        }
        @keyframes fadeIn {
          from {
            transform: translate3d(0, 0.375rem, 0);
            opacity: 0;
          }
          to {
            transform: translate3d(0, 0, 0);
            opacity: 1;
          }
        }
      `}</style>
    </div>
  )
}
Example #21
Source File: editor.tsx    From geist-ui with MIT License 4 votes vote down vote up
Editor: React.FC<Props> = ({ code }) => {
  const theme = useTheme()
  const { copy } = useClipboard()
  const { isChinese } = useConfigs()
  const [visible, setVisible] = useState(false)
  const { setToast } = useToasts()
  const clickHandler = (event: React.MouseEvent) => {
    event.stopPropagation()
    event.preventDefault()
    setVisible(!visible)
  }

  const copyHandler = (event: React.MouseEvent) => {
    event.stopPropagation()
    event.preventDefault()
    copy(code)
    setToast({ text: isChinese ? '代码已拷贝至剪切板。' : 'code copied.' })
  }

  return (
    <div className="editor">
      <details open={visible}>
        <summary onClick={clickHandler}>
          <div className="summary-safari">
            <div className="action">
              <span className="arrow">
                <RightIcon size={16} />
              </span>
              <span>{isChinese ? '编辑代码' : 'Code Editor'}</span>
            </div>
            <div className="action">
              {visible && (
                <span
                  className="copy"
                  onClick={copyHandler}
                  title={isChinese ? '拷贝代码' : 'Copy Code'}>
                  <CopyIcon size={18} />
                </span>
              )}
            </div>
          </div>
        </summary>
        <div className="area">
          <LiveEditor />
        </div>
      </details>

      <style jsx>{`
        .editor {
          border-bottom-left-radius: ${theme.layout.radius};
          border-bottom-right-radius: ${theme.layout.radius};
        }

        details {
          transition: all 0.2s ease;
          overflow: hidden;
          border-bottom-left-radius: ${theme.layout.radius};
          border-bottom-right-radius: ${theme.layout.radius};
        }

        details summary::-webkit-details-marker {
          display: none;
        }

        summary {
          box-sizing: border-box;
          border-top: 1px solid ${theme.palette.accents_2};
          color: ${theme.palette.accents_5};
          width: 100%;
          list-style: none;
          user-select: none;
          outline: none;
        }

        .summary-safari {
          box-sizing: border-box;
          display: flex;
          justify-content: space-between;
          align-items: center;
          width: 100%;
          height: 2.875rem;
          padding: 0 ${theme.layout.gap};
        }

        summary :global(svg) {
          cursor: pointer;
        }

        .action {
          width: auto;
          display: flex;
          align-items: center;
          font-size: 0.8rem;
        }

        .area {
          position: relative;
          box-sizing: border-box;
          white-space: pre;
          font-family: ${theme.font.mono};
          color: ${theme.palette.foreground};
          background-color: ${theme.palette.background};
          font-size: 1em;
          overflow: hidden;
          border-top: 1px solid ${theme.palette.accents_2};
          padding: ${theme.layout.gapHalf};
        }

        .arrow {
          transition: all 0.2s ease;
          transform: rotate(${visible ? 90 : 0}deg);
          display: inline-flex;
          align-items: center;
          width: 1rem;
          height: 1rem;
          margin-right: 0.5rem;
        }

        .copy {
          display: inline-flex;
          align-items: center;
          color: ${theme.palette.accents_4};
          transition: color 0.2s ease;
        }

        .copy:hover {
          color: ${theme.palette.accents_6};
        }
      `}</style>
    </div>
  )
}
Example #22
Source File: editor.tsx    From geist-ui with MIT License 4 votes vote down vote up
Editor = () => {
  const theme = useTheme()
  const DefaultTheme = Themes.getPresetStaticTheme()
  const { updateCustomTheme, isChinese } = useConfigs()

  // const resetLayout = () => updateCustomTheme({ layout: DefaultTheme.layout })
  const restColors = () => updateCustomTheme({ palette: DefaultTheme.palette })
  const resetExpressiveness = () => {
    updateCustomTheme({ expressiveness: DefaultTheme.expressiveness })
  }

  return (
    <div className="editor">
      <Text h3 mt="40px" font="22px">
        {isChinese ? '色彩' : 'Colors'}
        <Button
          type="abort"
          icon={<RotateCcwIcon />}
          auto
          px={0.65}
          scale={0.4}
          ml="10px"
          onClick={restColors}
        />
      </Text>
      <p className="subtitle">{isChinese ? '基础' : 'basic'}</p>
      <div className="content">
        {basicColors.map((item, index) => (
          <EditorColorItem key={`${item}-${index}`} keyName={item} />
        ))}
      </div>
      <p className="subtitle">{isChinese ? '状态' : 'status'}</p>
      <div className="content">
        {statusColors.map((item, index) => (
          <EditorColorItem key={`${item}-${index}`} keyName={item} />
        ))}
      </div>
      <p className="subtitle">{isChinese ? '其他' : 'others'}</p>
      <div className="content">
        {otherColors.map((item, index) => (
          <EditorColorItem key={`${item}-${index}`} keyName={item} />
        ))}
      </div>

      <Text h3 mt="40px">
        {isChinese ? '表现力' : 'Expressiveness'}
        <Button
          type="abort"
          icon={<RotateCcwIcon />}
          auto
          px={0.65}
          scale={0.4}
          ml="10px"
          onClick={resetExpressiveness}
        />
      </Text>
      <p className="subtitle">{isChinese ? '基础' : 'basic'}</p>
      <div className="content">
        {expressiveness.map((item, index) => (
          <EditorInputItem
            key={`${item}-${index}`}
            groupName="expressiveness"
            keyName={item}
          />
        ))}
      </div>
      <style jsx>{`
        .content {
          display: flex;
          justify-content: flex-start;
          align-items: center;
          flex-wrap: wrap;
          width: auto;
          margin: 0 auto;
          padding-left: ${theme.layout.gapQuarter};
        }

        .subtitle {
          color: ${theme.palette.accents_4};
          text-transform: uppercase;
          font-size: 0.75rem;
          margin-top: 2rem;
        }
      `}</style>
    </div>
  )
}
Example #23
Source File: attributes-table.tsx    From geist-ui with MIT License 4 votes vote down vote up
AttributesTable: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
  const theme = useTheme()

  return (
    <Card className="attr">
      {children}
      <style jsx global>{`
        .attr .pre {
          margin-top: 12px !important;
        }
        .attr table {
          margin-top: 12px;
          margin-right: ${theme.layout.gap};
        }
        .attr h4.title {
          margin-top: calc(${theme.layout.gap} * 2.2);
        }
        .attr h4.title:first-of-type {
          margin-top: 0;
        }
        .attr table {
          border-collapse: separate;
          border-spacing: 0;
          width: 100%;
        }
        .attr thead th td {
          height: 2.5rem;
        }
        .attr tbody tr td {
          height: 3.333rem;
        }
        .attr th,
        .attr td {
          padding: 0 0.625rem;
          text-align: left;
        }
        .attr th {
          height: 2.5rem;
          color: ${theme.palette.accents_5};
          font-size: 0.75rem;
          font-weight: 400;
          letter-spacing: 0;
          background: ${theme.palette.accents_1};
          border-bottom: 1px solid ${theme.palette.border};
          border-top: 1px solid ${theme.palette.border};
        }
        .attr th:nth-child(1) {
          border-bottom: 1px solid ${theme.palette.border};
          border-left: 1px solid ${theme.palette.border};
          border-radius: 4px 0 0 4px;
          border-top: 1px solid ${theme.palette.border};
        }
        .attr th:last-child {
          border-bottom: 1px solid ${theme.palette.border};
          border-radius: 0 4px 4px 0;
          border-right: 1px solid ${theme.palette.border};
          border-top: 1px solid ${theme.palette.border};
        }
        .attr tr td {
          border-bottom: 1px solid ${theme.palette.border};
          color: ${theme.palette.accents_6};
          font-size: 0.875rem;
          height: 2.5rem;
        }
        .attr td:nth-child(1) {
          border-left: 1px solid transparent;
        }
        @media only screen and (max-width: ${theme.layout.breakpointMobile}) {
          .attr {
            overflow-x: scroll;
          }
          .attr::-webkit-scrollbar {
            width: 0;
            height: 0;
            background-color: transparent;
          }
        }
      `}</style>
    </Card>
  )
}
Example #24
Source File: controls.tsx    From geist-ui with MIT License 4 votes vote down vote up
Controls: React.FC<unknown> = React.memo(() => {
  const theme = useTheme()
  const { themes } = useAllThemes()
  const { switchTheme, updateChineseState } = useConfigs()
  const { pathname } = useRouter()
  const { locale } = useLocale()
  const isChinese = useMemo(() => locale === CHINESE_LANGUAGE_IDENT, [locale])
  const nextLocalePath = useMemo(() => {
    const nextLocale = isChinese ? ENGLISH_LANGUAGE_IDENT : CHINESE_LANGUAGE_IDENT
    return pathname.replace(locale, nextLocale)
  }, [locale, pathname])
  const hasCustomTheme = useMemo(() => Themes.hasUserCustomTheme(themes), [themes])

  const switchThemes = (type: string) => {
    switchTheme(type)
    if (typeof window === 'undefined' || !window.localStorage) return
    window.localStorage.setItem('theme', type)
  }
  const switchLanguages = () => {
    updateChineseState(!isChinese)
    Router.push(nextLocalePath)
  }
  const redirectGithub = () => {
    if (typeof window === 'undefined') return
    window.open(GITHUB_URL)
  }

  return (
    <div className="wrapper">
      <Keyboard
        h="28px"
        command
        font="12px"
        className="shortcuts"
        title="Command + K to search.">
        K
      </Keyboard>
      <Spacer w={0.75} />
      <Button
        w="28px"
        h="28px"
        py={0}
        px={0}
        onClick={switchLanguages}
        title={isChinese ? '切换语言' : 'switch language'}>
        <Text font="13px" style={{ fontWeight: 500 }}>
          {isChinese ? 'En' : '中'}
        </Text>
      </Button>
      <Spacer w={0.75} />
      <Button
        w="28px"
        h="28px"
        py={0}
        px={0}
        icon={<GitHubIcon />}
        onClick={redirectGithub}
        title={isChinese ? '代码仓库' : 'GitHub Repository'}
      />
      <Spacer w={0.75} />
      <Select
        scale={0.5}
        h="28px"
        pure
        onChange={switchThemes}
        value={theme.type}
        title={isChinese ? '切换主题' : 'Switch Themes'}>
        <Select.Option value="light">
          <span className="select-content">
            <SunIcon size={14} /> {isChinese ? '明亮' : 'Light'}
          </span>
        </Select.Option>
        <Select.Option value="dark">
          <span className="select-content">
            <MoonIcon size={14} /> {isChinese ? '暗黑' : 'Dark'}
          </span>
        </Select.Option>
        {hasCustomTheme && (
          <Select.Option value={CUSTOM_THEME_TYPE}>
            <span className="select-content">
              <UserIcon size={14} /> {CUSTOM_THEME_TYPE}
            </span>
          </Select.Option>
        )}
      </Select>
      <style jsx>{`
        .wrapper {
          display: flex;
          align-items: center;
        }
        .wrapper :global(kbd.shortcuts) {
          line-height: 28px !important;
          cursor: help;
          opacity: 0.75;
          border: none;
        }
        .wrapper :global(.select) {
          width: 85px;
          min-width: 85px;
        }
        .select-content {
          width: auto;
          height: 18px;
          display: flex;
          justify-content: space-between;
          align-items: center;
        }
        .select-content :global(svg) {
          margin-right: 10px;
          margin-left: 2px;
        }
      `}</style>
    </div>
  )
})
Example #25
Source File: editor-color-item.tsx    From geist-ui with MIT License 4 votes vote down vote up
EditorColorItem: React.FC<React.PropsWithChildren<Props>> = ({ keyName }) => {
  const theme = useTheme()
  const { updateCustomTheme } = useConfigs()
  const label = `${keyName}`
  const mainColor = useMemo(() => theme.palette[keyName], [theme.palette, keyName])
  const randomColors = useMemo(() => getRandomColors(), [])
  const colorChangeHandler = ({ hex }: ColorResult) => {
    updateCustomTheme({
      palette: { [keyName]: hex },
    })
  }

  const popoverContent = (color: string) => (
    <TwitterPicker
      triangle="hide"
      color={color}
      onChangeComplete={colorChangeHandler}
      colors={randomColors}
    />
  )
  return (
    <Popover
      content={() => popoverContent(mainColor)}
      portalClassName="editor-popover"
      offset={3}>
      <div className="editor-item">
        <div className="dot-box">
          <span className="dot" />
        </div>
        {label}
        <style jsx>{`
          .editor-item {
            background-color: transparent;
            width: auto;
            padding: 0 ${theme.layout.gapHalf};
            line-height: 2rem;
            display: inline-flex;
            align-items: center;
            border: 1px solid ${theme.palette.border};
            border-radius: ${theme.layout.radius};
            color: ${theme.palette.accents_5};
            margin-right: 0.75rem;
            margin-bottom: 0.5rem;
            cursor: pointer;
            transition: color 200ms ease;
          }

          :global(.editor-popover .inner) {
            padding: 0 !important;
          }

          :global(.editor-popover .twitter-picker) {
            box-shadow: none !important;
            border: 0 !important;
            background: transparent !important;
          }

          .editor-item:hover {
            color: ${theme.palette.accents_8};
          }

          .editor-item:hover .dot {
            transform: scale(1);
          }

          .dot-box,
          .dot {
            display: inline-flex;
            justify-content: center;
            align-items: center;
          }

          .dot-box {
            width: 1rem;
            height: 1rem;
            margin-right: 0.75rem;
          }

          .dot {
            width: 100%;
            height: 100%;
            border-radius: 50%;
            background-color: ${mainColor};
            transform: scale(0.8);
            transition: transform 200ms ease;
          }
        `}</style>
      </div>
    </Popover>
  )
}
Example #26
Source File: search-items.tsx    From geist-ui with MIT License 4 votes vote down vote up
SearchItems = React.forwardRef<
  SearchItemsRef,
  React.PropsWithChildren<SearchItemsProps>
>(
  (
    { data, onSelect, preventHoverHighlightSync },
    outRef: React.Ref<SearchItemsRef | null>,
  ) => {
    const theme = useTheme()
    const { rect, setRect } = useRect()
    const ref = useRef<HTMLUListElement | null>(null)
    const [displayHighlight, setDisplayHighlight] = useState<boolean>(false)
    useImperativeHandle(outRef, () =>
      Object.assign(ref.current, {
        closeHighlight: () => setDisplayHighlight(false),
      }),
    )

    const hoverHandler = (event: MouseEvent<HTMLButtonElement>) => {
      if (preventHoverHighlightSync) return
      if (!isSearchItem(event.target as HTMLButtonElement)) return
      ;(event.target as HTMLButtonElement).focus()
    }
    const focusHandler = (event: FocusEvent<HTMLButtonElement>) => {
      if (!isSearchItem(event.target as HTMLButtonElement)) return
      setRect(event, () => ref.current)
      setDisplayHighlight(true)
    }
    const blurHandler = () => {
      setDisplayHighlight(false)
    }

    const grouppedResults = useMemo(() => groupResults(data), [data])

    return (
      <ul className="results" role="listbox" ref={ref}>
        <Highlight
          className="results-hover"
          rect={rect}
          visible={displayHighlight}
          activeOpacity={0.5}
        />
        {grouppedResults.map((group) => (
          <li role="presentation" key={group.title}>
            <div className="group-title">{group.title}</div>
            <ul role="group">
              {group.items.map(item => (
                <SearchItem
                  onSelect={onSelect}
                  onMouseOver={hoverHandler}
                  onFocus={focusHandler}
                  onBlur={blurHandler}
                  data={item}
                  key={item.url}
                />
              ))}
            </ul>
          </li>
        ))}
        <style jsx>{`
          .results {
            width: 100%;
            max-height: 300px;
            overflow-y: auto;
            position: relative;
            scroll-behavior: smooth;
            margin-bottom: 0.5rem;
          }
          .results :global(li:before) {
            content: none;
          }
          .group-title {
            color: ${theme.palette.accents_5};
            font-size: 0.75rem;
            text-align: start;
            margin: 0.25rem 0;
          }
          .results:global(div.highlight.results-hover) {
            border-radius: 8px;
          }
        `}</style>
      </ul>
    )
  },
)
Example #27
Source File: search.tsx    From geist-ui with MIT License 4 votes vote down vote up
Search: React.FC<unknown> = () => {
  const theme = useTheme()
  const router = useRouter()
  const { locale } = useLocale()
  const [preventHover, setPreventHover, preventHoverRef] = useCurrentState<boolean>(false)
  const ref = useRef<HTMLInputElement | null>(null)
  const itemsRef = useRef<SearchItemsRef | null>(null)
  const [state, setState] = useState<SearchResults>([])
  const { bindings, setVisible, visible } = useModal(false)
  const { bindings: inputBindings, setState: setInput, state: input } = useInput('')

  const cleanAfterModalClose = () => {
    setVisible(false)
    const timer = window.setTimeout(() => {
      setState([])
      setInput('')
      itemsRef.current?.scrollTo(0, 0)
      setPreventHover(true)
      window.clearTimeout(timer)
    }, 400)
  }

  useKeyboard(() => {
    setVisible(true)
    const timer = setTimeout(() => {
      ref.current?.focus()
      window.clearTimeout(timer)
    }, 0)
  }, [KeyMod.CtrlCmd, KeyCode.KEY_K])

  useEffect(() => {
    if (!input) return setState([])
    setPreventHover(true)
    setState(search(input, locale))
    itemsRef.current?.scrollTo(0, 0)
  }, [input])

  useEffect(() => {
    if (visible) return
    cleanAfterModalClose()
  }, [visible])

  useEffect(() => {
    const eventHandler = () => {
      if (!preventHoverRef.current) return
      setPreventHover(false)
    }
    document.addEventListener('mousemove', eventHandler)
    return () => {
      document.removeEventListener('mousemove', eventHandler)
    }
  }, [])

  const selectHandler = (url: string) => {
    if (url.startsWith('http')) return window.open(url)
    router.push(url)
    setVisible(false)
  }

  const { bindings: KeyBindings } = useKeyboard(
    event => {
      const isBack = event.keyCode === KeyCode.UpArrow
      focusNextElement(
        itemsRef.current,
        () => {
          setPreventHover(true)
        },
        isBack,
      )
    },
    [KeyCode.DownArrow, KeyCode.UpArrow],
    {
      disableGlobalEvent: true,
    },
  )

  return (
    <div className="container" {...KeyBindings}>
      <Modal
        {...bindings}
        py={0}
        px={0.75}
        wrapClassName="search-menu"
        positionClassName="search-position">
        <Input
          ref={ref}
          w="100%"
          font="1.125rem"
          py={0.75}
          placeholder="Search a component"
          className="search-input"
          clearable
          {...inputBindings}
        />
        {state.length > 0 && (
          <>
            <Divider mt={0} mb={1} />
            <SearchItems
              preventHoverHighlightSync={preventHover}
              ref={itemsRef}
              data={state}
              onSelect={selectHandler}
            />
          </>
        )}
      </Modal>
      <style jsx>{`
        .title {
          width: 100%;
          color: ${theme.palette.background};
          background-color: ${theme.palette.violet};
          display: flex;
          justify-content: flex-end;
          padding: 0 10px;
          user-select: none;
        }
        .container {
          visibility: hidden;
        }
        :global(.search-menu ul),
        :global(.search-menu li) {
          padding: 0;
          margin: 0;
          list-style: none;
        }
        :global(.search-menu .input-container.search-input) {
          border: none;
          border-radius: 0;
        }
        :global(.search-menu .input-container div.input-wrapper) {
          border: none;
          border-radius: 0;
        }
        :global(.search-menu .input-container .input-wrapper.hover) {
          border: none;
        }
        :global(.search-menu .input-container .input-wrapper:active) {
          border: none;
        }
        :global(div.search-position.position) {
          position: absolute;
          top: 100px;
          left: 50%;
          transform: translateX(-50%);
          transition: all 500ms ease;
          width: 500px;
          height: auto;
        }
        :global(.search-menu.wrapper) {
          box-shadow: 0 5px 20px 0 rgba(0, 0, 0, 0.15), 0 -5px 20px 0 rgba(0, 0, 0, 0.15) !important;
        }
      `}</style>
    </div>
  )
}
Example #28
Source File: demo.tsx    From geist-ui with MIT License 4 votes vote down vote up
Demo: React.FC<React.PropsWithChildren<unknown>> = () => {
  const theme = useTheme()
  const { isChinese } = useConfigs()

  return (
    <div className="demo">
      <div className="content">
        {isChinese ? (
          <>
            <Text h2 mb={0} font="13px" type="secondary">
              预览
            </Text>
            <Text>
              这里是你变更主题后的即时预览。此外,当你每次更新主题变量时,整个文档站点也会随之变化。
            </Text>
          </>
        ) : (
          <>
            <Text h2 mb={0} font="13px">
              PREVIEWS
            </Text>
            <Text>
              Here&#39;s a preview of your changes to the Theme. When you set the changes,
              the entire document site will change with the theme.
            </Text>
          </>
        )}

        <Spacer h={1.7} />
        <Text h3 font="13px" type="secondary">
          {isChinese ? '色彩' : 'COLORS'}
        </Text>
        <Colors />

        <Spacer h={1.7} />
        <Text h3 font="13px" type="secondary">
          {isChinese ? '排版' : 'Typography'}
        </Text>
        <Text>
          <Link rel="nofollow" href="https://en.wikipedia.org/wiki/HTTP/2" color>
            HTTP/2
          </Link>{' '}
          allows the server to <Code>push</Code> content, that is, to respond with data
          for more queries than the client requested. This allows the server to supply
          data it knows a web browser will need to render a web page, without waiting for
          the browser to examine the first response, and without the overhead of an
          additional request cycle.
        </Text>
        <Text h6>Heading</Text>
        <Text h5>Heading</Text>
        <Text h4>Heading</Text>
        <Text h3>Heading</Text>

        <Spacer h={1.7} />
        <Text h3 font="13px" type="secondary">
          {isChinese ? '基础组件' : 'Basic Components'}
        </Text>
        <Select width="90%" placeholder="Choose one" initialValue="1">
          <Select.Option value="1">Option 1</Select.Option>
          <Select.Option value="2">Option 2</Select.Option>
        </Select>
        <Spacer h={1} />
        <Grid.Container width="100%">
          <Grid xs={8}>
            <Button disabled auto>
              Action
            </Button>
          </Grid>
          <Grid xs={8}>
            <Button auto>Action</Button>
          </Grid>
          <Grid xs={8}>
            <Button auto type="secondary">
              Action
            </Button>
          </Grid>
        </Grid.Container>
      </div>
      <style jsx>{`
        .demo {
          width: 34%;
          margin-top: calc(${theme.layout.gap} * 2);
          margin-right: ${theme.layout.gap};
          padding-right: ${theme.layout.gapQuarter};
          position: relative;
          border-right: 1px solid ${theme.palette.border};
          height: auto;
          transition: width 200ms ease;
        }

        .content {
          width: 100%;
        }

        @media only screen and (max-width: ${theme.layout.breakpointMobile}) {
          .demo {
            display: none;
          }
        }
      `}</style>
    </div>
  )
}
Example #29
Source File: _app.tsx    From geist-ui with MIT License 4 votes vote down vote up
Application: NextPage<AppProps<{}>> = ({ Component, pageProps }) => {
  const theme = useTheme()
  const [themeType, setThemeType] = useState<string>()
  const [customTheme, setCustomTheme] = useState<GeistUIThemes>(theme)
  const themeChangeHandle = (theme: GeistUIThemes) => {
    setCustomTheme(theme)
    setThemeType(theme.type)
  }

  useEffect(() => {
    const theme = window.localStorage.getItem('theme')
    if (theme !== 'dark') return
    setThemeType('dark')
  }, [])
  useDomClean()

  return (
    <>
      <Head>
        <title>Geist UI - Modern and minimalist React UI library</title>
        <meta name="google" content="notranslate" />
        <meta name="twitter:creator" content="@echo_witt" />
        <meta name="referrer" content="strict-origin" />
        <meta property="og:title" content="Geist UI" />
        <meta property="og:site_name" content="Geist UI" />
        <meta property="og:url" content="https://geist-ui.dev" />
        <link rel="dns-prefetch" href="//geist-ui.dev" />
        <meta name="twitter:card" content="summary_large_image" />
        <meta name="generator" content="Geist UI" />
        <meta
          name="description"
          content="An open-source design system for building modern websites and applications."
        />
        <meta
          property="og:description"
          content="An open-source design system for building modern websites and applications."
        />
        <meta
          itemProp="image"
          property="og:image"
          content="https://user-images.githubusercontent.com/11304944/91128466-dfc96c00-e6da-11ea-8b03-a96e6b98667d.png"
        />
        <meta
          property="og:image"
          content="https://user-images.githubusercontent.com/11304944/91128466-dfc96c00-e6da-11ea-8b03-a96e6b98667d.png"
        />
        <meta
          property="twitter:image"
          content="https://user-images.githubusercontent.com/11304944/91128466-dfc96c00-e6da-11ea-8b03-a96e6b98667d.png"
        />
        <meta
          name="viewport"
          content="initial-scale=1, maximum-scale=1, minimum-scale=1, viewport-fit=cover"
        />
      </Head>
      <GeistProvider themeType={themeType} themes={[customTheme]}>
        <CssBaseline />
        <ConfigContext
          onThemeChange={themeChangeHandle}
          onThemeTypeChange={type => setThemeType(type)}>
          <Menu />
          <Search />
          <MDXProvider
            components={{
              a: HybridLink,
              img: Image,
              pre: HybridCode,
            }}>
            <Component {...pageProps} />
          </MDXProvider>
        </ConfigContext>
        <style global jsx>{`
          .tag {
            color: ${theme.palette.accents_5};
          }
          .punctuation {
            color: ${theme.palette.accents_5};
          }
          .attr-name {
            color: ${theme.palette.accents_6};
          }
          .attr-value {
            color: ${theme.palette.accents_4};
          }
          .language-javascript {
            color: ${theme.palette.accents_4};
          }
          span.class-name {
            color: ${theme.palette.warning};
          }
          span.maybe-class-name {
            color: ${theme.palette.purple};
          }
          span.token.string {
            color: ${theme.palette.accents_5};
          }
          span.token.comment {
            color: ${theme.palette.accents_3};
          }
          span.keyword {
            color: ${theme.palette.success};
          }
          span.plain-text {
            color: ${theme.palette.accents_3};
          }
          body::-webkit-scrollbar {
            width: var(--geist-page-scrollbar-width);
            background-color: ${theme.palette.accents_1};
          }
          body::-webkit-scrollbar-thumb {
            background-color: ${theme.palette.accents_2};
            border-radius: ${theme.layout.radius};
          }
          :root {
            --geist-page-nav-height: 64px;
            --geist-page-scrollbar-width: 4px;
          }
        `}</style>
      </GeistProvider>
    </>
  )
}