Skip to content

Commit da9f232

Browse files
committed
Add tabs pinning for some control over what types show up first
1 parent 58399d1 commit da9f232

13 files changed

Lines changed: 733 additions & 281 deletions

File tree

locale/defaultMessages.json

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,10 @@
13541354
"context": "product field",
13551355
"string": "Export Variant SKU"
13561356
},
1357+
"5nUlWb": {
1358+
"context": "Section header in the Model Type More dropdown listing pinned types",
1359+
"string": "Pinned"
1360+
},
13571361
"5nrCxC": {
13581362
"string": "Go to model types"
13591363
},
@@ -3380,6 +3384,10 @@
33803384
"context": "dialog content",
33813385
"string": "You are not able to modify this group members. Solve this problem to continue with request."
33823386
},
3387+
"H0+SNu": {
3388+
"context": "Accessible label for the pin button next to a Model Type in the More dropdown",
3389+
"string": "Pin {name} to keep it visible at the start of the tab strip"
3390+
},
33833391
"H1L1cc": {
33843392
"context": "url",
33853393
"string": "URL"
@@ -3613,6 +3621,10 @@
36133621
"context": "error message",
36143622
"string": "Billing address is not set"
36153623
},
3624+
"IGOsZG": {
3625+
"context": "Accessible label for the unpin button next to a pinned Model Type",
3626+
"string": "Unpin {name}"
3627+
},
36163628
"IGvQ8k": {
36173629
"context": "button",
36183630
"string": "Create attribute"
@@ -5821,10 +5833,6 @@
58215833
"context": "Description for scheduled status when publication date is in the future",
58225834
"string": "Publication scheduled"
58235835
},
5824-
"UgCuqX": {
5825-
"context": "tab name",
5826-
"string": "All models"
5827-
},
58285836
"Uh3NX/": {
58295837
"context": "remove failed installation dialog content",
58305838
"string": "Are you sure you want to delete {name} failed installation?"
@@ -7830,6 +7838,10 @@
78307838
"ftcHpD": {
78317839
"string": "Customer created"
78327840
},
7841+
"ftio46": {
7842+
"context": "Section header in the Model Type More dropdown listing remaining (unpinned) types",
7843+
"string": "All types"
7844+
},
78337845
"fuFCpI": {
78347846
"context": "allow unpaid orders checkbox label",
78357847
"string": "Allow unpaid orders"

src/components/TableFilter/FilterTab.tsx

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Tab } from "@material-ui/core";
22
import { makeStyles } from "@saleor/macaw-ui";
33
import clsx from "clsx";
4+
import { type ReactNode } from "react";
45

56
const useStyles = makeStyles(
67
theme => ({
@@ -44,6 +45,18 @@ const useStyles = makeStyles(
4445
marginLeft: theme.spacing(0.5),
4546
flexShrink: 0,
4647
},
48+
// Slot for a small marker icon (e.g. a pin glyph for pinned tabs). Rendered
49+
// before the label, slightly raised relative to the baseline-aligned label
50+
// so it visually centers with the cap height of the text.
51+
tabLeadingIcon: {
52+
alignSelf: "center",
53+
display: "inline-flex",
54+
flexShrink: 0,
55+
// Inherit the tab's current text color (active vs inactive) but always
56+
// dim the marker so it reads as metadata, not as a primary glyph.
57+
color: theme.palette.text.disabled,
58+
marginRight: theme.spacing(0.75),
59+
},
4760
tabRoot: {
4861
minWidth: "80px",
4962
opacity: 1,
@@ -62,24 +75,30 @@ interface FilterTabProps {
6275
* Always rendered in a muted color so the label remains the dominant element.
6376
*/
6477
count?: number;
78+
/**
79+
* Optional small marker rendered before the label. Useful for surfacing
80+
* subtle metadata on a tab (e.g. a pin glyph for pinned tabs, à la Chrome).
81+
* Keep it tiny (≤ 12px) and let the component dim it via `text.disabled`.
82+
*/
83+
leadingIcon?: ReactNode;
6584
selected?: boolean;
6685
value?: number;
6786
}
6887

6988
export const FilterTab = (props: FilterTabProps) => {
70-
const { onClick, label, count, selected, value } = props;
89+
const { onClick, label, count, leadingIcon, selected, value } = props;
7190
const classes = useStyles(props);
72-
const tabContent =
73-
count === undefined ? (
74-
<span className={classes.tabContent} title={label}>
75-
<span className={classes.tabLabelText}>{label}</span>
76-
</span>
77-
) : (
78-
<span className={classes.tabContent} title={label}>
79-
<span className={classes.tabLabelText}>{label}</span>
80-
<span className={classes.tabCount}>({count})</span>
81-
</span>
82-
);
91+
const tabContent = (
92+
<span className={classes.tabContent} title={label}>
93+
{leadingIcon && (
94+
<span className={classes.tabLeadingIcon} aria-hidden="true">
95+
{leadingIcon}
96+
</span>
97+
)}
98+
<span className={classes.tabLabelText}>{label}</span>
99+
{count !== undefined && <span className={classes.tabCount}>({count})</span>}
100+
</span>
101+
);
83102

84103
return (
85104
<Tab
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Pin affordance lives inline at the right of each overflow item. Unpinned
3+
* items only reveal it on hover/focus (keeps the dropdown clean and avoids
4+
* implying every type is "managed"). Pinned items show the pin always,
5+
* filled-in, so it reads as state — not just a control.
6+
*/
7+
8+
.overflowItem {
9+
/* Establish hover scope so children can react via :hover on this row. */
10+
position: relative;
11+
}
12+
13+
.pinButton,
14+
.pinButtonActive {
15+
display: inline-flex;
16+
align-items: center;
17+
justify-content: center;
18+
padding: 4px;
19+
border: 0;
20+
border-radius: 4px;
21+
cursor: pointer;
22+
background-color: transparent;
23+
transition:
24+
background-color 120ms ease,
25+
color 120ms ease,
26+
opacity 120ms ease;
27+
}
28+
29+
.pinButton {
30+
/* Hidden by default; revealed when the row is hovered or the button itself
31+
is focused. The focus rule keeps the control reachable for keyboard-only
32+
navigation even though it's visually dormant. */
33+
color: var(--mu-colors-text-default2);
34+
opacity: 0;
35+
}
36+
37+
.overflowItem:hover .pinButton,
38+
.pinButton:focus-visible {
39+
opacity: 1;
40+
}
41+
42+
.pinButton:hover,
43+
.pinButton:focus-visible {
44+
background-color: var(--mu-colors-background-default1-hovered);
45+
color: var(--mu-colors-text-default1);
46+
}
47+
48+
.pinButtonActive {
49+
/* Pinned state is always visible — this is information, not just an action. */
50+
color: var(--mu-colors-text-accent1);
51+
}
52+
53+
.pinButtonActive:hover,
54+
.pinButtonActive:focus-visible {
55+
background-color: var(--mu-colors-background-default1-hovered);
56+
}

src/modeling/components/PageListPage/ModelTypeTabs/ModelTypeTabs.stories.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import type { ComponentType } from "react";
33
import { MemoryRouter } from "react-router-dom";
44
import { fn } from "storybook/test";
55

6-
import { type ModelType, ModelTypeTabs } from "./ModelTypeTabs";
6+
import { type ModelType } from "./computeVisibleTypes";
7+
import { ModelTypeTabs } from "./ModelTypeTabs";
78

89
const fewTypes: ModelType[] = [
910
{ id: "pt-1", name: "Brand" },
@@ -59,7 +60,10 @@ const meta: Meta<typeof ModelTypeTabs> = {
5960
],
6061
args: {
6162
onChange: fn(),
63+
onTogglePin: fn(),
64+
onOverflowOpen: fn(),
6265
activeType: null,
66+
pinnedTypeIds: [],
6367
loading: false,
6468
},
6569
};
@@ -127,3 +131,25 @@ export const CountsStillLoading: Story = {
127131
totalCount: undefined,
128132
},
129133
};
134+
135+
export const WithPinnedTypes: Story = {
136+
args: {
137+
types: manyTypes,
138+
counts: manyCounts,
139+
totalCount: 41,
140+
// "Refund Reason" + "Career" pinned (in pin order). They appear right
141+
// after the "All" tab, before the alphabetical fillers.
142+
pinnedTypeIds: ["pt-1", "pt-9"],
143+
},
144+
};
145+
146+
export const PinnedExceedsSlotBudget: Story = {
147+
args: {
148+
types: manyTypes,
149+
counts: manyCounts,
150+
totalCount: 41,
151+
// 7 pinned types — pinned always wins, so they all stay visible even though
152+
// visibleSlots defaults to 6.
153+
pinnedTypeIds: ["pt-1", "pt-2", "pt-3", "pt-4", "pt-5", "pt-6", "pt-7"],
154+
},
155+
};

0 commit comments

Comments
 (0)