Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 5 additions & 192 deletions web/src/app/pattern-lab/LabPanels.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* ── Layered lab: dock header + panel chrome ─────────────────────────────────
New-in-v2 styles only; knob/ramp/gallery/modal styles are shared from
PatternLab.module.css. */
The dock with its header menus, and the chrome every panel shares. A
panel's own classes go in panels/<Panel>.module.css, as Layers' did on
2026-10-25; the Preview, Pixel, Ramp and Knobs rules below are still here.
Modal styles are in PatternLab.module.css. */

.dockShell {
height: 100vh;
Expand Down Expand Up @@ -311,195 +313,7 @@
image-rendering: auto;
}

/* ── Layers panel ── */

.layerList {
list-style: none;
margin: 0;
padding: 4px;
display: flex;
flex-direction: column;
gap: 2px;
}

.layerRow {
min-height: 34px;
border-radius: 4px;
display: flex;
align-items: center;
gap: 6px;
padding: 5px 6px;
border: 1px solid transparent;
background: #fbf7ee;
cursor: pointer;
user-select: none;
}

.layerRow:hover {
border-color: rgba(23, 21, 18, 0.25);
}

.layerRowActive {
background: #171512;
color: #f7f3ea;
border-color: #171512;
}

.layerRow[data-dragover="true"] {
border-top: 2px solid #ff4d00;
}

.layerEye {
appearance: none;
border: 0;
background: none;
color: inherit;
cursor: pointer;
font-size: 12px;
width: 20px;
padding: 0;
opacity: 0.85;
}

.layerEyeOff {
opacity: 0.3;
}

.layerName {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
font-weight: 600;
}

.layerRenameInput {
flex: 1 1 auto;
min-width: 0;
border: 1px solid rgba(23, 21, 18, 0.4);
background: #fffdf8;
color: #171512;
font: inherit;
font-size: 12px;
padding: 1px 4px;
}

.layerType {
font-family: var(--font-inter), system-ui, sans-serif;
font-size: 11px;
letter-spacing: 0.06em;
padding: 1px 5px;
border: 1px solid currentColor;
opacity: 0.65;
flex: 0 0 auto;
}

.layerErrDot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #d63e2e;
flex: 0 0 auto;
}

.layerMeta {
font-family: var(--font-inter), system-ui, sans-serif;
font-size: 11px;
opacity: 0.55;
flex: 0 0 auto;
}

.layerControls {
display: flex;
flex-direction: column;
gap: 8px;
padding: 10px;
border-top: 1px solid rgba(23, 21, 18, 0.12);
background: #fbf7ee;
flex: 0 0 auto;
font-family: var(--font-inter), system-ui, sans-serif;
font-size: 12px;
}

.layerControlRow {
display: flex;
align-items: center;
gap: 8px;
}

.layerControlRow > span {
width: 58px;
flex: 0 0 auto;
text-transform: none;
color: rgba(23, 21, 18, 0.6);
font-size: 11px;
}

.layerControlRow input[type="range"] {
flex: 1 1 auto;
min-width: 0;
accent-color: #171512;
}

.layerControlRow select {
min-height: 28px;
border-radius: 4px;
min-width: 0;
flex: 1 1 auto;
border: 1px solid rgba(23, 21, 18, 0.3);
background: #fffdf8;
font-family: inherit;
font-size: 12px;
padding: 2px 4px;
}

.layerControlValue {
width: 40px;
text-align: right;
flex: 0 0 auto;
font-size: 11px;
}

.layerMaskLabel {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 11px;
text-transform: none;
cursor: pointer;
}

/* Touch screens get fingertip-sized targets, as in the Text panel. On a
390px phone the bar buttons were 28px tall, Del 37px wide, the eye 20×17px
and the mask box 13×13px. The row grows to 46px so the eye can take its
whole height. */
@media (hover: none) {
.layerBar button {
min-width: 44px;
min-height: 44px;
}

.layerRow {
min-height: 46px;
padding-block: 0;
}

.layerEye {
align-self: stretch;
width: 44px;
}

.layerMaskLabel {
min-height: 44px;
}

.layerControlRow input[type="range"],
.layerControlRow select {
min-height: 44px;
}

/* A finger has no Alt key, right button or middle button. On a 390×844
phone this line wrapped the Pixel status bar to 51px, which left the
picture a 372×151 viewport and fitted it at 1×. Without it the viewport
Expand Down Expand Up @@ -640,8 +454,7 @@

/* Keyboard focus stays visible throughout the panel controls. */
.panelBar :is(button, select, input):focus-visible,
.menuPop button:focus-visible,
.layerControlRow select:focus-visible {
.menuPop button:focus-visible {
outline: 2px solid #9b4827;
outline-offset: 2px;
}
Expand Down
35 changes: 18 additions & 17 deletions web/src/app/pattern-lab/labCss.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,32 +118,33 @@ describe("Lab dock on a touch screen", () => {

describe("Layers and Knobs on a touch screen", () => {
const dock = css("LabPanels.module.css");
const own = css("panels/LayersPanel.module.css");
const knobs = css("panels/KnobsPanel.module.css");
const layers = css("panels/LayersPanel.tsx");
const touch = (source: string, selector: string) => mediaRule(source, "(hover: none)", selector);

it("give the Layers bar buttons a 44px box", () => {
const button = touch(dock, ".layerBar button");
const button = touch(own, ".layerBar button");
expect(button).toMatch(/min-width: 44px;/);
expect(button).toMatch(/min-height: 44px;/);
// Only the Layers bar: the Pixel toolbars carry a dozen buttons each.
expect(layers).toMatch(/className=\{`\$\{dock\.panelBar\} \$\{dock\.layerBar\}`\}/);
expect(layers).toMatch(/className=\{`\$\{dock\.panelBar\} \$\{local\.layerBar\}`\}/);
expect(touch(dock, ".panelBar button")).toBe("");
});

it("let the eye fill a 44px row", () => {
const row = touch(dock, ".layerRow");
const row = touch(own, ".layerRow");
expect(row).toMatch(/min-height: 46px;/);
expect(row).toMatch(/padding-block: 0;/);
const eye = touch(dock, ".layerEye");
const eye = touch(own, ".layerEye");
expect(eye).toMatch(/align-self: stretch;/);
expect(eye).toMatch(/width: 44px;/);
});

it("give the mask, opacity and blend rows a 44px height", () => {
expect(touch(dock, ".layerMaskLabel")).toMatch(/min-height: 44px;/);
expect(touch(dock, ".layerControlRow select")).toMatch(/min-height: 44px;/);
expect(mediaBlocks(dock, "(hover: none)").join("")).toContain('.layerControlRow input[type="range"],\n .layerControlRow select {');
expect(touch(own, ".layerMaskLabel")).toMatch(/min-height: 44px;/);
expect(touch(own, ".layerControlRow select")).toMatch(/min-height: 44px;/);
expect(mediaBlocks(own, "(hover: none)").join("")).toContain('.layerControlRow input[type="range"],\n .layerControlRow select {');
});

it("give Push a 44px box", () => {
Expand All @@ -154,17 +155,17 @@ describe("Layers and Knobs on a touch screen", () => {

it("come after the rules they override", () => {
// Same specificity again, so each touch block has to sit below them.
const dockTouch = dock.lastIndexOf("@media (hover: none) {");
for (const base of [
"\n.panelBar button {",
"\n.layerRow {",
"\n.layerEye {",
"\n.layerControlRow select {",
"\n.layerMaskLabel {",
]) {
expect(dock.indexOf(base)).toBeGreaterThan(-1);
expect(dockTouch).toBeGreaterThan(dock.indexOf(base));
const ownTouch = own.lastIndexOf("@media (hover: none) {");
for (const base of ["\n.layerRow {", "\n.layerEye {", "\n.layerControlRow select {", "\n.layerMaskLabel {"]) {
expect(own.indexOf(base)).toBeGreaterThan(-1);
expect(ownTouch).toBeGreaterThan(own.indexOf(base));
}
// `.layerBar button` answers `.panelBar button` from the other file. The
// bundle keeps the stylesheets in the order the panel imports them.
expect(dock.indexOf("\n.panelBar button {")).toBeGreaterThan(-1);
const dockImport = layers.indexOf('import dock from "../LabPanels.module.css";');
expect(dockImport).toBeGreaterThan(-1);
expect(layers.indexOf('import local from "./LayersPanel.module.css";')).toBeGreaterThan(dockImport);
// The 980px block sets Push back to 22px.
expect(knobs.lastIndexOf("@media (hover: none) {")).toBeGreaterThan(knobs.indexOf("@media (max-width: 980px) {"));
});
Expand Down
Loading
Loading