@kolkrabbi/kol-styleguide 0.18.3 → 0.18.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/ColorLab.jsx +48 -84
- package/src/ComboLab.jsx +4 -4
- package/src/LockupLab.jsx +34 -2
- package/src/lab/LabWindow.jsx +1 -1
- package/src/lockupLab.js +9 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-styleguide",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Brand guide components and the brand tool",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
"react-dom": "^18.3.0 || ^19.0.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@kolkrabbi/kol-component": "^0.317.
|
|
26
|
-
"@kolkrabbi/kol-icons": "^0.53.3",
|
|
27
|
-
"@kolkrabbi/kol-print": "^0.1.1",
|
|
25
|
+
"@kolkrabbi/kol-component": "^0.317.4",
|
|
28
26
|
"@kolkrabbi/kol-framework": "^0.65.0",
|
|
29
|
-
"@kolkrabbi/kol-
|
|
27
|
+
"@kolkrabbi/kol-print": "^0.1.1",
|
|
28
|
+
"@kolkrabbi/kol-icons": "^0.53.5",
|
|
29
|
+
"@kolkrabbi/kol-theme": "^0.200.4"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"src",
|
package/src/ColorLab.jsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { Button, ColorPickerPopover, Dropdown, Icon, IconFrame, PaletteHarmonyWheel, Slider, Tooltip, hexToHsl, hslToHex } from '@kolkrabbi/kol-component'
|
|
2
|
+
import { Button, ColorPickerPopover, Dropdown, Icon, IconFrame, PaletteHarmonyWheel, SettingsSections, Slider, Tooltip, hexToHsl, hslToHex } from '@kolkrabbi/kol-component'
|
|
3
3
|
import { COLOR_RELATIONS, CVD_TYPES, DEFAULT_COLOR_LAB, colorLab, colorLabCss, contrastGrade, contrastRatio, hexToOklch, inkOn, midColor, oklchToHex, paletteFromPixels, rollColorLab, simulateCvd, tintLadder } from './colorLab.js'
|
|
4
4
|
import LabWindow, { CopyAction, LabHistory, useLabValue } from './lab/LabWindow.jsx'
|
|
5
5
|
|
|
@@ -260,7 +260,7 @@ export default function ColorLab({ value: valueProp, onChange, samples = [], lib
|
|
|
260
260
|
const harmony = (
|
|
261
261
|
/* HARMONIES (review 3): the DS's glyphs, each named on hover, the mode named in the label — on the Wheel tab only
|
|
262
262
|
* (review 6 — *"you know there is a tab for wheel. why do you put it on every page?"*) */
|
|
263
|
-
|
|
263
|
+
<div className="flex flex-col gap-3">
|
|
264
264
|
{/* THE EYEBROW IS THE RAIL'S LABEL (review 8 — *"IT WAS PERFECT!!! I was saying why is it only used here"*): the
|
|
265
265
|
* settings rail's section label, now on every label in the rail */}
|
|
266
266
|
<p className="kol-eyebrow text-fg-80">Harmony: {harmonyLabel(c.relation)}</p>
|
|
@@ -276,7 +276,7 @@ export default function ColorLab({ value: valueProp, onChange, samples = [], lib
|
|
|
276
276
|
</Tooltip>
|
|
277
277
|
))}
|
|
278
278
|
</div>
|
|
279
|
-
|
|
279
|
+
</div>
|
|
280
280
|
)
|
|
281
281
|
const sources = [
|
|
282
282
|
{ id: 'base', label: 'Base', icon: 'eyedrop', content: (
|
|
@@ -300,10 +300,12 @@ export default function ColorLab({ value: valueProp, onChange, samples = [], lib
|
|
|
300
300
|
{image
|
|
301
301
|
? <img src={image} alt="" className="absolute inset-0 h-full w-full object-cover" />
|
|
302
302
|
: (
|
|
303
|
-
<span className="relative flex flex-col items-center gap-
|
|
303
|
+
<span className="relative flex flex-col items-center gap-6">
|
|
304
304
|
<Button size="md" tone="inverted" iconLeft="upload" onClick={(e) => { e.stopPropagation(); fileInput.current?.click() }}>Upload your image</Button>
|
|
305
|
-
<span className="
|
|
306
|
-
|
|
305
|
+
<span className="flex flex-col items-center gap-1">
|
|
306
|
+
<span className="kol-mono-12 text-emphasis">Or drag and drop here</span>
|
|
307
|
+
<span className="whitespace-nowrap uppercase kol-mono-10 text-subtle">Files: JPG, PNG or WebP | Size: 40MB</span>
|
|
308
|
+
</span>
|
|
307
309
|
</span>
|
|
308
310
|
)}
|
|
309
311
|
<input ref={fileInput} type="file" accept="image/jpeg,image/png,image/webp" className="hidden" onChange={(e) => { fromFile(e.target.files?.[0]); e.target.value = '' }} />
|
|
@@ -313,8 +315,8 @@ export default function ColorLab({ value: valueProp, onChange, samples = [], lib
|
|
|
313
315
|
* and linked beside them */
|
|
314
316
|
<div className="flex flex-col gap-3">
|
|
315
317
|
<div className="flex items-baseline justify-between gap-3">
|
|
316
|
-
<span className="min-w-0 truncate kol-
|
|
317
|
-
{library && <a href={library.href} target="_blank" rel="noreferrer" className="group inline-flex shrink-0 items-center gap-1 kol-
|
|
318
|
+
<span className="min-w-0 truncate kol-mono-12 text-emphasis">Try one of ours:</span>
|
|
319
|
+
{library && <a href={library.href} target="_blank" rel="noreferrer" className="group inline-flex shrink-0 items-center gap-1 kol-mono-12 text-meta hover:text-emphasis">{library.label}<Icon name="external-link" size={12} className="text-oq-48 group-hover:text-oq-96" /></a>}
|
|
318
320
|
</div>
|
|
319
321
|
<div className="grid grid-cols-4 gap-2">
|
|
320
322
|
{samples.slice(0, 4).map((x) => <SampleTile key={x.src} src={x.src} label={x.label ?? x.src} onPick={() => takeImage(x.src)} />)}
|
|
@@ -323,12 +325,13 @@ export default function ColorLab({ value: valueProp, onChange, samples = [], lib
|
|
|
323
325
|
)}
|
|
324
326
|
</div>
|
|
325
327
|
) },
|
|
326
|
-
{ id: 'wheel', label: 'Wheel', icon: '
|
|
327
|
-
|
|
328
|
+
{ id: 'wheel', label: 'Wheel', icon: 'color-wheel', content: (
|
|
329
|
+
/* the Wheel tab's own air (the user, 2026-10-30: "MORE GAP between tabs, wheel and group"): 24 under the tabs on top of the rail's 16, 40 between the wheel and the harmony group */
|
|
330
|
+
<div className="flex flex-col gap-10 pt-6">
|
|
328
331
|
<WheelFill>{(size) => <PaletteHarmonyWheel size={size} hue={base.h} saturation={base.s} lightness={base.l} harmony={WHEEL_HARMONIES.includes(c.relation) ? c.relation : 'analogous'}
|
|
329
332
|
onHueChange={(hue) => regenerate({ base: hslToHex(hue, base.s, base.l).toUpperCase() })} />}</WheelFill>
|
|
330
333
|
{harmony}
|
|
331
|
-
|
|
334
|
+
</div>
|
|
332
335
|
) },
|
|
333
336
|
]
|
|
334
337
|
/* THE PALETTE SMALL (Adobe's) under every view's stage, Randomise at its right on the palette view (review 5 — *"under
|
|
@@ -408,92 +411,53 @@ export default function ColorLab({ value: valueProp, onChange, samples = [], lib
|
|
|
408
411
|
}
|
|
409
412
|
return best
|
|
410
413
|
}
|
|
411
|
-
/*
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
<div className="grid grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-start gap-2">
|
|
429
|
-
{pairCol('Foreground', fgI, 'fg')}
|
|
430
|
-
<Button variant="nav" size="sm" iconOnly="swap" aria-label="Swap foreground and background" className="mt-6" onClick={() => setPair({ fg: bgI, bg: fgI })} />
|
|
431
|
-
{pairCol('Background', bgI, 'bg')}
|
|
432
|
-
</div>
|
|
433
|
-
</div>
|
|
434
|
-
)
|
|
435
|
-
const summaryTable = (
|
|
436
|
-
<div className="flex flex-col">
|
|
437
|
-
<div className="grid grid-cols-[minmax(0,1fr)_64px_64px] border-b border-oq-16 pb-2 kol-eyebrow text-fg-80"><span>Category</span><span>AA</span><span>AAA</span></div>
|
|
438
|
-
{SUMMARY.map((r) => (
|
|
439
|
-
<div key={r.id} className="grid grid-cols-[minmax(0,1fr)_64px_64px] items-center border-b border-oq-08 py-3 kol-sans-body-02 text-emphasis"><span>{r.label}</span><Verdict ok={r.aa} /><Verdict ok={r.aaa} na={r.aaa === null} /></div>
|
|
440
|
-
))}
|
|
441
|
-
</div>
|
|
442
|
-
)
|
|
443
|
-
/* KOL's text levels: the foreground at every ink stop over the background, each measured */
|
|
444
|
-
const ladder = (
|
|
445
|
-
<div className="flex flex-col gap-2">
|
|
446
|
-
<span className="kol-eyebrow text-fg-80">Text levels</span>
|
|
447
|
-
<div className="flex w-full flex-col rounded p-3" style={{ background: bg }}>
|
|
448
|
-
{c.text.map((x) => {
|
|
449
|
-
const r = contrastRatio(over(fg, bg, x.stop / 100), bg)
|
|
450
|
-
return (
|
|
451
|
-
<span key={x.stop} className="grid grid-cols-[32px_minmax(0,1fr)_auto] items-baseline gap-3 py-0.5" style={{ color: over(fg, bg, x.stop / 100) }}>
|
|
452
|
-
<span className="kol-mono-12">{x.stop}</span>
|
|
453
|
-
<span className="truncate kol-sans-body-03">{x.name ? x.name[0].toUpperCase() + x.name.slice(1) : 'Text'}</span>
|
|
454
|
-
<span className="kol-mono-12">{ratioText(r)} {contrastGrade(r)}</span>
|
|
455
|
-
</span>
|
|
456
|
-
)
|
|
457
|
-
})}
|
|
458
|
-
</div>
|
|
459
|
-
</div>
|
|
460
|
-
)
|
|
414
|
+
/* THE CONTRAST RAIL ON SETTINGSSECTIONS ROWS (2026-10-30, the user: *"if you need to scroll in this siderail then you
|
|
415
|
+
* are doing something wrong"*, *"we always have used mono sections and labels"*): the same rows the other labs use —
|
|
416
|
+
* label left, control right, the rail's rhythm and its mono — and nothing taller than the rail. The eleven-step ladder
|
|
417
|
+
* is three rows, each grade with the stops it holds. */
|
|
418
|
+
const pairSection = { label: 'Pair', rows: [
|
|
419
|
+
{ id: 'ratio', label: 'Contrast ratio', value: `${ratioText(ratio)} : 1 ${contrastGrade(ratio)}` },
|
|
420
|
+
{ id: 'fg', label: 'Foreground', type: 'choice', value: String(fgI), onChange: (v) => setPair({ fg: Number(v), bg: bgI }), options: colourOptions },
|
|
421
|
+
{ id: 'fgL', label: 'Lightness', type: 'slider', min: 0, max: 100, step: 1, value: lightness(fgI), onChange: (L) => setLightness(fgI, L) },
|
|
422
|
+
{ id: 'bg', label: 'Background', type: 'choice', value: String(bgI), onChange: (v) => setPair({ fg: fgI, bg: Number(v) }), options: colourOptions },
|
|
423
|
+
{ id: 'bgL', label: 'Lightness', type: 'slider', min: 0, max: 100, step: 1, value: lightness(bgI), onChange: (L) => setLightness(bgI, L) },
|
|
424
|
+
{ id: 'swap', label: 'Swap', render: () => <Button variant="nav" size="sm" iconOnly="swap" aria-label="Swap foreground and background" onClick={() => setPair({ fg: bgI, bg: fgI })} /> },
|
|
425
|
+
] }
|
|
426
|
+
const verdict = (ok) => (ok === null ? 'n/a' : ok ? 'pass' : 'fail')
|
|
427
|
+
const wcagSection = { label: 'WCAG', rows: SUMMARY.map((r) => ({ id: r.id, label: r.label, value: `AA ${verdict(r.aa)}, AAA ${verdict(r.aaa)}` })) }
|
|
428
|
+
/* KOL's text levels: the foreground at every ink stop over the background, grouped by the grade each stop earns */
|
|
429
|
+
const levels = c.text.reduce((acc, x) => { const g = contrastGrade(contrastRatio(over(fg, bg, x.stop / 100), bg)); (acc[g] ??= []).push(x.stop); return acc }, {})
|
|
430
|
+
const levelsSection = { label: 'Text levels', rows: ['AAA', 'AA', 'AA large', 'fail'].filter((g) => levels[g]).map((g) => ({ id: g, label: g === 'fail' ? 'Fail' : g, value: levels[g].length > 1 ? `${levels[g][0]} to ${levels[g][levels[g].length - 1]}` : String(levels[g][0]) })) }
|
|
461
431
|
const contrastRail = [
|
|
462
|
-
{ id: 'summary', label: 'Summary', content:
|
|
432
|
+
{ id: 'summary', label: 'Summary', content: <SettingsSections divided sections={[pairSection, wcagSection, levelsSection]} /> },
|
|
463
433
|
{ id: 'suggest', label: 'Suggestions', content: (
|
|
464
|
-
|
|
465
|
-
{
|
|
466
|
-
|
|
467
|
-
<span className="border-b border-oq-16 pb-2 kol-eyebrow text-fg-80">Foreground colors that pass AA</span>
|
|
468
|
-
{suggestions.length === 0 && <span className="py-3 kol-sans-body-02 text-meta">None at this hue. Move the background’s lightness.</span>}
|
|
469
|
-
{suggestions.map((x) => (
|
|
470
|
-
<button key={x.hex} type="button" onClick={() => editAt(fgI, x.hex)} className="grid cursor-pointer grid-cols-[16px_minmax(0,1fr)_auto] items-center gap-3 border-b border-oq-08 py-2 text-left kol-mono-12 text-emphasis hover:bg-oq-04">
|
|
471
|
-
<span className="size-4 rounded-full" style={{ background: x.hex }} /><span>{x.hex}</span><span className="text-meta">{ratioText(x.r)} : 1 {contrastGrade(x.r)}</span>
|
|
472
|
-
</button>
|
|
473
|
-
))}
|
|
474
|
-
</div>
|
|
475
|
-
</>
|
|
434
|
+
<SettingsSections divided sections={[pairSection, { label: 'Foreground colors that pass AA', note: suggestions.length === 0 ? 'None at this hue. Move the background’s lightness.' : undefined, rows: suggestions.length === 0 ? [] : [
|
|
435
|
+
{ id: 'pick', label: 'Use', type: 'choice', value: '', onChange: (hex) => editAt(fgI, hex), options: suggestions.map((x) => ({ value: x.hex, label: `${x.hex} ${ratioText(x.r)} : 1 ${contrastGrade(x.r)}` })) },
|
|
436
|
+
] }]} />
|
|
476
437
|
) },
|
|
477
438
|
{ id: 'ratio', label: 'Set a ratio', content: (
|
|
478
|
-
|
|
479
|
-
{
|
|
480
|
-
|
|
481
|
-
<span className="kol-eyebrow text-fg-80">Target ratio</span>
|
|
482
|
-
<Slider className="w-full" min={1} max={21} step={0.5} value={target ?? Math.round(ratio * 2) / 2} onChange={(t) => { setTarget(t); editAt(fgI, solveFor(t)) }} readout="input" displayWidth={4} readoutPreset="rail" aria-label="Target contrast ratio" />
|
|
483
|
-
<span className="kol-sans-body-02 text-meta">The foreground’s lightness moves until the pair reaches it.</span>
|
|
484
|
-
</div>
|
|
485
|
-
</>
|
|
439
|
+
<SettingsSections divided sections={[pairSection, { label: 'Target', note: 'The foreground’s lightness moves until the pair reaches it.', rows: [
|
|
440
|
+
{ id: 'target', label: 'Ratio', type: 'slider', min: 1, max: 21, step: 0.5, value: target ?? Math.round(ratio * 2) / 2, displayWidth: 4, onChange: (t) => { setTarget(t); editAt(fgI, solveFor(t)) } },
|
|
441
|
+
] }]} />
|
|
486
442
|
) },
|
|
487
443
|
]
|
|
488
444
|
/* THE PREVIEW IN THE PALETTE'S OWN FORMS (review 8 — *"how is this using ANYTHING but the colors?? … show some
|
|
489
445
|
* forms that use the pallette"*): the page in bg and fg, the other colours as forms beside the text; the DS Button */
|
|
490
|
-
|
|
446
|
+
/* KOL's own vector forms (public/kol-vector, the brand's art) in the palette's colours — each a single-fill SVG worn as a
|
|
447
|
+
* CSS mask, so any colour fills it (the user, 2026-10-30: *"find a better SVG image to use, something that actually makes sense"*) */
|
|
448
|
+
const FORMS = [
|
|
449
|
+
{ src: '/kol-vector/form-09.svg', box: { left: '6%', top: '8%', width: '50%', aspectRatio: '163 / 192' } },
|
|
450
|
+
{ src: '/kol-vector/form-05.svg', box: { right: '8%', top: '10%', width: '30%', aspectRatio: '187 / 233' } },
|
|
451
|
+
{ src: '/kol-vector/form-02.svg', box: { right: '12%', bottom: '8%', width: '34%', aspectRatio: '1 / 1' } },
|
|
452
|
+
{ src: '/kol-vector/form-13.svg', box: { left: '8%', bottom: '6%', width: '36%', aspectRatio: '267 / 107' } },
|
|
453
|
+
]
|
|
454
|
+
const masked = (src) => ({ WebkitMaskImage: `url(${src})`, maskImage: `url(${src})`, WebkitMaskRepeat: 'no-repeat', maskRepeat: 'no-repeat', WebkitMaskSize: 'contain', maskSize: 'contain', WebkitMaskPosition: 'center', maskPosition: 'center' })
|
|
491
455
|
const forms = c.colors.filter((_, i) => i !== fgI && i !== bgI)
|
|
492
456
|
const contrastStage = (
|
|
493
457
|
<div className="flex h-full min-h-[240px] flex-col gap-3">
|
|
494
458
|
<div className="grid min-h-0 flex-1 grid-cols-2 grid-rows-[minmax(0,1fr)] overflow-hidden rounded" style={{ background: bg, color: fg }}>
|
|
495
459
|
<div className="relative min-h-0 overflow-hidden">
|
|
496
|
-
{(forms.length ? forms : [fg]).slice(0, 4).map((hex, i) => <span key={`${hex}${i}`} className=
|
|
460
|
+
{(forms.length ? forms : [fg]).slice(0, 4).map((hex, i) => <span key={`${hex}${i}`} className="absolute" style={{ ...FORMS[i].box, background: hex, ...masked(FORMS[i].src) }} />)}
|
|
497
461
|
</div>
|
|
498
462
|
<div className="flex min-h-0 flex-col justify-center gap-6 p-10">
|
|
499
463
|
<span className="kol-sans-heading-01 leading-none">The quick brown fox</span>
|
package/src/ComboLab.jsx
CHANGED
|
@@ -38,13 +38,13 @@ export default function ComboLab({ color, value: valueProp, onChange, logo, clas
|
|
|
38
38
|
const [tab, setTab] = useState('layout')
|
|
39
39
|
const Layout = LAYOUT_COMPONENTS[combo.layout] ?? LAYOUT_COMPONENTS[COMBO_LAYOUTS[0].id]
|
|
40
40
|
const css = comboLabCss(value, c)
|
|
41
|
-
const colourOptions = c.swatches.map((s) => ({ value: String(s.index), label:
|
|
41
|
+
const colourOptions = c.swatches.map((s) => ({ value: String(s.index), label: s.roleLabel }))
|
|
42
42
|
|
|
43
43
|
/* THE SETTINGS RAIL'S ROWS (review 4) */
|
|
44
44
|
const tabs = [
|
|
45
45
|
{ id: 'layout', label: 'Layout', content: (
|
|
46
|
-
<SettingsSections divided sections={[{
|
|
47
|
-
{ id: 'layout', label: '
|
|
46
|
+
<SettingsSections divided sections={[{ rows: [
|
|
47
|
+
{ id: 'layout', label: 'Preset', type: 'choice', value: combo.layout, onChange: (v) => set({ layout: v }), options: COMBO_LAYOUTS.map((l) => ({ value: l.id, label: l.label })) },
|
|
48
48
|
{ id: 'mark', label: logo ? 'Brand mark' : 'Mark slot', type: 'switch', on: !!combo.logo, onChange: (on) => set({ logo: on }) },
|
|
49
49
|
] }]} />
|
|
50
50
|
) },
|
|
@@ -77,7 +77,7 @@ export default function ComboLab({ color, value: valueProp, onChange, logo, clas
|
|
|
77
77
|
}}
|
|
78
78
|
>
|
|
79
79
|
<div className="flex h-full items-center justify-center">
|
|
80
|
-
<div className="w-full max-w-[
|
|
80
|
+
<div className="w-full max-w-[1200px]">
|
|
81
81
|
<Layout palette={combo.palette} logo={combo.logo ? (logo ?? PLAIN_MARK) : null} />
|
|
82
82
|
</div>
|
|
83
83
|
</div>
|
package/src/LockupLab.jsx
CHANGED
|
@@ -120,18 +120,24 @@ export default function LockupLab({ value: valueProp, onChange, mark, font, pres
|
|
|
120
120
|
) },
|
|
121
121
|
{ id: 'ratio', label: 'Ratio', content: (
|
|
122
122
|
<SettingsSections divided sections={[
|
|
123
|
+
/* EVERY TEXT PART GETS THE SAME THREE (the user, 2026-10-30: *"why do you get UPPERCASE toggle for one thing and not another"*):
|
|
124
|
+
* size to the product (the product is the base), weight, case — company, product, sub-category, optional alike */
|
|
123
125
|
{ label: 'Size to product', rows: [
|
|
124
126
|
{ id: 'pr', label: 'Company %', type: 'slider', labelWidth: 112, min: 10, max: 100, step: 2, value: Math.round(l.parentRatio * 100), onChange: (v) => set({ parentRatio: v / 100 }) },
|
|
125
127
|
{ id: 'sr', label: 'Sub-category %', type: 'slider', labelWidth: 112, min: 10, max: 100, step: 2, value: Math.round(l.subRatio * 100), onChange: (v) => set({ subRatio: v / 100 }) },
|
|
128
|
+
{ id: 'or', label: 'Optional %', type: 'slider', labelWidth: 112, min: 10, max: 100, step: 2, value: Math.round(l.optionalRatio * 100), onChange: (v) => set({ optionalRatio: v / 100 }) },
|
|
126
129
|
] },
|
|
127
130
|
{ label: 'Weight', rows: [
|
|
128
131
|
{ id: 'pw', label: 'Company', type: 'slider', labelWidth: 112, min: 100, max: 900, step: 100, value: l.parentWeight, onChange: (v) => set({ parentWeight: v }) },
|
|
129
132
|
{ id: 'w', label: 'Product', type: 'slider', labelWidth: 112, min: 100, max: 900, step: 100, value: l.productWeight, onChange: (v) => set({ productWeight: v }) },
|
|
130
133
|
{ id: 'sw', label: 'Sub-category', type: 'slider', labelWidth: 112, min: 100, max: 900, step: 100, value: l.subWeight, onChange: (v) => set({ subWeight: v }) },
|
|
134
|
+
{ id: 'ow', label: 'Optional', type: 'slider', labelWidth: 112, min: 100, max: 900, step: 100, value: l.optionalWeight, onChange: (v) => set({ optionalWeight: v }) },
|
|
131
135
|
] },
|
|
132
136
|
{ label: 'Case', rows: [
|
|
133
137
|
{ id: 'pc', label: 'Company', type: 'choice', value: l.parentCase, onChange: (v) => set({ parentCase: v }), options: dd(LOCKUP_CASES) },
|
|
134
138
|
{ id: 'c', label: 'Product', type: 'choice', value: l.productCase, onChange: (v) => set({ productCase: v }), options: dd(LOCKUP_CASES) },
|
|
139
|
+
{ id: 'sc', label: 'Sub-category', type: 'choice', value: l.subCase, onChange: (v) => set({ subCase: v }), options: dd(LOCKUP_CASES) },
|
|
140
|
+
{ id: 'oc', label: 'Optional', type: 'choice', value: l.optionalCase, onChange: (v) => set({ optionalCase: v }), options: dd(LOCKUP_CASES) },
|
|
135
141
|
] },
|
|
136
142
|
]} />
|
|
137
143
|
) },
|
|
@@ -168,9 +174,35 @@ export default function LockupLab({ value: valueProp, onChange, mark, font, pres
|
|
|
168
174
|
),
|
|
169
175
|
}}
|
|
170
176
|
>
|
|
171
|
-
|
|
177
|
+
{/* THE LOCKUP FILLS THE STAGE (the user, 2026-10-30: *"WHY IS THE LOGO TINY"*): drawn at 64 and scaled to 80 % of the stage's width or 60 % of its height, whichever binds */}
|
|
178
|
+
<FitStage>
|
|
172
179
|
<BrandLockup value={value} size={64} mark={mark} font={font} />
|
|
173
|
-
</
|
|
180
|
+
</FitStage>
|
|
174
181
|
</LabWindow>
|
|
175
182
|
)
|
|
176
183
|
}
|
|
184
|
+
|
|
185
|
+
/* FitStage — scales its one child to the stage: 80 % of the width, 60 % of the height, whichever binds; remeasured on resize */
|
|
186
|
+
function FitStage({ children }) {
|
|
187
|
+
const outer = useRef(null), inner = useRef(null)
|
|
188
|
+
const [k, setK] = useState(1)
|
|
189
|
+
useLayoutEffect(() => {
|
|
190
|
+
const fit = () => {
|
|
191
|
+
const o = outer.current, i = inner.current
|
|
192
|
+
if (!o || !i) return
|
|
193
|
+
const w = i.offsetWidth, h = i.offsetHeight
|
|
194
|
+
if (!w || !h) return
|
|
195
|
+
setK(Math.max(0.25, Math.min((o.clientWidth * 0.8) / w, (o.clientHeight * 0.6) / h)))
|
|
196
|
+
}
|
|
197
|
+
fit()
|
|
198
|
+
const ro = new ResizeObserver(fit)
|
|
199
|
+
ro.observe(outer.current)
|
|
200
|
+
ro.observe(inner.current)
|
|
201
|
+
return () => ro.disconnect()
|
|
202
|
+
}, [children])
|
|
203
|
+
return (
|
|
204
|
+
<div ref={outer} className="flex h-full min-h-[200px] items-center justify-center overflow-hidden text-emphasis">
|
|
205
|
+
<div ref={inner} className="inline-flex" style={{ transform: `scale(${k})`, transformOrigin: 'center' }}>{children}</div>
|
|
206
|
+
</div>
|
|
207
|
+
)
|
|
208
|
+
}
|
package/src/lab/LabWindow.jsx
CHANGED
|
@@ -65,7 +65,7 @@ export default function LabWindow({ title, actions, tabs = [], tab: tabProp, onT
|
|
|
65
65
|
<div {...grabProps} className="kol-rail-grab kol-rail-grab--left hidden md:block" />
|
|
66
66
|
{/* THE TABSROW AT THE TOP OF EVERY WINDOW (review 6 — *"why dont we make it a rule at top of each window?"*):
|
|
67
67
|
* one section or many, the rail opens on its tabs */}
|
|
68
|
-
{tabs.length > 0 && <TabsRow type="
|
|
68
|
+
{tabs.length > 0 && <TabsRow type="mono-light" tabs={tabs.map(({ id, label, icon }) => ({ id, label, icon }))} value={open?.id} onChange={setTab} ariaLabel={`${title} sections`} />}
|
|
69
69
|
<div className="flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto">{open?.content}</div>
|
|
70
70
|
{foot && <div className="flex flex-wrap items-center gap-1">{foot}</div>}
|
|
71
71
|
</aside>
|
package/src/lockupLab.js
CHANGED
|
@@ -22,9 +22,9 @@ export const LOCKUP_PARTS = [
|
|
|
22
22
|
|
|
23
23
|
/* the five lockup types on bh-logo-system-0.svg, in the sheet's order */
|
|
24
24
|
export const LOCKUP_TYPES = [
|
|
25
|
-
{ id: 'full', label: '
|
|
26
|
-
{ id: 'mark-product-sub', label: 'Logo, product, sub
|
|
27
|
-
{ id: 'product-sub', label: 'Product, sub
|
|
25
|
+
{ id: 'full', label: 'Full', parts: ['mark', 'parent', 'product', 'sub', 'optional'] },
|
|
26
|
+
{ id: 'mark-product-sub', label: 'Logo, product, sub', parts: ['mark', 'product', 'sub', 'optional'] },
|
|
27
|
+
{ id: 'product-sub', label: 'Product, sub', parts: ['product', 'sub', 'optional'] },
|
|
28
28
|
{ id: 'mark-product', label: 'Logo, product', parts: ['mark', 'product'] },
|
|
29
29
|
{ id: 'product', label: 'Product', parts: ['product'] },
|
|
30
30
|
]
|
|
@@ -48,9 +48,9 @@ export const LOCKUP_SUB_PRESETS = ['Solutions', 'Services', 'Courts', 'Archives'
|
|
|
48
48
|
export const DEFAULT_LOCKUP_LAB = {
|
|
49
49
|
type: 'full', shape: 'rounded',
|
|
50
50
|
parent: 'Brand', product: 'Product', sub: 'Solutions', optional: '',
|
|
51
|
-
parentRatio: 0.36, subRatio: 0.62,
|
|
52
|
-
parentWeight: 700, productWeight: 700, subWeight: 400,
|
|
53
|
-
parentCase: 'upper', productCase: 'none',
|
|
51
|
+
parentRatio: 0.36, subRatio: 0.62, optionalRatio: 0.62,
|
|
52
|
+
parentWeight: 700, productWeight: 700, subWeight: 400, optionalWeight: 400,
|
|
53
|
+
parentCase: 'upper', productCase: 'none', subCase: 'none', optionalCase: 'none',
|
|
54
54
|
clip: true, grid: false,
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -59,9 +59,9 @@ export function lockupLab(value, size = 64) {
|
|
|
59
59
|
const v = { ...DEFAULT_LOCKUP_LAB, ...value }
|
|
60
60
|
const type = LOCKUP_TYPES.find((t) => t.id === v.type) ?? LOCKUP_TYPES[0]
|
|
61
61
|
const text = { parent: v.parent, product: v.product, sub: v.sub, optional: v.optional }
|
|
62
|
-
const px = { parent: Math.round(size * v.parentRatio), product: size, sub: Math.round(size * v.subRatio), optional: Math.round(size * v.
|
|
63
|
-
const weight = { parent: v.parentWeight, product: v.productWeight, sub: v.subWeight, optional: v.
|
|
64
|
-
const kase = { parent: v.parentCase, product: v.productCase, sub:
|
|
62
|
+
const px = { parent: Math.round(size * v.parentRatio), product: size, sub: Math.round(size * v.subRatio), optional: Math.round(size * v.optionalRatio) }
|
|
63
|
+
const weight = { parent: v.parentWeight, product: v.productWeight, sub: v.subWeight, optional: v.optionalWeight }
|
|
64
|
+
const kase = { parent: v.parentCase, product: v.productCase, sub: v.subCase, optional: v.optionalCase }
|
|
65
65
|
/* the mark spans the text it stands beside: the company and product lines when both show, else the product's cap */
|
|
66
66
|
const markSize = Math.round(type.parts.includes('parent') ? size * (0.78 + v.parentRatio) : size * 0.82)
|
|
67
67
|
const parts = type.parts
|