@dillingerstaffing/strand-ui 0.6.0 → 0.7.1
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/HTML_REFERENCE.md +64 -1
- package/README.md +1 -1
- package/dist/components/InstrumentViewport/InstrumentViewport.d.ts +10 -0
- package/dist/components/InstrumentViewport/InstrumentViewport.d.ts.map +1 -0
- package/dist/components/InstrumentViewport/index.d.ts +3 -0
- package/dist/components/InstrumentViewport/index.d.ts.map +1 -0
- package/dist/components/ScrollReveal/ScrollReveal.d.ts +12 -0
- package/dist/components/ScrollReveal/ScrollReveal.d.ts.map +1 -0
- package/dist/components/ScrollReveal/index.d.ts +3 -0
- package/dist/components/ScrollReveal/index.d.ts.map +1 -0
- package/dist/css/strand-ui.css +104 -24
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +568 -527
- package/package.json +2 -2
- package/src/__tests__/build-output.test.ts +1 -0
- package/src/components/Avatar/Avatar.css +2 -2
- package/src/components/Button/Button.css +12 -12
- package/src/components/Checkbox/Checkbox.css +3 -2
- package/src/components/InstrumentViewport/InstrumentViewport.css +36 -0
- package/src/components/InstrumentViewport/InstrumentViewport.test.tsx +70 -0
- package/src/components/InstrumentViewport/InstrumentViewport.tsx +31 -0
- package/src/components/InstrumentViewport/index.ts +2 -0
- package/src/components/Nav/Nav.css +2 -2
- package/src/components/Radio/Radio.css +3 -2
- package/src/components/ScrollReveal/ScrollReveal.css +29 -0
- package/src/components/ScrollReveal/ScrollReveal.test.tsx +68 -0
- package/src/components/ScrollReveal/ScrollReveal.tsx +64 -0
- package/src/components/ScrollReveal/index.ts +2 -0
- package/src/components/Slider/Slider.css +10 -4
- package/src/components/Switch/Switch.css +1 -0
- package/src/index.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dillingerstaffing/strand-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Strand UI - Preact/React component library built on the Strand Design Language",
|
|
5
5
|
"author": "Dillinger Staffing <engineering@dillingerstaffing.com> (https://dillingerstaffing.com)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@dillingerstaffing/strand": "^0.
|
|
63
|
+
"@dillingerstaffing/strand": "^0.7.1"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@testing-library/preact": "^3.2.0",
|
|
@@ -44,6 +44,7 @@ describe("Build output", () => {
|
|
|
44
44
|
".strand-nav", ".strand-toast", ".strand-alert",
|
|
45
45
|
".strand-dialog", ".strand-tooltip", ".strand-progress",
|
|
46
46
|
".strand-spinner", ".strand-skeleton",
|
|
47
|
+
".strand-instrument-viewport", ".strand-reveal",
|
|
47
48
|
];
|
|
48
49
|
for (const cls of expectedClasses) {
|
|
49
50
|
expect(css, `Missing CSS class: ${cls}`).toContain(cls);
|
|
@@ -35,32 +35,32 @@
|
|
|
35
35
|
|
|
36
36
|
/* ── Sizes ── */
|
|
37
37
|
.strand-btn--sm {
|
|
38
|
-
padding: var(--strand-space-
|
|
38
|
+
padding: var(--strand-space-2) var(--strand-space-5);
|
|
39
39
|
font-size: var(--strand-text-sm);
|
|
40
|
-
min-height:
|
|
40
|
+
min-height: var(--strand-touch-target);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
.strand-btn--md {
|
|
44
|
-
padding:
|
|
44
|
+
padding: var(--strand-space-3) var(--strand-space-8);
|
|
45
45
|
font-size: var(--strand-text-sm);
|
|
46
|
-
min-height:
|
|
46
|
+
min-height: var(--strand-touch-target);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
.strand-btn--lg {
|
|
50
|
-
padding: var(--strand-space-3) var(--strand-space-
|
|
50
|
+
padding: var(--strand-space-3) var(--strand-space-10);
|
|
51
51
|
font-size: var(--strand-text-base);
|
|
52
|
-
min-height:
|
|
52
|
+
min-height: var(--strand-touch-target);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/* ── Icon-only ── */
|
|
56
56
|
.strand-btn--icon-only.strand-btn--sm {
|
|
57
57
|
padding: var(--strand-space-1);
|
|
58
|
-
min-width:
|
|
58
|
+
min-width: var(--strand-touch-target);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
.strand-btn--icon-only.strand-btn--md {
|
|
62
62
|
padding: var(--strand-space-2);
|
|
63
|
-
min-width:
|
|
63
|
+
min-width: var(--strand-touch-target);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
.strand-btn--icon-only.strand-btn--lg {
|
|
@@ -75,18 +75,18 @@
|
|
|
75
75
|
|
|
76
76
|
/* ── Primary variant ── */
|
|
77
77
|
.strand-btn--primary {
|
|
78
|
-
background: var(--strand-blue-
|
|
79
|
-
color: var(--strand-on-blue-
|
|
78
|
+
background: var(--strand-blue-deep);
|
|
79
|
+
color: var(--strand-on-blue-deep);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
.strand-btn--primary:hover:not(:disabled) {
|
|
83
|
-
background: var(--strand-blue-
|
|
83
|
+
background: var(--strand-blue-midnight);
|
|
84
84
|
transform: translateY(-1px);
|
|
85
85
|
box-shadow: var(--strand-hover-shadow-primary);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
.strand-btn--primary:active:not(:disabled) {
|
|
89
|
-
background: var(--strand-blue-
|
|
89
|
+
background: var(--strand-blue-abyss);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
/* ── Secondary variant ── */
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
display: inline-flex;
|
|
6
6
|
align-items: center;
|
|
7
7
|
gap: var(--strand-space-2);
|
|
8
|
+
min-height: var(--strand-touch-target);
|
|
8
9
|
cursor: pointer;
|
|
9
10
|
user-select: none;
|
|
10
11
|
font-family: var(--strand-font-sans);
|
|
@@ -31,8 +32,8 @@
|
|
|
31
32
|
display: flex;
|
|
32
33
|
align-items: center;
|
|
33
34
|
justify-content: center;
|
|
34
|
-
width:
|
|
35
|
-
height:
|
|
35
|
+
width: var(--strand-control-size);
|
|
36
|
+
height: var(--strand-control-size);
|
|
36
37
|
border: 1px solid var(--strand-gray-200);
|
|
37
38
|
border-radius: var(--strand-radius-sm);
|
|
38
39
|
background: var(--strand-surface-elevated);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*! Strand UI | MIT License | dillingerstaffing.com */
|
|
2
|
+
|
|
3
|
+
.strand-instrument-viewport {
|
|
4
|
+
background: var(--strand-blue-abyss);
|
|
5
|
+
color: var(--strand-gray-100);
|
|
6
|
+
border-radius: var(--strand-radius-lg);
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
position: relative;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.strand-instrument-viewport__label {
|
|
12
|
+
color: var(--strand-gray-400);
|
|
13
|
+
font-family: var(--strand-font-mono);
|
|
14
|
+
font-size: var(--strand-text-xs);
|
|
15
|
+
font-weight: var(--strand-weight-medium);
|
|
16
|
+
letter-spacing: var(--strand-tracking-widest);
|
|
17
|
+
text-transform: uppercase;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.strand-instrument-viewport__value {
|
|
21
|
+
color: var(--strand-on-blue-primary);
|
|
22
|
+
font-family: var(--strand-font-mono);
|
|
23
|
+
font-variant-numeric: tabular-nums;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Optional grid overlay */
|
|
27
|
+
.strand-instrument-viewport--grid::before {
|
|
28
|
+
content: '';
|
|
29
|
+
position: absolute;
|
|
30
|
+
inset: 0;
|
|
31
|
+
background:
|
|
32
|
+
linear-gradient(rgba(59, 142, 246, 0.04) 1px, transparent 1px),
|
|
33
|
+
linear-gradient(90deg, rgba(59, 142, 246, 0.04) 1px, transparent 1px);
|
|
34
|
+
background-size: var(--strand-viewport-grid-size) var(--strand-viewport-grid-size);
|
|
35
|
+
pointer-events: none;
|
|
36
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { render } from "@testing-library/preact";
|
|
3
|
+
import { InstrumentViewport } from "./InstrumentViewport.js";
|
|
4
|
+
|
|
5
|
+
describe("InstrumentViewport", () => {
|
|
6
|
+
// ── Rendering ──
|
|
7
|
+
|
|
8
|
+
it("renders a div element", () => {
|
|
9
|
+
const { container } = render(
|
|
10
|
+
<InstrumentViewport>Content</InstrumentViewport>,
|
|
11
|
+
);
|
|
12
|
+
const el = container.firstElementChild;
|
|
13
|
+
expect(el?.tagName).toBe("DIV");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("applies base class", () => {
|
|
17
|
+
const { container } = render(
|
|
18
|
+
<InstrumentViewport>Content</InstrumentViewport>,
|
|
19
|
+
);
|
|
20
|
+
const el = container.firstElementChild;
|
|
21
|
+
expect(el?.className).toContain("strand-instrument-viewport");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("renders children", () => {
|
|
25
|
+
const { getByText } = render(
|
|
26
|
+
<InstrumentViewport>Hello viewport</InstrumentViewport>,
|
|
27
|
+
);
|
|
28
|
+
expect(getByText("Hello viewport")).toBeTruthy();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// ── Grid modifier ──
|
|
32
|
+
|
|
33
|
+
it("does not apply grid class by default", () => {
|
|
34
|
+
const { container } = render(
|
|
35
|
+
<InstrumentViewport>Content</InstrumentViewport>,
|
|
36
|
+
);
|
|
37
|
+
const el = container.firstElementChild;
|
|
38
|
+
expect(el?.className).not.toContain("strand-instrument-viewport--grid");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("applies grid modifier class when grid prop is true", () => {
|
|
42
|
+
const { container } = render(
|
|
43
|
+
<InstrumentViewport grid>Content</InstrumentViewport>,
|
|
44
|
+
);
|
|
45
|
+
const el = container.firstElementChild;
|
|
46
|
+
expect(el?.className).toContain("strand-instrument-viewport--grid");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// ── Custom className ──
|
|
50
|
+
|
|
51
|
+
it("merges custom className with component classes", () => {
|
|
52
|
+
const { container } = render(
|
|
53
|
+
<InstrumentViewport className="custom">Content</InstrumentViewport>,
|
|
54
|
+
);
|
|
55
|
+
const el = container.firstElementChild;
|
|
56
|
+
expect(el?.className).toContain("strand-instrument-viewport");
|
|
57
|
+
expect(el?.className).toContain("custom");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ── Props forwarding ──
|
|
61
|
+
|
|
62
|
+
it("forwards additional props", () => {
|
|
63
|
+
const { container } = render(
|
|
64
|
+
<InstrumentViewport id="vp-1" data-testid="viewport">
|
|
65
|
+
Content
|
|
66
|
+
</InstrumentViewport>,
|
|
67
|
+
);
|
|
68
|
+
expect(container.firstElementChild?.getAttribute("id")).toBe("vp-1");
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*! Strand UI | MIT License | dillingerstaffing.com */
|
|
2
|
+
|
|
3
|
+
import type { JSX } from "preact";
|
|
4
|
+
import { forwardRef } from "preact/compat";
|
|
5
|
+
|
|
6
|
+
export interface InstrumentViewportProps
|
|
7
|
+
extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
/** Show subtle grid overlay */
|
|
9
|
+
grid?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const InstrumentViewport = forwardRef<
|
|
13
|
+
HTMLDivElement,
|
|
14
|
+
InstrumentViewportProps
|
|
15
|
+
>(({ grid = false, className = "", children, ...rest }, ref) => {
|
|
16
|
+
const classes = [
|
|
17
|
+
"strand-instrument-viewport",
|
|
18
|
+
grid ? "strand-instrument-viewport--grid" : "",
|
|
19
|
+
className,
|
|
20
|
+
]
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.join(" ");
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div ref={ref} className={classes} {...rest}>
|
|
26
|
+
{children}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
InstrumentViewport.displayName = "InstrumentViewport";
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
.strand-nav {
|
|
5
5
|
position: relative;
|
|
6
6
|
width: 100%;
|
|
7
|
-
height:
|
|
7
|
+
height: var(--strand-nav-height);
|
|
8
8
|
background: var(--strand-surface-elevated);
|
|
9
9
|
border-bottom: 1px solid var(--strand-gray-200);
|
|
10
10
|
font-family: var(--strand-font-sans);
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
|
|
166
166
|
.strand-nav {
|
|
167
167
|
height: auto;
|
|
168
|
-
min-height:
|
|
168
|
+
min-height: var(--strand-nav-height);
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
display: inline-flex;
|
|
6
6
|
align-items: center;
|
|
7
7
|
gap: var(--strand-space-2);
|
|
8
|
+
min-height: var(--strand-touch-target);
|
|
8
9
|
cursor: pointer;
|
|
9
10
|
user-select: none;
|
|
10
11
|
font-family: var(--strand-font-sans);
|
|
@@ -31,8 +32,8 @@
|
|
|
31
32
|
display: flex;
|
|
32
33
|
align-items: center;
|
|
33
34
|
justify-content: center;
|
|
34
|
-
width:
|
|
35
|
-
height:
|
|
35
|
+
width: var(--strand-control-size);
|
|
36
|
+
height: var(--strand-control-size);
|
|
36
37
|
border: 1px solid var(--strand-gray-200);
|
|
37
38
|
border-radius: var(--strand-radius-full);
|
|
38
39
|
background: var(--strand-surface-elevated);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*! Strand UI | MIT License | dillingerstaffing.com */
|
|
2
|
+
|
|
3
|
+
.strand-reveal {
|
|
4
|
+
opacity: 0;
|
|
5
|
+
transform: translateY(24px);
|
|
6
|
+
transition: opacity var(--strand-duration-glacial) var(--strand-ease-out-expo),
|
|
7
|
+
transform var(--strand-duration-glacial) var(--strand-ease-out-expo);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.strand-reveal--visible {
|
|
11
|
+
opacity: 1;
|
|
12
|
+
transform: translateY(0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Stagger children */
|
|
16
|
+
.strand-reveal-group > .strand-reveal:nth-child(1) { transition-delay: 0ms; }
|
|
17
|
+
.strand-reveal-group > .strand-reveal:nth-child(2) { transition-delay: var(--strand-stagger-delay); }
|
|
18
|
+
.strand-reveal-group > .strand-reveal:nth-child(3) { transition-delay: calc(var(--strand-stagger-delay) * 2); }
|
|
19
|
+
.strand-reveal-group > .strand-reveal:nth-child(4) { transition-delay: calc(var(--strand-stagger-delay) * 3); }
|
|
20
|
+
.strand-reveal-group > .strand-reveal:nth-child(5) { transition-delay: calc(var(--strand-stagger-delay) * 4); }
|
|
21
|
+
.strand-reveal-group > .strand-reveal:nth-child(6) { transition-delay: var(--strand-duration-slow); }
|
|
22
|
+
|
|
23
|
+
@media (prefers-reduced-motion: reduce) {
|
|
24
|
+
.strand-reveal {
|
|
25
|
+
opacity: 1;
|
|
26
|
+
transform: none;
|
|
27
|
+
transition: none;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { render } from "@testing-library/preact";
|
|
3
|
+
import { ScrollReveal } from "./ScrollReveal.js";
|
|
4
|
+
|
|
5
|
+
describe("ScrollReveal", () => {
|
|
6
|
+
// ── Rendering ──
|
|
7
|
+
|
|
8
|
+
it("renders a div element", () => {
|
|
9
|
+
const { container } = render(<ScrollReveal>Content</ScrollReveal>);
|
|
10
|
+
const el = container.firstElementChild;
|
|
11
|
+
expect(el?.tagName).toBe("DIV");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("applies strand-reveal class", () => {
|
|
15
|
+
const { container } = render(<ScrollReveal>Content</ScrollReveal>);
|
|
16
|
+
const el = container.firstElementChild;
|
|
17
|
+
expect(el?.className).toContain("strand-reveal");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("renders children", () => {
|
|
21
|
+
const { getByText } = render(<ScrollReveal>Hello reveal</ScrollReveal>);
|
|
22
|
+
expect(getByText("Hello reveal")).toBeTruthy();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("does not apply visible class initially", () => {
|
|
26
|
+
const { container } = render(<ScrollReveal>Content</ScrollReveal>);
|
|
27
|
+
const el = container.firstElementChild;
|
|
28
|
+
expect(el?.className).not.toContain("strand-reveal--visible");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// ── Custom className ──
|
|
32
|
+
|
|
33
|
+
it("merges custom className with component classes", () => {
|
|
34
|
+
const { container } = render(
|
|
35
|
+
<ScrollReveal className="custom">Content</ScrollReveal>,
|
|
36
|
+
);
|
|
37
|
+
const el = container.firstElementChild;
|
|
38
|
+
expect(el?.className).toContain("strand-reveal");
|
|
39
|
+
expect(el?.className).toContain("custom");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// ── Props forwarding ──
|
|
43
|
+
|
|
44
|
+
it("forwards additional props", () => {
|
|
45
|
+
const { container } = render(
|
|
46
|
+
<ScrollReveal id="reveal-1" data-testid="reveal">
|
|
47
|
+
Content
|
|
48
|
+
</ScrollReveal>,
|
|
49
|
+
);
|
|
50
|
+
expect(container.firstElementChild?.getAttribute("id")).toBe("reveal-1");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ── Props acceptance ──
|
|
54
|
+
|
|
55
|
+
it("accepts threshold prop without error", () => {
|
|
56
|
+
const { container } = render(
|
|
57
|
+
<ScrollReveal threshold={0.5}>Content</ScrollReveal>,
|
|
58
|
+
);
|
|
59
|
+
expect(container.firstElementChild).toBeTruthy();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("accepts once prop without error", () => {
|
|
63
|
+
const { container } = render(
|
|
64
|
+
<ScrollReveal once={false}>Content</ScrollReveal>,
|
|
65
|
+
);
|
|
66
|
+
expect(container.firstElementChild).toBeTruthy();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*! Strand UI | MIT License | dillingerstaffing.com */
|
|
2
|
+
|
|
3
|
+
import type { JSX } from "preact";
|
|
4
|
+
import { useEffect, useRef } from "preact/hooks";
|
|
5
|
+
import { forwardRef } from "preact/compat";
|
|
6
|
+
|
|
7
|
+
export interface ScrollRevealProps
|
|
8
|
+
extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
/** IntersectionObserver visibility threshold (0-1) */
|
|
10
|
+
threshold?: number;
|
|
11
|
+
/** Only trigger reveal once */
|
|
12
|
+
once?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const ScrollReveal = forwardRef<HTMLDivElement, ScrollRevealProps>(
|
|
16
|
+
({ threshold = 0.1, once = true, className = "", children, ...rest }, ref) => {
|
|
17
|
+
const innerRef = useRef<HTMLDivElement>(null);
|
|
18
|
+
|
|
19
|
+
const classes = ["strand-reveal", className].filter(Boolean).join(" ");
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const el = innerRef.current;
|
|
23
|
+
if (!el || typeof IntersectionObserver === "undefined") return;
|
|
24
|
+
|
|
25
|
+
const observer = new IntersectionObserver(
|
|
26
|
+
(entries) => {
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
if (entry.isIntersecting) {
|
|
29
|
+
entry.target.classList.add("strand-reveal--visible");
|
|
30
|
+
if (once) {
|
|
31
|
+
observer.unobserve(entry.target);
|
|
32
|
+
}
|
|
33
|
+
} else if (!once) {
|
|
34
|
+
entry.target.classList.remove("strand-reveal--visible");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{ threshold },
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
observer.observe(el);
|
|
42
|
+
|
|
43
|
+
return () => {
|
|
44
|
+
observer.disconnect();
|
|
45
|
+
};
|
|
46
|
+
}, [threshold, once]);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
ref={(node) => {
|
|
51
|
+
(innerRef as { current: HTMLDivElement | null }).current = node;
|
|
52
|
+
if (typeof ref === "function") ref(node);
|
|
53
|
+
else if (ref) (ref as { current: HTMLDivElement | null }).current = node;
|
|
54
|
+
}}
|
|
55
|
+
className={classes}
|
|
56
|
+
{...rest}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
ScrollReveal.displayName = "ScrollReveal";
|
|
@@ -20,14 +20,15 @@
|
|
|
20
20
|
transition: background var(--strand-duration-fast) var(--strand-ease-out-quart);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/* ── Thumb: Webkit ── */
|
|
23
|
+
/* ── Thumb: Webkit (44px touch target, 20px visual) ── */
|
|
24
24
|
.strand-slider__field::-webkit-slider-thumb {
|
|
25
25
|
appearance: none;
|
|
26
26
|
width: 20px;
|
|
27
27
|
height: 20px;
|
|
28
28
|
background: var(--strand-blue-primary);
|
|
29
|
-
border:
|
|
29
|
+
border: 12px solid transparent;
|
|
30
30
|
border-radius: var(--strand-radius-full);
|
|
31
|
+
background-clip: padding-box;
|
|
31
32
|
cursor: pointer;
|
|
32
33
|
box-shadow: var(--strand-elevation-1);
|
|
33
34
|
transition:
|
|
@@ -37,21 +38,24 @@
|
|
|
37
38
|
|
|
38
39
|
.strand-slider__field:hover:not(:disabled)::-webkit-slider-thumb {
|
|
39
40
|
background: var(--strand-blue-vivid);
|
|
41
|
+
background-clip: padding-box;
|
|
40
42
|
transform: scale(1.15);
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
.strand-slider__field:active:not(:disabled)::-webkit-slider-thumb {
|
|
44
46
|
background: var(--strand-blue-deep);
|
|
47
|
+
background-clip: padding-box;
|
|
45
48
|
transform: scale(1.05);
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
/* ── Thumb: Firefox ── */
|
|
51
|
+
/* ── Thumb: Firefox (44px touch target, 20px visual) ── */
|
|
49
52
|
.strand-slider__field::-moz-range-thumb {
|
|
50
53
|
width: 20px;
|
|
51
54
|
height: 20px;
|
|
52
55
|
background: var(--strand-blue-primary);
|
|
53
|
-
border:
|
|
56
|
+
border: 12px solid transparent;
|
|
54
57
|
border-radius: var(--strand-radius-full);
|
|
58
|
+
background-clip: padding-box;
|
|
55
59
|
cursor: pointer;
|
|
56
60
|
box-shadow: var(--strand-elevation-1);
|
|
57
61
|
transition:
|
|
@@ -61,11 +65,13 @@
|
|
|
61
65
|
|
|
62
66
|
.strand-slider__field:hover:not(:disabled)::-moz-range-thumb {
|
|
63
67
|
background: var(--strand-blue-vivid);
|
|
68
|
+
background-clip: padding-box;
|
|
64
69
|
transform: scale(1.15);
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
.strand-slider__field:active:not(:disabled)::-moz-range-thumb {
|
|
68
73
|
background: var(--strand-blue-deep);
|
|
74
|
+
background-clip: padding-box;
|
|
69
75
|
transform: scale(1.05);
|
|
70
76
|
}
|
|
71
77
|
|
package/src/index.ts
CHANGED
|
@@ -100,3 +100,11 @@ export type { SpinnerProps } from "./components/Spinner/index.js";
|
|
|
100
100
|
|
|
101
101
|
export { Skeleton } from "./components/Skeleton/index.js";
|
|
102
102
|
export type { SkeletonProps } from "./components/Skeleton/index.js";
|
|
103
|
+
|
|
104
|
+
// Surfaces
|
|
105
|
+
export { InstrumentViewport } from "./components/InstrumentViewport/index.js";
|
|
106
|
+
export type { InstrumentViewportProps } from "./components/InstrumentViewport/index.js";
|
|
107
|
+
|
|
108
|
+
// Animation
|
|
109
|
+
export { ScrollReveal } from "./components/ScrollReveal/index.js";
|
|
110
|
+
export type { ScrollRevealProps } from "./components/ScrollReveal/index.js";
|