@dfosco/storyboard 0.11.3 → 0.11.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 +1 -1
- package/src/internals/BranchesDropdown/BranchesDropdown.jsx +16 -5
- package/src/internals/BranchesDropdown/BranchesDropdown.module.css +16 -0
- package/src/internals/BranchesDropdown/BranchesDropdown.test.jsx +62 -0
- package/src/internals/Viewfinder.jsx +7 -2
- package/src/internals/Viewfinder.module.css +0 -20
package/package.json
CHANGED
|
@@ -19,26 +19,37 @@ function branchLabel(entry, homeTitle, homeFolder) {
|
|
|
19
19
|
return pieces.join(' ')
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export default function BranchesDropdown({ branches, branchBasePath, currentBranch, homeTitle = '', homeFolder = null }) {
|
|
22
|
+
export default function BranchesDropdown({ branches, branchBasePath, currentBranch, homeTitle = '', homeFolder = null, isBranchOnly = false }) {
|
|
23
23
|
if (!currentBranch || isLocalDev() || !Array.isArray(branches)) return null
|
|
24
24
|
|
|
25
25
|
const visibleBranches = branches.filter(entry => entry?.branch && entry.branch !== currentBranch)
|
|
26
26
|
if (visibleBranches.length === 0) return null
|
|
27
27
|
|
|
28
|
+
// When the prototype is branch-only (i.e. the current branch doesn't
|
|
29
|
+
// have it), give the button a subtle accent so users can spot the
|
|
30
|
+
// affordance without painting the whole card. Tooltip text shifts to
|
|
31
|
+
// make the meaning explicit.
|
|
32
|
+
const btnClass = isBranchOnly ? `${css.iconBtn} ${css.iconBtnAccent}` : css.iconBtn
|
|
33
|
+
const label = isBranchOnly
|
|
34
|
+
? 'Open on another branch'
|
|
35
|
+
: 'See branches'
|
|
36
|
+
|
|
28
37
|
return (
|
|
29
38
|
<Menu.Root>
|
|
30
39
|
<Menu.Trigger
|
|
31
|
-
className={
|
|
40
|
+
className={btnClass}
|
|
32
41
|
onClick={(e) => { e.preventDefault(); e.stopPropagation() }}
|
|
33
|
-
aria-label=
|
|
34
|
-
title=
|
|
42
|
+
aria-label={label}
|
|
43
|
+
title={label}
|
|
35
44
|
>
|
|
36
45
|
<GitBranchIcon size={16} />
|
|
37
46
|
</Menu.Trigger>
|
|
38
47
|
<Menu.Portal>
|
|
39
48
|
<Menu.Positioner className={css.branchesPositioner} side="bottom" align="end" sideOffset={4}>
|
|
40
49
|
<Menu.Popup className={css.branchesPopup}>
|
|
41
|
-
<div className={css.branchesTitle}>
|
|
50
|
+
<div className={css.branchesTitle}>
|
|
51
|
+
{isBranchOnly ? 'Available on' : 'Branches'}
|
|
52
|
+
</div>
|
|
42
53
|
{visibleBranches.map(entry => {
|
|
43
54
|
const href = entry.isExternal && entry.externalUrl
|
|
44
55
|
? entry.externalUrl
|
|
@@ -17,6 +17,22 @@
|
|
|
17
17
|
color: var(--color-foreground-muted, #555);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/* Subtle accent applied when the current branch doesn't have the
|
|
21
|
+
* prototype (the card is "branch-only" elsewhere). Replaces the
|
|
22
|
+
* previous loud card-level blue background — now the only marker
|
|
23
|
+
* that a prototype lives on another branch is this single tinted
|
|
24
|
+
* button, plus an accent ring on hover/open. */
|
|
25
|
+
.iconBtnAccent {
|
|
26
|
+
color: var(--color-foreground-accent, #0969da);
|
|
27
|
+
background: var(--color-background-accent-muted, #ddf4ff);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.iconBtnAccent:hover {
|
|
31
|
+
background: var(--color-background-accent-muted, #ddf4ff);
|
|
32
|
+
color: var(--color-foreground-accent, #0969da);
|
|
33
|
+
box-shadow: inset 0 0 0 1px var(--color-border-accent-muted, #54aeff66);
|
|
34
|
+
}
|
|
35
|
+
|
|
20
36
|
.branchesPositioner {
|
|
21
37
|
z-index: 200;
|
|
22
38
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
+
import { render, cleanup } from '@testing-library/react'
|
|
3
|
+
import BranchesDropdown from './BranchesDropdown.jsx'
|
|
4
|
+
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
cleanup()
|
|
7
|
+
// BranchesDropdown bails out when window.__SB_LOCAL_DEV__ === true.
|
|
8
|
+
if (typeof window !== 'undefined') {
|
|
9
|
+
delete window.__SB_LOCAL_DEV__
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const branches = [
|
|
14
|
+
{ branch: 'main', folder: 'demos', route: '/Signup' },
|
|
15
|
+
{ branch: 'feature-x', folder: 'demos', route: '/Signup' },
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
describe('BranchesDropdown', () => {
|
|
19
|
+
it('renders the trigger when other branches have the prototype', () => {
|
|
20
|
+
const { container } = render(
|
|
21
|
+
<BranchesDropdown branches={branches} branchBasePath="/" currentBranch="main" />,
|
|
22
|
+
)
|
|
23
|
+
const trigger = container.querySelector('button')
|
|
24
|
+
expect(trigger).toBeTruthy()
|
|
25
|
+
expect(trigger.getAttribute('aria-label')).toBe('See branches')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('applies the accent class and re-labels when isBranchOnly is true', () => {
|
|
29
|
+
const { container } = render(
|
|
30
|
+
<BranchesDropdown
|
|
31
|
+
branches={branches}
|
|
32
|
+
branchBasePath="/"
|
|
33
|
+
currentBranch="main"
|
|
34
|
+
isBranchOnly
|
|
35
|
+
/>,
|
|
36
|
+
)
|
|
37
|
+
const trigger = container.querySelector('button')
|
|
38
|
+
expect(trigger).toBeTruthy()
|
|
39
|
+
// CSS Modules hash class names — assert by suffix match.
|
|
40
|
+
expect(trigger.className).toMatch(/iconBtnAccent/)
|
|
41
|
+
expect(trigger.getAttribute('aria-label')).toBe('Open on another branch')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('does NOT apply the accent class when isBranchOnly is false', () => {
|
|
45
|
+
const { container } = render(
|
|
46
|
+
<BranchesDropdown branches={branches} branchBasePath="/" currentBranch="main" />,
|
|
47
|
+
)
|
|
48
|
+
const trigger = container.querySelector('button')
|
|
49
|
+
expect(trigger.className).not.toMatch(/iconBtnAccent/)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('returns null when only the current branch has the prototype', () => {
|
|
53
|
+
const { container } = render(
|
|
54
|
+
<BranchesDropdown
|
|
55
|
+
branches={[{ branch: 'main', folder: 'demos', route: '/Signup' }]}
|
|
56
|
+
branchBasePath="/"
|
|
57
|
+
currentBranch="main"
|
|
58
|
+
/>,
|
|
59
|
+
)
|
|
60
|
+
expect(container.querySelector('button')).toBeNull()
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -530,7 +530,12 @@ function ArtifactCard({ item, basePath, branchBasePath, currentBranch, starred,
|
|
|
530
530
|
const dirName = item.id.split(':').slice(1).join(':')
|
|
531
531
|
const typeLabel = item.type === 'canvas' ? 'Canvas' : item.type === 'prototype' ? 'Prototype' : 'Component'
|
|
532
532
|
const canEditDelete = !isBranchOnly && (item.type === 'canvas' || item.type === 'prototype')
|
|
533
|
-
|
|
533
|
+
// Cards intentionally look identical regardless of `isBranchOnly`. The
|
|
534
|
+
// only marker that a prototype lives on another branch is now the
|
|
535
|
+
// subtle blue tint on the BranchesDropdown button (see below). The
|
|
536
|
+
// previous full-card .branchOnlyCard background drew the eye too
|
|
537
|
+
// strongly for what is effectively a "hint, not a blocker" state.
|
|
538
|
+
const cardClass = `${css.card}${item.isPrivate ? ' ' + css.cardPrivate : ''}`
|
|
534
539
|
|
|
535
540
|
return (
|
|
536
541
|
<>
|
|
@@ -538,7 +543,6 @@ function ArtifactCard({ item, basePath, branchBasePath, currentBranch, starred,
|
|
|
538
543
|
<div className={css.cardHeader}>
|
|
539
544
|
<div className={css.cardBadgeGroup}>
|
|
540
545
|
<span className={css.cardBadge}>{getTypeLabel(item.type)}</span>
|
|
541
|
-
{isBranchOnly && <span className={css.branchOnlyBadge}>Branch-only</span>}
|
|
542
546
|
{item.isPrivate && !isBranchOnly && (
|
|
543
547
|
<Tooltip text={`${typeLabel} not published, only visible to you`} direction="n">
|
|
544
548
|
<button
|
|
@@ -562,6 +566,7 @@ function ArtifactCard({ item, basePath, branchBasePath, currentBranch, starred,
|
|
|
562
566
|
currentBranch={currentBranch}
|
|
563
567
|
homeTitle={item.name}
|
|
564
568
|
homeFolder={item.folder}
|
|
569
|
+
isBranchOnly={isBranchOnly}
|
|
565
570
|
/>
|
|
566
571
|
{canEditDelete && (
|
|
567
572
|
<CardActionsMenu
|
|
@@ -650,15 +650,6 @@
|
|
|
650
650
|
opacity: 1;
|
|
651
651
|
}
|
|
652
652
|
|
|
653
|
-
.branchOnlyCard {
|
|
654
|
-
background: var(--color-background-muted, #f6f8fa);
|
|
655
|
-
border-color: var(--color-border-muted, #d8dee4);
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
.branchOnlyCard:hover {
|
|
659
|
-
border-color: var(--color-border-accent-muted, #54aeff66);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
653
|
.cardThumb {
|
|
663
654
|
height: 140px;
|
|
664
655
|
background: var(--color-background-muted, #f5f5f5);
|
|
@@ -680,17 +671,6 @@
|
|
|
680
671
|
color: var(--color-foreground-muted, #555);
|
|
681
672
|
}
|
|
682
673
|
|
|
683
|
-
.branchOnlyBadge {
|
|
684
|
-
padding: 3px 8px;
|
|
685
|
-
border-radius: 999px;
|
|
686
|
-
font-size: 11px;
|
|
687
|
-
font-weight: 700;
|
|
688
|
-
text-transform: uppercase;
|
|
689
|
-
letter-spacing: 0.4px;
|
|
690
|
-
background: var(--color-background-accent-muted, #ddf4ff);
|
|
691
|
-
color: var(--color-foreground-accent, #0969da);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
674
|
.cardPrivateBadge {
|
|
695
675
|
display: inline-flex;
|
|
696
676
|
align-items: center;
|