HextaUI

Marker

Notas discretas entre o conteúdo, como divisores de data e eventos do sistema, com datas e horários fixos que aparecem como Hoje ou Ontem.

Mira added Jun and Sol
Kicking off the marker component. Dates, events, the works.
Can the dates stick while you scroll?
Jun renamed the thread to “Marker”
They do now. Scroll up and the date turns into a pill.
Sol pinned a message
Docs are written.
Screenshots look right in dark mode too.
Sol merged #482 into main
New messages
Shipping it 🎉
"use client"

import * as React from "react"
import {
  IconGitMerge,
  IconPencil,
  IconPin,
  IconUserPlus,
} from "@tabler/icons-react"

import { Bubble, BubbleContent, BubbleGroup } from "@/components/ui/bubble"
import {
  Marker,
  MarkerContent,
  MarkerIcon,
  MarkerTime,
} from "@/components/ui/marker"

const day = 24 * 60 * 60 * 1000

export function MarkerDemo() {
  const threadRef = React.useRef<HTMLDivElement>(null)

  React.useLayoutEffect(() => {
    const thread = threadRef.current
    if (thread) {
      thread.scrollTop = thread.scrollHeight
    }
  }, [])

  return (
    <div
      ref={threadRef}
      tabIndex={0}
      role="region"
      aria-label="Conversation"
      className="h-96 w-full max-w-md overflow-y-auto overscroll-none rounded-xl border px-4 outline-none focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:outline-hidden"
    >
      <section className="flex flex-col gap-3 pt-4 pb-3">
        <Marker variant="separator" sticky>
          <MarkerContent>
            <MarkerTime date={new Date(Date.now() - 2 * day)} />
          </MarkerContent>
        </Marker>
        <Marker className="justify-center">
          <MarkerIcon>
            <IconUserPlus />
          </MarkerIcon>
          <MarkerContent>Mira added Jun and Sol</MarkerContent>
        </Marker>
        <Bubble variant="secondary">
          <BubbleContent>
            Kicking off the marker component. Dates, events, the works.
          </BubbleContent>
        </Bubble>
        <Bubble align="end">
          <BubbleContent>Can the dates stick while you scroll?</BubbleContent>
        </Bubble>
      </section>
      <section className="flex flex-col gap-3 py-3">
        <Marker variant="separator" sticky>
          <MarkerContent>
            <MarkerTime date={new Date(Date.now() - day)} />
          </MarkerContent>
        </Marker>
        <Marker className="justify-center">
          <MarkerIcon>
            <IconPencil />
          </MarkerIcon>
          <MarkerContent>Jun renamed the thread to “Marker”</MarkerContent>
        </Marker>
        <Bubble variant="secondary">
          <BubbleContent>
            They do now. Scroll up and the date turns into a pill.
          </BubbleContent>
        </Bubble>
        <Marker className="justify-center">
          <MarkerIcon>
            <IconPin />
          </MarkerIcon>
          <MarkerContent>Sol pinned a message</MarkerContent>
        </Marker>
      </section>
      <section className="flex flex-col gap-3 pt-3 pb-4">
        <Marker variant="separator" sticky>
          <MarkerContent>
            <MarkerTime date={new Date()} />
          </MarkerContent>
        </Marker>
        <BubbleGroup>
          <Bubble align="end">
            <BubbleContent>Docs are written.</BubbleContent>
          </Bubble>
          <Bubble align="end">
            <BubbleContent>
              Screenshots look right in dark mode too.
            </BubbleContent>
          </Bubble>
        </BubbleGroup>
        <Marker className="justify-center">
          <MarkerIcon>
            <IconGitMerge className="text-success" />
          </MarkerIcon>
          <MarkerContent>
            Sol merged <a href="#">#482</a> into main
          </MarkerContent>
        </Marker>
        <Marker variant="separator">
          <MarkerContent>New messages</MarkerContent>
        </Marker>
        <Bubble variant="secondary">
          <BubbleContent>Shipping it 🎉</BubbleContent>
        </Bubble>
      </section>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/marker.json

Adiciona o componente, os tokens de tema do HextaUI e quaisquer componentes do HextaUI dos quais ele depende.

import {
  Marker,
  MarkerContent,
  MarkerIcon,
  MarkerTime,
} from "@/components/ui/marker"
<Marker variant="separator" sticky>
  <MarkerContent>
    <MarkerTime date={message.sentAt} />
  </MarkerContent>
</Marker>
Marker
├── MarkerIcon
└── MarkerContent
    └── MarkerTime

Variantes

default é uma nota discreta, separator a centraliza entre duas linhas finas e border a sublinha como um título de seção.

import { IconInfoCircle } from "@tabler/icons-react"

import { Marker, MarkerContent, MarkerIcon } from "@/components/ui/marker"

export function MarkerVariants() {
  return (
    <div className="flex w-full max-w-md flex-col gap-6">
      <Marker>
        <MarkerIcon>
          <IconInfoCircle />
        </MarkerIcon>
        <MarkerContent>Default: an icon and a short note.</MarkerContent>
      </Marker>
      <Marker variant="separator">
        <MarkerContent>Separator</MarkerContent>
      </Marker>
      <Marker variant="border">
        <MarkerContent>Border: a heading for the section below</MarkerContent>
      </Marker>
    </div>
  )
}

Datas fixas

Com sticky, um marcador permanece no topo da sua área de rolagem. Depois de fixado, suas linhas desaparecem e o rótulo vira uma pílula flutuante para continuar legível sobre as mensagens. Coloque cada dia em sua própria seção para que a próxima data empurre a anterior.

import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Marker, MarkerContent, MarkerTime } from "@/components/ui/marker"

const day = 24 * 60 * 60 * 1000

const days = [
  {
    date: new Date(Date.now() - 3 * day),
    messages: [
      "Kicking off the marker component today.",
      "Shadcn's version is a good base. Let's add sticky dates.",
      "Agreed, chats need them.",
    ],
  },
  {
    date: new Date(Date.now() - day),
    messages: [
      "Sticky dates work. They turn into a pill when they stick.",
      "Nice. Does it respect reduced motion?",
      "Yes, the pill just appears.",
    ],
  },
  {
    date: new Date(),
    messages: ["Docs are up.", "Shipping it.", "🎉"],
  },
]

export function MarkerSticky() {
  return (
    <div
      tabIndex={0}
      role="region"
      aria-label="Activity by day"
      className="h-80 w-full max-w-md overflow-y-auto overscroll-none rounded-xl border px-4 outline-none focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:outline-hidden"
    >
      {days.map((group) => (
        <section
          key={group.date.toDateString()}
          className="flex flex-col gap-3 py-3"
        >
          <Marker variant="separator" sticky>
            <MarkerContent>
              <MarkerTime date={group.date} />
            </MarkerContent>
          </Marker>
          {group.messages.map((message, index) => (
            <Bubble
              key={message}
              align={index % 2 === 0 ? "start" : "end"}
              variant={index % 2 === 0 ? "secondary" : "default"}
            >
              <BubbleContent>{message}</BubbleContent>
            </Bubble>
          ))}
        </section>
      ))}
    </div>
  )
}

Datas relativas

<MarkerTime /> renderiza um <time> que mostra Hoje, Ontem, um dia da semana dentro da última semana e, depois, uma data curta. Ele se atualiza sozinho à meia-noite. Passe format para usar sua própria redação.

"use client"

import { Marker, MarkerContent, MarkerTime } from "@/components/ui/marker"

const day = 24 * 60 * 60 * 1000
const dates = [0, 1, 3, 40, 400].map((ago) => new Date(Date.now() - ago * day))

export function MarkerTimeDemo() {
  return (
    <div className="flex w-full max-w-md flex-col gap-4">
      {dates.map((date) => (
        <Marker key={date.getTime()} variant="separator">
          <MarkerContent>
            <MarkerTime date={date} />
          </MarkerContent>
        </Marker>
      ))}
      <Marker variant="separator">
        <MarkerContent>
          <MarkerTime
            date={dates[0]}
            format={(date) =>
              date.toLocaleTimeString("en-US", {
                hour: "numeric",
                minute: "2-digit",
              })
            }
          />
        </MarkerContent>
      </Marker>
    </div>
  )
}

Atividade

Renderize os marcadores como itens de lista para um log de atividades. Links dentro deles ficam sublinhados até o hover.

import {
  IconGitMerge,
  IconLock,
  IconPencil,
  IconTag,
} from "@tabler/icons-react"

import { Marker, MarkerContent, MarkerIcon } from "@/components/ui/marker"

export function MarkerEvents() {
  return (
    <ol className="flex w-full max-w-md flex-col gap-3">
      <Marker render={<li />}>
        <MarkerIcon>
          <IconTag />
        </MarkerIcon>
        <MarkerContent>
          Jun added the <a href="#">design</a> label
        </MarkerContent>
      </Marker>
      <Marker render={<li />}>
        <MarkerIcon>
          <IconPencil />
        </MarkerIcon>
        <MarkerContent>Mira renamed the pull request</MarkerContent>
      </Marker>
      <Marker render={<li />}>
        <MarkerIcon>
          <IconGitMerge className="text-success" />
        </MarkerIcon>
        <MarkerContent>Sol merged into main</MarkerContent>
      </Marker>
      <Marker render={<li />}>
        <MarkerIcon>
          <IconLock />
        </MarkerIcon>
        <MarkerContent>Conversation locked</MarkerContent>
      </Marker>
    </ol>
  )
}

Conteúdo longo

As notas quebram linha e strings sem quebras são quebradas em vez de alargar o layout. As linhas separadoras mantêm um comprimento mínimo nos dois lados.

import { IconAlertTriangle } from "@tabler/icons-react"

import { Marker, MarkerContent, MarkerIcon } from "@/components/ui/marker"

export function MarkerLongContent() {
  return (
    <div className="flex w-72 max-w-full flex-col gap-6">
      <Marker>
        <MarkerIcon>
          <IconAlertTriangle className="text-warning" />
        </MarkerIcon>
        <MarkerContent>
          Messages older than 90 days were archived to
          [email protected]
        </MarkerContent>
      </Marker>
      <Marker variant="separator">
        <MarkerContent>
          A separator label that is long enough to wrap onto two lines
        </MarkerContent>
      </Marker>
    </div>
  )
}

Da direita para a esquerda

Ícones e texto seguem a direção de leitura.

import { IconUserPlus } from "@tabler/icons-react"

import { Marker, MarkerContent, MarkerIcon } from "@/components/ui/marker"

export function MarkerRtl() {
  return (
    <div dir="rtl" className="flex w-full max-w-md flex-col gap-4">
      <Marker variant="separator">
        <MarkerContent>اليوم</MarkerContent>
      </Marker>
      <Marker>
        <MarkerIcon>
          <IconUserPlus />
        </MarkerIcon>
        <MarkerContent>أضافت ميرا جون إلى المحادثة</MarkerContent>
      </Marker>
    </div>
  )
}
  • Os marcadores são texto simples, então os leitores de tela os leem no lugar. Os ícones ficam ocultos deles.
  • <MarkerTime /> mantém o momento exato em dateTime enquanto mostra um rótulo amigável.
  • A pílula fixa aparece sem movimento quando o movimento reduzido está ativado.
PropTipoPadrão
variant
"default" | "separator" | "border""default"
stickyFixe-se ao topo da área de rolagem e vire uma pílula enquanto fixado.
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescrição
data-slot="marker"Seleciona os marcadores no CSS.
data-variantA variante atual.
data-stickyPresente quando sticky está ativado.
data-stuckPresente enquanto o marcador está fixado ao topo.
--marker-sticky-topDistância do topo enquanto fixado. O padrão é 0.5rem.
AtributoDescrição
data-slot="marker-content"O texto. Vira a pílula enquanto fixado.
AtributoDescrição
data-slot="marker-icon"Uma caixa de ícone de 16px, oculta dos leitores de tela.
PropTipoPadrão
dateObrigatório. Qualquer valor que new Date() aceite.
Date | string | number–
localeFixo por padrão para que servidor e navegador concordem.
Intl.LocalesArgument"en-US"
formatSubstitua o rótulo relativo pelo seu.
(date: Date) => ReactNode–
AtributoDescrição
data-slot="marker-time"O elemento time, com um dateTime ISO.