{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "time-chip",
  "type": "registry:ui",
  "title": "Time Chip",
  "description": "00–23시 또는 00–55분을 칩 그리드로 펼쳐 여러 개를 고르는 선택 컨트롤입니다. 드래그로 구간을 한 번에 고릅니다.",
  "registryDependencies": [
    "@atom/tokens",
    "@atom/button",
    "@atom/lib-bem",
    "@atom/lib-cn",
    "@atom/lib-use-controllable-state"
  ],
  "files": [
    {
      "path": "poc/design-system/implement/src/components/time-chip.tsx",
      "content": "\"use client\";\n\nimport {\n  type CSSProperties,\n  type HTMLAttributes,\n  type KeyboardEvent,\n  type PointerEvent as ReactPointerEvent,\n  type Ref,\n  useEffect,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { mod } from \"../lib/bem\";\nimport { cn } from \"../lib/cn\";\nimport { useControllableState } from \"../lib/use-controllable-state\";\nimport { Button } from \"./button\";\n\n/* -------------------------------------------------------------------------- */\n/*  Time Chip (components/time-chip.md, styles/atom/elements/time-chip.css)   */\n/*    <TimeChip label=\"업무 시간 선택\" title=\"업무 시간\"                         */\n/*              value={hours} onChange={setHours} />                          */\n/*    <TimeChip unit=\"minute\" step={15} columns={4} label=\"분 선택\" />          */\n/*  칸 하나는 Chip(type_checkbox) 마크업을 그대로 씁니다 (chip.css 소유).        */\n/* -------------------------------------------------------------------------- */\n\nexport type TimeChipUnit = \"hour\" | \"minute\";\nexport type TimeChipStep = 5 | 10 | 15 | 30;\nexport type TimeChipSize = \"small\" | \"medium\";\n\nconst B = \"atom-time-chip\";\n\nexport type TimeChipProps = Omit<HTMLAttributes<HTMLDivElement>, \"defaultValue\" | \"onChange\" | \"title\"> & {\n  ref?: Ref<HTMLDivElement>;\n  /** 고르는 단위입니다. 값의 집합이 여기서 정해집니다. */\n  unit?: TimeChipUnit;\n  /** 분 간격입니다. `unit: \"minute\"` 에서만 씁니다. */\n  step?: TimeChipStep;\n  /** 선택된 값입니다. 시는 0–23, 분은 0–55. 주면 controlled 입니다. */\n  value?: number[];\n  /** uncontrolled 일 때 처음 선택된 값입니다. */\n  defaultValue?: number[];\n  /** 그리드 열 수입니다. */\n  columns?: number;\n  /** 고를 수 없는 값입니다. 범위 드래그가 지나가도 바뀌지 않습니다. */\n  disabledValues?: number[];\n  /** 드래그로 구간을 고릅니다. */\n  rangeSelect?: boolean;\n  /** 전체 선택 · 전체 해제 버튼을 둡니다. */\n  showBulkActions?: boolean;\n  /** Toolbar 의 이름입니다. 없으면 Toolbar 는 개수와 버튼만 담습니다. */\n  title?: string;\n  /** 이름 뒤에 `9 / 24` 를 붙입니다. */\n  showCount?: boolean;\n  /** 칩 높이와 칸 간격입니다. */\n  size?: TimeChipSize;\n  /** 그리드의 `aria-label` 입니다. */\n  label: string;\n  /** 선택이 바뀔 때 호출됩니다. 드래그는 **손을 뗀 시점에 한 번** 부릅니다. */\n  onChange?: (value: number[]) => void;\n};\n\ntype Drag = { anchor: number; current: number; on: boolean; pointerId: number };\n\nconst pad = (n: number) => String(n).padStart(2, \"0\");\nconst sorted = (set: Iterable<number>) => [...set].sort((a, b) => a - b);\n\nfunction TimeChipRoot({\n  ref,\n  unit = \"hour\",\n  step = 5,\n  value: valueProp,\n  defaultValue = [],\n  columns = 12,\n  disabledValues = [],\n  rangeSelect = true,\n  showBulkActions = true,\n  title,\n  showCount = true,\n  size = \"medium\",\n  label,\n  onChange,\n  className,\n  style,\n  ...rest\n}: TimeChipProps) {\n  const [value, setValue] = useControllableState({ value: valueProp, defaultValue, onChange });\n  const [drag, setDrag] = useState<Drag | null>(null);\n  const [announce, setAnnounce] = useState(\"\");\n  /** 포인터 제스처가 선택을 이미 확정했으면, 뒤따르는 label click 의 기본 토글을 막습니다. */\n  const suppressClick = useRef(false);\n  const inputs = useRef(new Map<number, HTMLInputElement>());\n  /** Shift + 클릭 범위의 기준 칸과 그 칸에 적용된 상태 */\n  const lastToggle = useRef<{ value: number; on: boolean } | null>(null);\n\n  const values =\n    unit === \"hour\"\n      ? Array.from({ length: 24 }, (_, i) => i)\n      : Array.from({ length: Math.floor(60 / step) }, (_, i) => i * step);\n  const disabled = new Set(disabledValues);\n  const selected = new Set(value);\n  const selectable = values.filter((v) => !disabled.has(v));\n  const selectedCount = values.filter((v) => selected.has(v)).length;\n  const unitWord = unit === \"hour\" ? \"시\" : \"분\";\n\n  /** values 순서로 a~b 사이(양 끝 포함)의 고를 수 있는 값 */\n  const between = (a: number, b: number) => {\n    const ia = values.indexOf(a);\n    const ib = values.indexOf(b);\n    const [lo, hi] = ia <= ib ? [ia, ib] : [ib, ia];\n    return values.slice(lo, hi + 1).filter((v) => !disabled.has(v));\n  };\n\n  const commit = (cells: number[], on: boolean, live: boolean) => {\n    const next = new Set(selected);\n    for (const v of cells) {\n      if (disabled.has(v)) continue;\n      if (on) next.add(v);\n      else next.delete(v);\n    }\n    const out = sorted(next);\n    const prev = sorted(value);\n    if (out.length === prev.length && out.every((v, i) => v === prev[i])) return;\n    setValue(out);\n    if (live) setAnnounce(`${values.filter((v) => next.has(v)).length}개 선택됨`);\n  };\n\n  // 손을 뗀 시점에 한 번 확정 — 포인터가 그리드 밖에서 올라와도 받도록 document 에서 듣습니다.\n  const dragRef = useRef(drag);\n  const commitRef = useRef(commit);\n  const betweenRef = useRef(between);\n  useLayoutEffect(() => {\n    dragRef.current = drag;\n    commitRef.current = commit;\n    betweenRef.current = between;\n  });\n  useEffect(() => {\n    if (!drag) return;\n    const end = (e: PointerEvent) => {\n      const d = dragRef.current;\n      if (!d || e.pointerId !== d.pointerId) return;\n      const cells = betweenRef.current(d.anchor, d.current);\n      commitRef.current(cells, d.on, cells.length > 1);\n      lastToggle.current = { value: d.current, on: d.on };\n      // 바로 뒤따르는 label click 만 막습니다 (같은 입력 처리 안에서 발생).\n      suppressClick.current = true;\n      setTimeout(() => {\n        suppressClick.current = false;\n      });\n      setDrag(null);\n    };\n    const cancel = (e: PointerEvent) => {\n      if (dragRef.current && e.pointerId === dragRef.current.pointerId) setDrag(null);\n    };\n    document.addEventListener(\"pointerup\", end);\n    document.addEventListener(\"pointercancel\", cancel);\n    return () => {\n      document.removeEventListener(\"pointerup\", end);\n      document.removeEventListener(\"pointercancel\", cancel);\n    };\n  }, [drag !== null]); // eslint-disable-line react-hooks/exhaustive-deps -- 드래그 시작·끝에만 다시 겁니다\n\n  const onPointerDown = (v: number) => (e: ReactPointerEvent<HTMLElement>) => {\n    if (e.button !== 0) return;\n    suppressClick.current = false;\n    // Shift + 클릭: 직전에 누른 칸부터 이 칸까지를 직전 상태로 맞춥니다 (키보드·포인터 공용 대안).\n    if (e.shiftKey && lastToggle.current && values.includes(lastToggle.current.value)) {\n      e.preventDefault();\n      const { value: from, on } = lastToggle.current;\n      commit(between(from, v), on, true);\n      lastToggle.current = { value: v, on };\n      suppressClick.current = true;\n      setTimeout(() => {\n        suppressClick.current = false;\n      });\n      return;\n    }\n    if (!rangeSelect) return;\n    // 터치는 누른 요소에 포인터가 묶이므로 풀어 줘야 지나가는 칸이 pointerover 를 받습니다.\n    const target = e.target as Element;\n    if (target.hasPointerCapture?.(e.pointerId)) target.releasePointerCapture(e.pointerId);\n    // 시작한 칩의 상태가 반대로 뒤집히고, 구간 전체가 그 상태를 따릅니다.\n    setDrag({ anchor: v, current: v, on: !selected.has(v), pointerId: e.pointerId });\n  };\n\n  const onPointerOver = (v: number) => (e: ReactPointerEvent<HTMLElement>) => {\n    if (drag && e.pointerId === drag.pointerId && drag.current !== v) setDrag({ ...drag, current: v });\n  };\n\n  const onKeyDown = (v: number) => (e: KeyboardEvent<HTMLInputElement>) => {\n    // Shift + 화살표: 이웃 칸으로 초점을 옮기며 지금 칸의 상태를 이어 칠합니다.\n    if (!e.shiftKey) return;\n    const delta = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -columns, ArrowDown: columns }[e.key];\n    if (delta === undefined) return;\n    e.preventDefault();\n    let i = values.indexOf(v) + delta;\n    while (i >= 0 && i < values.length && disabled.has(values[i]!)) i += delta > 0 ? 1 : -1;\n    const next = values[i];\n    if (next === undefined) return;\n    const on = selected.has(v);\n    commit(between(v, next), on, true);\n    lastToggle.current = { value: next, on };\n    inputs.current.get(next)?.focus();\n  };\n\n  const range = drag ? new Set(between(drag.anchor, drag.current)) : null;\n  const allSelected = selectable.every((v) => selected.has(v));\n  const noneSelected = selectable.every((v) => !selected.has(v));\n  const hasToolbar = title !== undefined || showCount || showBulkActions;\n\n  return (\n    <div\n      {...rest}\n      ref={ref}\n      style={style}\n      data-dragging={drag ? \"\" : undefined}\n      className={cn(B, mod(B, \"size\", size, \"medium\"), className)}\n    >\n      {hasToolbar && (\n        <div className={`${B}__toolbar`}>\n          <span className={`${B}__title`}>\n            {title}\n            {showCount && (\n              <span className={`${B}__count`}>\n                {selectedCount} / {values.length}\n              </span>\n            )}\n          </span>\n          {showBulkActions && (\n            <span className={`${B}__actions`}>\n              <Button\n                variant=\"neutralOutline\"\n                size=\"xsmall\"\n                disabled={allSelected}\n                onClick={() => commit(selectable, true, true)}\n              >\n                전체 선택\n              </Button>\n              <Button\n                variant=\"neutralOutline\"\n                size=\"xsmall\"\n                disabled={noneSelected}\n                onClick={() => commit(selectable, false, true)}\n              >\n                전체 해제\n              </Button>\n            </span>\n          )}\n        </div>\n      )}\n\n      <div\n        className={`${B}__grid`}\n        role=\"group\"\n        aria-label={label}\n        style={{ \"--atom-time-chip-cols\": columns } as CSSProperties}\n      >\n        {values.map((v) => {\n          const chip = cn(\"atom-chip\", \"atom-chip--type_checkbox\", mod(\"atom-chip\", \"size\", size, \"medium\"));\n          const inRange = range?.has(v) ?? false;\n          const isAnchor = drag?.anchor === v;\n          return (\n            <span\n              key={v}\n              className={`${B}__item`}\n              data-range={inRange || isAnchor ? \"\" : undefined}\n              data-anchor={isAnchor ? \"\" : undefined}\n              onPointerOver={onPointerOver(v)}\n            >\n              {disabled.has(v) ? (\n                <span className={chip} aria-disabled=\"true\">\n                  <span className=\"atom-chip__label\">{pad(v)}</span>\n                </span>\n              ) : (\n                <label\n                  className={chip}\n                  onPointerDown={onPointerDown(v)}\n                  onClick={(e) => {\n                    // 키보드 Space 는 input 에서 시작한 click 이므로 막지 않습니다.\n                    if (suppressClick.current && !(e.target instanceof HTMLInputElement)) {\n                      suppressClick.current = false;\n                      e.preventDefault();\n                    }\n                  }}\n                >\n                  <input\n                    ref={(el) => {\n                      if (el) inputs.current.set(v, el);\n                      else inputs.current.delete(v);\n                    }}\n                    className=\"atom-chip__input\"\n                    type=\"checkbox\"\n                    checked={selected.has(v)}\n                    aria-label={`${v}${unitWord}`}\n                    onChange={(e) => {\n                      commit([v], e.currentTarget.checked, false);\n                      lastToggle.current = { value: v, on: e.currentTarget.checked };\n                    }}\n                    onKeyDown={onKeyDown(v)}\n                  />\n                  <span className=\"atom-chip__label\">{pad(v)}</span>\n                </label>\n              )}\n            </span>\n          );\n        })}\n      </div>\n\n      <span className=\"atom-sr-only\" aria-live=\"polite\">\n        {announce}\n      </span>\n    </div>\n  );\n}\n\nexport const TimeChip = Object.assign(TimeChipRoot, {});\n",
      "type": "registry:ui",
      "target": "components/ui/time-chip.tsx"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/button.css",
      "content": "/* ============================================================================\n   Element — Button\n   ----------------------------------------------------------------------------\n   atom-button\n     --size_{xsmall|small|medium|large}      medium is the default\n     --variant_{brand-solid|neutral-solid|neutral-weak|critical-solid\n                |brand-outline|neutral-outline|ghost}\n     --layout_icon-only\n     --width_full\n   states: :hover  [data-pressed]  [disabled]  [data-loading]\n\n   Loaded by both the card preview and the detail page, so\n   this file is the only definition of the component.\n   ========================================================================== */\n\n.atom-button {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--atom-x2);\n  height: 47px;\n  padding-inline: var(--atom-x5);\n  border-radius: var(--atom-radius-r2);\n  font-size: 15px;\n  font-weight: 600;\n  line-height: 1;\n  white-space: nowrap;\n  user-select: none;\n  transition: filter var(--atom-duration-fast) ease,\n    background-color var(--atom-duration-fast) ease;\n}\n\n.atom-button__icon {\n  width: 18px;\n  height: 18px;\n  flex-shrink: 0;\n}\n\n.atom-button__label {\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n/* --- variants ------------------------------------------------------------- */\n.atom-button--variant_neutral-solid {\n  background: var(--atom-bg-neutral-solid);\n  color: var(--atom-fg-on-solid);\n}\n\n.atom-button--variant_brand-solid {\n  background: var(--atom-primary-600);\n  color: #fff;\n}\n\n.atom-button--variant_critical-solid {\n  background: var(--atom-bg-critical-solid);\n  color: #fff;\n}\n\n.atom-button--variant_neutral-weak {\n  background: var(--atom-bg-neutral-weak);\n  color: var(--atom-fg-neutral);\n}\n\n.atom-button--variant_brand-outline {\n  background: transparent;\n  color: var(--atom-primary-600);\n  box-shadow: inset 0 0 0 1px currentColor;\n}\n\n.atom-button--variant_neutral-outline {\n  background: transparent;\n  color: var(--atom-fg-neutral);\n  box-shadow: inset 0 0 0 1px var(--atom-border-default);\n}\n\n.atom-button--variant_ghost {\n  background: transparent;\n  color: var(--atom-fg-neutral);\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-button--size_xsmall {\n  height: 32px;\n  gap: var(--atom-x1);\n  padding-inline: var(--atom-x3);\n  border-radius: var(--atom-radius-full);\n  font-size: 13px;\n}\n\n.atom-button--size_xsmall .atom-button__icon {\n  width: 14px;\n  height: 14px;\n}\n\n.atom-button--size_small {\n  height: 40px;\n  padding-inline: var(--atom-x4);\n  font-size: 14px;\n}\n\n.atom-button--size_small .atom-button__icon {\n  width: 16px;\n  height: 16px;\n}\n\n.atom-button--size_large {\n  height: 54px;\n  padding-inline: var(--atom-x6);\n  border-radius: var(--atom-radius-r3);\n  font-size: 16px;\n}\n\n.atom-button--size_large .atom-button__icon {\n  width: 20px;\n  height: 20px;\n}\n\n/* --- layout -------------------------------------------------------------- */\n.atom-button--layout_icon-only {\n  width: 47px;\n  padding-inline: 0;\n}\n\n.atom-button--layout_icon-only.atom-button--size_xsmall {\n  width: 32px;\n}\n\n.atom-button--layout_icon-only.atom-button--size_small {\n  width: 40px;\n}\n\n.atom-button--layout_icon-only.atom-button--size_large {\n  width: 54px;\n}\n\n.atom-button--width_full {\n  width: 100%;\n}\n\n/* --- states -------------------------------------------------------------- */\n.atom-button:hover:not([disabled]):not([data-loading]) {\n  filter: brightness(0.94);\n}\n\n.atom-button--variant_ghost:hover:not([disabled]),\n.atom-button--variant_neutral-outline:hover:not([disabled]),\n.atom-button--variant_brand-outline:hover:not([disabled]) {\n  filter: none;\n  background: var(--atom-bg-neutral-weak);\n}\n\n.atom-button[data-pressed] {\n  filter: brightness(0.88);\n}\n\n.atom-button[disabled] {\n  opacity: 0.35;\n  cursor: not-allowed;\n  filter: none;\n}\n\n.atom-button[data-loading] {\n  cursor: progress;\n}\n\n.atom-button[data-loading] .atom-button__label {\n  opacity: 0.45;\n}\n\n.atom-button__spinner {\n  width: 16px;\n  height: 16px;\n  flex-shrink: 0;\n  border-radius: var(--atom-radius-full);\n  border: 2px solid currentColor;\n  border-top-color: transparent;\n  animation: atom-button-spin 700ms linear infinite;\n}\n\n@keyframes atom-button-spin {\n  to {\n    transform: rotate(360deg);\n  }\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/button.css"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/chip.css",
      "content": "/* ============================================================================\n   Element — Chip\n   ----------------------------------------------------------------------------\n   One pill, four behaviours. What changes between them is not the look but the\n   contract: whether the chip holds a state at all, and if so whether that state\n   is exclusive within its group.\n\n     button     상태 없음. 누르면 액션이 일어난다\n     toggle     혼자서 켜고 끈다\n     radio      그룹 안에서 하나만\n     checkbox   그룹 안에서 여러 개\n\n   So `--type_*` is a documentation marker for the author, and the real\n   difference lives in the markup each type demands — see the Specification\n   table. The selected treatment is shared: whatever the unselected variant was,\n   a selected chip fills solid.\n\n   atom-chip\n     --type_{button|toggle|radio|checkbox}   button is the default\n     --variant_{neutral-weak|neutral-outline|neutral-solid}\n                                             neutral-weak is the default\n     --size_{small|medium}                   medium is the default\n     [data-selected]  [aria-pressed=\"true\"]  __input:checked\n     [disabled]  [aria-disabled=\"true\"]\n     __input     visually-hidden radio/checkbox behind a <label>\n     __prefix    leading icon\n     __label\n     __check     선택 표시. 넣으면 선택됐을 때만 보인다\n     __caret     메뉴를 여는 button 타입에서\n     __count     뒤에 붙는 개수\n\n   atom-chip-group  (+ --wrap  --scroll)\n   ========================================================================== */\n\n.atom-chip {\n  position: relative;\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n  height: 33px;\n  padding-inline: var(--atom-x3);\n  border-radius: var(--atom-radius-full);\n  font-size: 13px;\n  line-height: 1;\n  white-space: nowrap;\n  cursor: pointer;\n  user-select: none;\n  transition: background-color var(--atom-duration-fast) ease,\n    box-shadow var(--atom-duration-fast) ease,\n    color var(--atom-duration-fast) ease;\n}\n\n/* radio and checkbox chips are a real input behind a label, so the control is\n   removed from view but not from the accessibility tree or the tab order. */\n.atom-chip__input {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  opacity: 0;\n  pointer-events: none;\n}\n\n.atom-chip__label {\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.atom-chip__prefix {\n  width: 14px;\n  height: 14px;\n  flex-shrink: 0;\n  margin-inline-start: -2px;\n}\n\n.atom-chip__caret {\n  width: 12px;\n  height: 12px;\n  flex-shrink: 0;\n  margin-inline-end: -2px;\n  opacity: 0.7;\n  transition: transform var(--atom-duration-base) var(--atom-ease-out);\n}\n\n/* The check is opt-in and costs width, so it collapses instead of merely fading\n   — a row of chips that reserves space for a mark none of them shows reads as\n   badly aligned rather than as unselected. */\n.atom-chip__check {\n  width: 0;\n  height: 14px;\n  flex-shrink: 0;\n  opacity: 0;\n  transition: width var(--atom-duration-fast) ease,\n    opacity var(--atom-duration-fast) ease;\n}\n\n.atom-chip__count {\n  flex-shrink: 0;\n  font-variant-numeric: tabular-nums;\n  opacity: 0.7;\n}\n\n/* --- types — guardrails, not decoration ---------------------------------- */\n/* These modifiers mostly document which contract a chip follows; the real\n   difference is the markup each type demands. The two rules below are the\n   exceptions, and both exist to stop wrong markup from shipping a wrong signal.\n\n   A button chip holds no state, so a check on one can only mislead. */\n.atom-chip--type_button .atom-chip__check {\n  display: none;\n}\n\n/* A caret promises \"this opens a menu\". The stateful three change a value in\n   place, so a caret on them is a promise the chip does not keep. */\n.atom-chip--type_toggle .atom-chip__caret,\n.atom-chip--type_radio .atom-chip__caret,\n.atom-chip--type_checkbox .atom-chip__caret {\n  display: none;\n}\n\n/* --- variants — the unselected look -------------------------------------- */\n.atom-chip,\n.atom-chip--variant_neutral-weak {\n  background: var(--atom-bg-neutral-weak);\n  color: var(--atom-fg-neutral);\n}\n\n.atom-chip--variant_neutral-outline {\n  background: transparent;\n  box-shadow: inset 0 0 0 1px var(--atom-border-default);\n  color: var(--atom-fg-neutral);\n}\n\n.atom-chip--variant_neutral-solid {\n  background: var(--atom-bg-neutral-solid);\n  color: var(--atom-fg-on-solid);\n  font-weight: 600;\n}\n\n/* --- selected — shared by toggle, radio and checkbox ---------------------- */\n/* One rule for three types: whatever the chip looked like unselected, selecting\n   it fills solid. Fill is the only difference a chip can carry that survives\n   being read at a glance in a scrolling row. */\n.atom-chip[data-selected],\n.atom-chip[aria-pressed=\"true\"],\n.atom-chip:has(.atom-chip__input:checked) {\n  background: var(--atom-bg-neutral-solid);\n  box-shadow: none;\n  color: var(--atom-fg-on-solid);\n  font-weight: 600;\n}\n\n.atom-chip[data-selected] .atom-chip__check,\n.atom-chip[aria-pressed=\"true\"] .atom-chip__check,\n.atom-chip:has(.atom-chip__input:checked) .atom-chip__check {\n  width: 14px;\n  opacity: 1;\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-chip--size_small {\n  height: 28px;\n  padding-inline: var(--atom-x2);\n  font-size: 12px;\n}\n\n.atom-chip--size_small .atom-chip__prefix,\n.atom-chip--size_small .atom-chip__check {\n  height: 12px;\n}\n\n.atom-chip--size_small[data-selected] .atom-chip__check,\n.atom-chip--size_small[aria-pressed=\"true\"] .atom-chip__check,\n.atom-chip--size_small:has(.atom-chip__input:checked) .atom-chip__check {\n  width: 12px;\n}\n\n/* --- states -------------------------------------------------------------- */\n.atom-chip:hover:not([disabled]):not([aria-disabled=\"true\"]) {\n  filter: brightness(0.96);\n}\n\n.atom-chip[data-state=\"open\"] .atom-chip__caret {\n  transform: rotate(180deg);\n}\n\n.atom-chip:focus-visible,\n.atom-chip:has(.atom-chip__input:focus-visible) {\n  outline: 2px solid var(--atom-fg-brand);\n  outline-offset: 2px;\n}\n\n.atom-chip[disabled],\n.atom-chip[aria-disabled=\"true\"] {\n  opacity: 0.4;\n  cursor: not-allowed;\n}\n\n/* --- group --------------------------------------------------------------- */\n.atom-chip-group {\n  display: flex;\n  align-items: center;\n  gap: var(--atom-x2);\n  min-width: 0;\n}\n\n.atom-chip-group--wrap {\n  flex-wrap: wrap;\n  row-gap: var(--atom-x2);\n}\n\n/* A single-line rail. The row keeps its height when it scrolls, which a wrapping\n   group cannot promise, so filter bars pinned under a header use this one. */\n.atom-chip-group--scroll {\n  flex-wrap: nowrap;\n  overflow-x: auto;\n  scrollbar-width: none;\n}\n\n.atom-chip-group--scroll::-webkit-scrollbar {\n  display: none;\n}\n\n.atom-chip-group--scroll > .atom-chip {\n  flex-shrink: 0;\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/chip.css"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/time-chip.css",
      "content": "/* ============================================================================\n   Element — Time Chip\n   ----------------------------------------------------------------------------\n   A fixed set of times laid out as a grid of chips: 00–23 for hours, 00–55 in\n   five-minute steps for minutes. Every value is on screen at once, so choosing\n   \"08, 09, 10 and 14\" is four clicks with no list to scroll and no range\n   arithmetic to do in your head.\n\n   It does not restyle the chip. `.atom-chip--type_checkbox` already owns the\n   pill, the selected fill and the disabled treatment, and a second definition\n   of those would drift from the first. What this element owns is the three\n   things a chip on its own cannot do: put the values in a grid so columns line\n   up, offer select-all / clear, and paint a drag across a range.\n\n   The grid is columns, not a wrapping row. A wrapping row breaks wherever the\n   container happens to end, so 14:00 lands under a different column every time\n   the panel resizes and the set stops being scannable. 12 columns is the\n   default because both sets divide by it — hours into 2 rows, minutes into 1.\n\n   atom-time-chip\n     --size_{small|medium}          medium is the default; passes through to the chips\n     [data-dragging]                a range drag is in progress\n     __toolbar    __title  __actions\n     __grid       --atom-time-chip-cols on the element sets the column count\n     __item       the chip wrapper — carries range state, not the chip\n   ========================================================================== */\n\n.atom-time-chip {\n  --atom-time-chip-cols: 12;\n\n  display: flex;\n  flex-direction: column;\n  gap: var(--atom-x3);\n  min-width: 0;\n}\n\n/* --- toolbar -------------------------------------------------------------- */\n/* Same shape as Multi Select's: a name on the left, the bulk actions on the\n   right, a hairline under it. The two controls read as one family because they\n   do the same job in both places. */\n.atom-time-chip__toolbar {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--atom-x3);\n  padding-bottom: var(--atom-x2);\n  border-bottom: 1px solid var(--atom-border-subtle);\n}\n\n.atom-time-chip__title {\n  min-width: 0;\n  font-size: 13px;\n  color: var(--atom-fg-neutral);\n}\n\n/* The count is the only thing that answers \"how many did I pick?\" without\n   counting fills, and a 24-cell grid is too many to count by eye. */\n.atom-time-chip__count {\n  margin-inline-start: var(--atom-x2);\n  font-variant-numeric: tabular-nums;\n  color: var(--atom-fg-neutral-muted);\n}\n\n.atom-time-chip__actions {\n  display: flex;\n  align-items: center;\n  gap: var(--atom-x2);\n  flex-shrink: 0;\n}\n\n/* --- grid ----------------------------------------------------------------- */\n.atom-time-chip__grid {\n  display: grid;\n  grid-template-columns: repeat(var(--atom-time-chip-cols, 12), minmax(0, 1fr));\n  gap: var(--atom-x2);\n  min-width: 0;\n}\n\n/* The wrapper, not the chip, carries the range state. A chip is reused from\n   chip.css and must not grow attributes that only make sense here; and the drag\n   ring needs a box that is not already spending its background on the selected\n   fill. */\n.atom-time-chip__item {\n  position: relative;\n  display: flex;\n  min-width: 0;\n  border-radius: var(--atom-radius-full);\n}\n\n/* Grid items stretch, so every cell is the same width whatever the digits are.\n   `justify-content: center` is what puts the number in the middle of that\n   stretched box — the chip's own layout is start-aligned for label + icon. */\n.atom-time-chip__item > .atom-chip {\n  width: 100%;\n  justify-content: center;\n  padding-inline: var(--atom-x2);\n}\n\n/* Tabular figures: 00 and 11 are different widths in proportional digits, and\n   in a grid of two-digit numbers that shows up as a wobbling column. */\n.atom-time-chip__item > .atom-chip .atom-chip__label {\n  font-variant-numeric: tabular-nums;\n}\n\n/* --- range drag ----------------------------------------------------------- */\n/* Three states have to stay apart while a drag is in flight: what is already\n   selected, what the drag is about to do, and where the drag started.\n\n   The pending band is a ring, not a fill. The selected state is a fill, so a\n   second fill would leave the user comparing two shades to work out which cells\n   are committed and which are still hypothetical. A ring sits on top of either\n   state and reads the same on both. */\n.atom-time-chip__item[data-range] {\n  box-shadow: 0 0 0 2px var(--atom-fg-brand);\n}\n\n/* Pending cells that are not yet selected also take a wash, so the band reads as\n   one continuous run rather than a row of separate rings.\n\n   `--atom-bg-side-active` and not `--atom-bg-informative-weak`: the informative\n   token measures rgb(241,249,251) against the chip's own rgb(243,243,245), a\n   six-value shift that does not survive being looked at. This one is a 9% brand\n   tint, which composites to a cell you can pick out of the row. */\n.atom-time-chip__item[data-range] > .atom-chip:not([data-selected]):not(\n    :has(.atom-chip__input:checked)\n  ) {\n  background: var(--atom-bg-side-active);\n}\n\n/* Where the finger went down. Keeping it visible is what makes a drag\n   correctable — the user can see which end is pinned while the other end moves.\n\n   A second ring outside the first, with a gap. Going thicker instead would read\n   as \"more of the same cell\" rather than a different kind of cell, and the near\n   white ring this used to draw disappeared against the stage. */\n.atom-time-chip__item[data-anchor] {\n  outline: 2px solid var(--atom-fg-brand);\n  outline-offset: 2px;\n}\n\n/* Text selection is the browser's default answer to press-and-drag, and it\n   turns a range drag into a page full of highlighted numbers. */\n.atom-time-chip[data-dragging] {\n  user-select: none;\n}\n\n.atom-time-chip[data-dragging] .atom-time-chip__item > .atom-chip {\n  cursor: grabbing;\n}\n\n/* --- sizes ---------------------------------------------------------------- */\n/* The size passes through to the chips, which own the height. This element only\n   tightens the gaps so a small grid reads as one block rather than a scatter. */\n.atom-time-chip--size_small {\n  gap: var(--atom-x2);\n}\n\n.atom-time-chip--size_small .atom-time-chip__grid {\n  gap: 4px;\n}\n\n.atom-time-chip--size_small .atom-time-chip__title {\n  font-size: 12px;\n}\n\n/* --- motion --------------------------------------------------------------- */\n@media (prefers-reduced-motion: reduce) {\n  .atom-time-chip__item {\n    transition: none;\n  }\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/time-chip.css"
    }
  ],
  "categories": [
    "selection"
  ],
  "meta": {
    "version": "0.1.0",
    "kind": "component",
    "status": "stable",
    "specSource": "measured",
    "parts": [
      "TimeChip"
    ],
    "docs": "poc/design-system/components/time-chip.md"
  }
}
