package webui import ( "strconv" "kjol/wasmruntime/vdom" ) // Port of web/kit/CrmTabs.tsx. // // CrmTabGroup / CrmSubTabGroup are behaviourally-identical siblings of TabGroup // with a different look: CrmTabGroup is boxed tabs with a sky-blue top accent; // CrmSubTabGroup is a segmented control. Content sits flat beneath the tab row. // // NOTE: As in Tabs.tsx, the TSX localStorage persistence (storageKey) and // cross-component "storage"-event syncing are browser-only and dropped; // selection collapses to a plain ActiveIndex value + OnTabChange callback (the // caller owns state). The TSX defaultIndex prop collapses into ActiveIndex. // CrmTabItem is one CRM tab: title, optional numeric badge (shown when > 0), and // inline panel content. type CrmTabItem struct { Title string Badge int Content *vdom.VNode } // CrmTabGroupProps configures CrmTabGroup / CrmSubTabGroup. ActiveIndex is the // selected tab; clicking a tab calls OnTabChange with its index. type CrmTabGroupProps struct { Items []CrmTabItem ActiveIndex int OnTabChange func(int) } // crmTabsPanels renders one wrapper div per item; only the active one is shown. func crmTabsPanels(p CrmTabGroupProps) []*vdom.VNode { out := make([]*vdom.VNode, 0, len(p.Items)) for i, item := range p.Items { cls := "hidden" if i == p.ActiveIndex { cls = "" } out = append(out, vdom.Div(vdom.Attr("class", cls), item.Content)) } return out } // --- CrmTabGroup: boxed top-accent tabs ------------------------------------- const crmTabRow = "flex w-full overflow-x-auto text-sm" const crmTabBase = "flex items-center gap-1.5 cursor-pointer p-4 font-medium border-line-strong transition-colors" const crmTabInactive = "border-b text-ink-muted hover:text-ink" const crmTabActive = "border-x border-t-2 border-t-sky-700 text-accent" const crmTabBadge = "inline-flex items-center justify-center min-w-5 h-5 px-1 text-xs font-semibold bg-primary text-white rounded-full" // CrmTabGroup renders boxed tabs with a sky-blue top accent; content sits flat // beneath the row (no body panel), with a trailing filler extending the baseline. func CrmTabGroup(p CrmTabGroupProps) *vdom.VNode { row := []vdom.Mod{vdom.Attr("class", crmTabRow)} for i, item := range p.Items { idx := i state := crmTabInactive if i == p.ActiveIndex { state = crmTabActive } btn := []vdom.Mod{ vdom.Attr("type", "button"), vdom.Attr("class", cx(crmTabBase, state)), vdom.On(vdom.EVENT_CLICK, func() { if p.OnTabChange != nil { p.OnTabChange(idx) } }), vdom.Text(item.Title), } if item.Badge > 0 { btn = append(btn, vdom.Span(vdom.Attr("class", crmTabBadge), vdom.Text(strconv.Itoa(item.Badge)))) } row = append(row, vdom.Button(btn...)) } row = append(row, vdom.Div(vdom.Attr("class", "flex-1 border-b border-line-strong"))) return vdom.Div(vdom.Attr("class", "w-full"), vdom.Div(row...), vdom.Div(kids(nil, crmTabsPanels(p))...), ) } // --- CrmSubTabGroup: segmented control -------------------------------------- const crmSubTabWrap = "flex pb-3 border-b border-line-strong overflow-x-auto" const crmSubTabGroup = "inline-flex items-stretch rounded-md border border-line-strong overflow-hidden text-sm select-none" const crmSubTabBase = "flex items-center gap-1.5 py-1 px-3 cursor-pointer font-medium whitespace-nowrap transition-colors" const crmSubTabDivider = "border-l border-line-strong" const crmSubTabActive = "bg-neutral-500 text-white" const crmSubTabInactive = "bg-surface text-ink-soft hover:bg-surface-raised hover:text-ink" const crmSubTabBadge = "inline-flex items-center justify-center min-w-5 h-5 px-1 text-xs font-semibold bg-black/10 text-current rounded-full" // CrmSubTabGroup renders a left-aligned segmented control (interlocking // segments) with a full-width baseline separating the bar from the content. func CrmSubTabGroup(p CrmTabGroupProps) *vdom.VNode { group := []vdom.Mod{vdom.Attr("class", crmSubTabGroup)} for i, item := range p.Items { idx := i divider := "" if i > 0 { divider = crmSubTabDivider } state := crmSubTabInactive if i == p.ActiveIndex { state = crmSubTabActive } btn := []vdom.Mod{ vdom.Attr("type", "button"), vdom.Attr("class", cx(crmSubTabBase, divider, state)), vdom.On(vdom.EVENT_CLICK, func() { if p.OnTabChange != nil { p.OnTabChange(idx) } }), vdom.Text(item.Title), } if item.Badge > 0 { btn = append(btn, vdom.Span(vdom.Attr("class", crmSubTabBadge), vdom.Text(strconv.Itoa(item.Badge)))) } group = append(group, vdom.Button(btn...)) } return vdom.Div(vdom.Attr("class", "w-full pt-3"), vdom.Div(vdom.Attr("class", crmSubTabWrap), vdom.Div(group...), ), vdom.Div(kids([]vdom.Mod{vdom.Attr("class", "pt-3")}, crmTabsPanels(p))...), ) }