Design
llms.txt digitalcrew.tech

Unibox

ChannelSegments

The Unibox channel filter: a rail of icons where one shared pill glides to the selected channel, and only that segment spells out its name.

MaxUniboxDigitalCrew.ChannelSegments
ChannelSegments · liveOpen

Guidelines#

Use it for switching the conversation list between All, Mail, LinkedIn, WhatsApp and Meetings. It fits any width, down to the 224px tablet rail.

What you provide

  • options: ChannelSegmentOption[], each { value, label, icon, needsAttention?, hint? }.
    • value is one of "all" | "email" | "linkedin" | "whatsapp" | "meeting".
    • needsAttention means an account stopped syncing.
    • hint is the tooltip's second line, such as "2 accounts".
  • value, onChange and className.

In the app, only channels the workspace actually has get a segment.

Anatomy

  • The rail is rounded-full border border-border/60 bg-muted/50 p-1 backdrop-blur-sm with gap-1.
  • Segments are h-8 rounded-full px-2.5 text-xs font-medium with size-3.5 icons, text-muted-foreground when inactive.
  • The selected segment takes the leftover width (flex-1) on a bg-background shadow-sm ring-1 ring-border/60 pill.
  • needsAttention adds a size-1.5 bg-amber-500 dot.

Motion: the pill (layoutId) and the expanding label move on SPRING_GLIDE, and the label exits in 0.12s. Reduced motion sets durations to 0.

Keyboard: the ARIA tabs pattern. Arrow keys, Home and End select, and the rail scrolls itself rather than the page.

Don't put counts inside the segments, and don't mount two rails at once: they share the pill's layoutId.

Facts#

GroupUnibox
ProductMax
Globalwindow.DigitalCrew.ChannelSegments
Sourcemax-agent: src/features/unibox/ui/components/channel-segments.tsx
Import in the appimport { ChannelSegments } from "@/features/unibox/ui/components/channel-segments";

API#

TypeScript declarations emitted from src/features/unibox/ui/components/channel-segments.tsx.

export interface ChannelSegmentOption {
    value: ChannelFilter;
    label: string;
    icon: LucideIcon;
    /**
     * An account behind this channel stopped syncing. The dot turns "this
     * channel looks empty" into "this channel has gone quiet, and here is why"
     * — the reconnect link itself lives in the alerts banner above the list.
     */
    needsAttention?: boolean;
    /** Second line of the tooltip, e.g. "2 accounts". */
    hint?: string;
}
interface ChannelSegmentsProps {
    options: ChannelSegmentOption[];
    value: ChannelFilter;
    onChange: (value: ChannelFilter) => void;
    className?: string;
}
export declare function ChannelSegments({ options, value, onChange, className, }: ChannelSegmentsProps): JSX.Element;

Example#

The code of the preview above: plain React.createElement against window.DigitalCrew, with realistic data.

<div id="root" class="p-6"></div>
<script>
(function () {
  var h = React.createElement, DC = window.DigitalCrew, I = DC.Icons;
  var OPTIONS = [
    { value: "all", label: "All", icon: I.Inbox },
    { value: "email", label: "Mail", icon: I.Mail, hint: "2 accounts" },
    { value: "linkedin", label: "LinkedIn", icon: I.Linkedin, hint: "1 account" },
    { value: "whatsapp", label: "WhatsApp", icon: I.MessageCircleMore, hint: "1 account, 1 needs reconnecting", needsAttention: true }
  ];
  var COUNTS = { all: 128, email: 74, linkedin: 41, whatsapp: 13 };
  function App() {
    var st = React.useState("all"), value = st[0], setValue = st[1];
    var touched = React.useRef(false);
    React.useEffect(function () {
      // Glide once on load so the pill and the expanding label are visible.
      var t = setTimeout(function () { if (!touched.current) setValue("linkedin"); }, 700);
      return function () { clearTimeout(t); };
    }, []);
    var current = OPTIONS.filter(function (o) { return o.value === value; })[0];
    return h("div", { className: "mx-auto flex w-[340px] flex-col gap-2" },
      h(DC.ChannelSegments, { options: OPTIONS, value: value, onChange: function (v) { touched.current = true; setValue(v); } }),
      h("div", { className: "flex items-center justify-between px-1 text-[11px] text-muted-foreground" },
        h("span", { className: "tabular-nums" }, COUNTS[value] + " conversations"),
        h("span", null, current.hint || "3 channels, 4 accounts")));
  }
  ReactDOM.createRoot(document.getElementById("root")).render(h(DC.TooltipProvider, { delayDuration: 80 }, h(App)));
})();
</script>