@helloimjolopez-pathway/pathway-components 0.7.2 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helloimjolopez-pathway/pathway-components",
3
- "version": "0.7.2",
3
+ "version": "0.8.1",
4
4
  "description": "Pathway Design System React components",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -1,145 +0,0 @@
1
- /**
2
- * BottomSheet's standalone demo. Imports the real module, and the rows are the
3
- * real DropdownItem and DropdownDivider, so nothing here can fall out of step
4
- * with what ships: see components/_demo/harness.jsx for why demos are generated.
5
- */
6
- import React, { useRef, useState } from "react";
7
- import { DemoChrome, mount } from "../_demo/harness.jsx";
8
- import { BottomSheet } from "./bottom-sheet.jsx";
9
- import { DropdownItem, DropdownDivider, DropdownClose } from "../dropdown/dropdown.jsx";
10
- import { Button } from "../button/button.jsx";
11
- import { ActionIcon } from "../icon/action-icon.jsx";
12
-
13
- const U = (n) => `var(--semantic-layout-units-${n})`;
14
- const C = (n) => `var(--semantic-color-${n})`;
15
- const Y = (n) => `var(--semantic-type-${n})`;
16
-
17
- const APPS = ["Financials Home", "Accounts Payable", "Accounts Receivable", "General Ledger", "Fixed Assets", "Purchasing Management"];
18
- const ROWS = [
19
- { id: 1, payee: "Grace Office Supply", amount: "$412.50" },
20
- { id: 2, payee: "Northside Utilities", amount: "$1,208.00" },
21
- { id: 3, payee: "Covenant Insurance", amount: "$3,940.12" },
22
- ];
23
-
24
- const caption = { fontSize: Y("font-size-xs"), lineHeight: Y("line-height-xs-single"), color: C("foreground-static-neutral-subtle") };
25
-
26
- /* The real screen: Accounts Payable on a phone. The app switcher, a row's More
27
- menu, and Filters each open a sheet. */
28
- function InContext() {
29
- const [app, setApp] = useState("Accounts Payable");
30
- const [sheet, setSheet] = useState(null); // "apps" | { row } | "filters"
31
- const [moving, setMoving] = useState(false); // the row menu swapped to "Move to fund"
32
- const [log, setLog] = useState("");
33
- const close = () => { setSheet(null); setMoving(false); };
34
-
35
- const row = sheet && typeof sheet === "object" ? sheet.row : null;
36
-
37
- return (
38
- <section style={{ background: C("fill-surface-elevated"), border: `1px solid ${C("stroke-static-neutral-base")}`,
39
- borderRadius: U("cornerradius-large"), overflow: "hidden", maxWidth: 420, width: "100%", margin: "0 auto" }}>
40
- <div style={{ padding: U("padding-base"), display: "flex", flexDirection: "column", gap: U("gap-base") }}>
41
- <button type="button" aria-haspopup="dialog" onClick={() => setSheet("apps")}
42
- style={{ font: "inherit", display: "flex", alignItems: "center", gap: U("gap-tight"), minHeight: 44, cursor: "pointer",
43
- padding: `0 ${U("padding-tight")}`, borderRadius: U("cornerradius-base"), textAlign: "left",
44
- border: `1px solid ${C("stroke-action-primary-strong-pressed")}`, background: C("fill-action-primary-subtle-pressed"),
45
- color: C("foreground-action-primary-on-subtle-pressed"), fontSize: Y("font-size-s"), fontWeight: Y("weight-medium") }}>
46
- <span className="material-symbols-rounded" aria-hidden="true" style={{ fontSize: 24 }}>apps</span>
47
- <span style={{ flex: 1 }}>{app}</span>
48
- <span className="material-symbols-rounded" aria-hidden="true" style={{ fontSize: 14 }}>chevron_right</span>
49
- </button>
50
- <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
51
- <h2 style={{ margin: 0, fontSize: Y("font-size-m"), lineHeight: Y("line-height-m-single"), fontWeight: Y("weight-semibold"), color: C("foreground-static-neutral-strong") }}>Bills to pay</h2>
52
- <Button buttonStyle="Outlined" type="Secondary" size="S" text="Filters" showLeadingIcon leadingIcon="filter_list"
53
- aria-haspopup="dialog" onClick={() => setSheet("filters")} />
54
- </div>
55
- <div>
56
- {ROWS.map((r) => (
57
- <div key={r.id} style={{ display: "flex", alignItems: "center", gap: U("gap-tight"), padding: `${U("padding-xtight")} 0`,
58
- borderBottom: `1px solid ${C("stroke-static-neutral-faint")}`, fontSize: Y("font-size-s"), color: C("foreground-static-neutral-base") }}>
59
- <span style={{ flex: 1, minWidth: 0 }}>{r.payee}</span>
60
- <span>{r.amount}</span>
61
- <ActionIcon name="more_vert" size="Base" label={`More for ${r.payee}`} aria-haspopup="dialog"
62
- onClick={() => setSheet({ row: r })} />
63
- </div>
64
- ))}
65
- </div>
66
- <p role="status" style={{ margin: 0, ...caption }}>{log || "Open the app switcher, a row's More menu, or Filters."}</p>
67
- </div>
68
-
69
- <BottomSheet open={sheet === "apps"} onOpenChange={(o) => !o && close()} title="Financial Applications" menu>
70
- {APPS.map((a, i) => (
71
- <React.Fragment key={a}>
72
- <DropdownItem label={a} checked={a === app} onSelect={() => { setApp(a); setLog(`Switched to ${a}.`); }} />
73
- {i === 0 && <DropdownDivider />}
74
- </React.Fragment>
75
- ))}
76
- </BottomSheet>
77
-
78
- <BottomSheet open={!!row} onOpenChange={(o) => !o && close()} menu
79
- title={moving ? "Move to fund" : row?.payee}
80
- showBack={moving} onBack={() => setMoving(false)}>
81
- {moving ? (
82
- ["General Fund", "Building Fund", "Missions Fund"].map((f) => (
83
- <DropdownItem key={f} label={f} onSelect={() => setLog(`${row?.payee} moved to ${f}.`)} />
84
- ))
85
- ) : (
86
- <>
87
- <DropdownItem label="Edit" icon="edit" onSelect={() => setLog(`Edit ${row?.payee}.`)} />
88
- <DropdownItem label="Duplicate" icon="content_copy" onSelect={() => setLog(`Duplicated ${row?.payee}.`)} />
89
- {/* A follow-up choice swaps the Body in place; it never opens a second sheet. */}
90
- <MoveItem onMove={() => setMoving(true)} />
91
- <DropdownDivider />
92
- <DropdownItem label="Delete" icon="delete" danger onSelect={() => setLog(`Delete ${row?.payee} would ask for confirmation.`)} />
93
- </>
94
- )}
95
- </BottomSheet>
96
-
97
- <Filters open={sheet === "filters"} onClose={close} onApply={(n) => setLog(`Showing ${n} results.`)} />
98
- </section>
99
- );
100
- }
101
-
102
- /* "Move to fund…" swaps the Body instead of closing the sheet, so it is the
103
- real DropdownItem with the close context cleared. */
104
- function MoveItem({ onMove }) {
105
- return (
106
- <DropdownClose.Provider value={null}>
107
- <DropdownItem label="Move to fund…" icon="drive_file_move" onSelect={onMove} />
108
- </DropdownClose.Provider>
109
- );
110
- }
111
-
112
- function Filters({ open, onClose, onApply }) {
113
- const [unpaid, setUnpaid] = useState(true);
114
- const [overdue, setOverdue] = useState(false);
115
- const count = 24 - (unpaid ? 0 : 6) - (overdue ? 10 : 0);
116
- const reset = () => { setUnpaid(false); setOverdue(false); };
117
- const Check = ({ label, on, set }) => (
118
- <label style={{ display: "flex", alignItems: "center", gap: U("gap-medium"), minHeight: 44, padding: `0 ${U("padding-tight")}`,
119
- fontSize: Y("font-size-s"), color: C("foreground-static-neutral-strong"), cursor: "pointer" }}>
120
- <input type="checkbox" checked={on} onChange={(e) => set(e.target.checked)} /> {label}
121
- </label>
122
- );
123
- return (
124
- <BottomSheet open={open} onOpenChange={(o) => !o && onClose()} title="Filters"
125
- footer={<>
126
- <Button buttonStyle="Fill" type="Secondary" size="M" text="Reset" onClick={reset} />
127
- <Button buttonStyle="Fill" type="Primary" size="M" text={`Show ${count} results`} onClick={() => { onApply(count); onClose(); }} />
128
- </>}>
129
- <Check label="Unpaid" on={unpaid} set={setUnpaid} />
130
- <Check label="Overdue only" on={overdue} set={setOverdue} />
131
- </BottomSheet>
132
- );
133
- }
134
-
135
- function App() {
136
- return (
137
- <DemoChrome title="BottomSheet"
138
- blurb={"The mobile way to show a menu, a switcher or a short set of options. Close, the overlay, Esc and a swipe down all close it, " +
139
- "and focus returns to the control that opened it. Choosing an item closes it. Below 768 only: the host decides, so switch to Mobile."}>
140
- <InContext />
141
- </DemoChrome>
142
- );
143
- }
144
-
145
- mount(<App />);
@@ -1,59 +0,0 @@
1
- /**
2
- * NavShell's standalone demo. Imports the real NavShell, which composes the
3
- * real TopNav and SideNav, so nothing here can fall out of step with them: see
4
- * components/_demo/harness.jsx for why the newer demos are generated.
5
- *
6
- * Replaced the hand-written nav-shell.html on 2026-11-03. That page carried its
7
- * own copies of TopNav and SideNav, both from before the Figma parity passes:
8
- * a 240px nav, no item radius, a 4px stripe rounded at 4, a 20px glyph, a 22px
9
- * module mark, raw rgba hover fills, and a 56px NavHeader Figma does not draw.
10
- */
11
- import React, { useState } from "react";
12
- import { DemoChrome, mount } from "../_demo/harness.jsx";
13
- import { NavShell, ACCOUNTING_APP_SWITCHER } from "./nav-shell.jsx";
14
- import { PageTemplate } from "../page-template/page-template.jsx";
15
- import { NAV_SECTIONS } from "../../src/stories/Library/SideNav/sidenavDemoData.jsx";
16
-
17
- const C = (n) => `var(--semantic-color-${n})`;
18
- const Y = (n) => `var(--semantic-type-${n})`;
19
-
20
- // NavShell is fixed to the viewport, so every width, Desktop included, renders
21
- // in an inner frame: in this document it would sit on top of the demo chrome.
22
- const WIDTHS = { Desktop: 1440, Tablet: 900, Mobile: 390 };
23
-
24
- const ORG = { id: "gcc", name: "Grace Community Church", campus: "Knoxville", initials: "GC" };
25
- const USER = { name: "Jo Lopez", initials: "JL", email: "jo@gracecommunity.org" };
26
-
27
- function App() {
28
- // The app switcher's on/off, the same switch as the web component's
29
- // `app-switcher` attribute. It lives in the page so it works in every width.
30
- const [appSwitcher, setAppSwitcher] = useState(false);
31
- return (
32
- <DemoChrome title="NavShell" widths={WIDTHS}
33
- blurb={"TopNav, SideNav and the page area composed into one application frame. " +
34
- "Desktop pushes the page with a collapsible nav, Tablet locks the 72px rail, " +
35
- "Mobile opens the nav as an overlay from the hamburger. Tick App switcher to turn on the Accounting app switcher."}>
36
- <NavShell activeModuleId="accounting" org={ORG} user={USER}
37
- navSections={NAV_SECTIONS} activeNavId="balance_sheet"
38
- // NavShell tracks the current page and app itself. With the switcher on,
39
- // each Accounting app brings its own side nav.
40
- appSwitcher={appSwitcher ? ACCOUNTING_APP_SWITCHER : null}>
41
- <PageTemplate>
42
- <label style={{ display: "flex", alignItems: "center", gap: "var(--semantic-layout-units-gap-tight)",
43
- fontSize: Y("font-size-s"), lineHeight: Y("line-height-s-single"),
44
- color: C("foreground-static-neutral-base"), cursor: "pointer" }}>
45
- <input id="navshell-app-switcher" type="checkbox" checked={appSwitcher}
46
- onChange={(e) => setAppSwitcher(e.target.checked)} />
47
- App switcher (Accounting)
48
- </label>
49
- <h1 style={{ margin: 0, fontSize: Y("font-size-xl"), lineHeight: Y("line-height-xl-single"),
50
- fontWeight: Y("weight-semibold"), color: C("foreground-static-neutral-bold") }}>
51
- Balance Sheet
52
- </h1>
53
- </PageTemplate>
54
- </NavShell>
55
- </DemoChrome>
56
- );
57
- }
58
-
59
- mount(<App />);
@@ -1,175 +0,0 @@
1
- /**
2
- * Radio's standalone demo. Imports the real module, so there is nothing here
3
- * that can fall out of step with the shipped component: see
4
- * components/_demo/harness.jsx for why the newer demos are generated.
5
- */
6
- import React, { useState } from "react";
7
- import { DemoChrome, mount } from "../_demo/harness.jsx";
8
- import { RadioGroup, Radio, RadioMark, SIZES, ORIENTATIONS } from "./radio.jsx";
9
- import { Button } from "../button/button.jsx";
10
-
11
- const U = (n) => `var(--semantic-layout-units-${n})`;
12
- const C = (n) => `var(--semantic-color-${n})`;
13
- const Y = (n) => `var(--semantic-type-${n})`;
14
-
15
- const label = { fontSize: Y("font-size-xs"), lineHeight: Y("line-height-xs-single"), color: C("foreground-static-neutral-subtle") };
16
- const card = {
17
- background: C("fill-surface-elevated"), border: `1px solid ${C("stroke-static-neutral-base")}`,
18
- borderRadius: U("cornerradius-large"), padding: U("padding-relaxed"),
19
- display: "flex", flexDirection: "column", gap: U("gap-base"), minWidth: 0,
20
- };
21
- const h2 = { margin: 0, fontSize: Y("font-size-m"), lineHeight: Y("line-height-m-single"), fontWeight: Y("weight-semibold"), color: C("foreground-static-neutral-bold") };
22
-
23
- const TYPES = ["Unselected", "Selected", "Error Unselected", "Error Selected"];
24
- const STATES = ["Active", "Hover", "Focused", "Pressed", "Disabled"];
25
-
26
- function Pick({ list, value, onPick }) {
27
- return (
28
- <div style={{ display: "flex", gap: U("gap-xtight"), flexWrap: "wrap" }}>
29
- {list.map((x) => <Button key={x} size="S" buttonStyle={x === value ? "Fill" : "Outlined"} type="Secondary" text={x} onClick={() => onPick(x)} />)}
30
- </div>
31
- );
32
- }
33
-
34
- function InContext() {
35
- const [contact, setContact] = useState(null);
36
- const [tried, setTried] = useState(false);
37
- const error = tried && !contact;
38
- return (
39
- <section style={card}>
40
- <h2 style={h2}>Set up a recurring gift</h2>
41
- <span style={label}>Press Save gift without picking how to reach Ana. Tab into a group and use the arrow keys.</span>
42
- <RadioGroup label="Frequency" helpText="The first gift is taken on the start date." defaultValue="monthly" name="frequency">
43
- <Radio value="once" text="One time" />
44
- <Radio value="weekly" text="Weekly" />
45
- <Radio value="biweekly" text="Every two weeks" />
46
- <Radio value="monthly" text="Monthly" />
47
- </RadioGroup>
48
- <RadioGroup label="Statement delivery" orientation="Horizontal" defaultValue="email" name="statement">
49
- <Radio value="email" text="Email" />
50
- <Radio value="mail" text="Mail" />
51
- <Radio value="sms" text="Text message" disabled />
52
- </RadioGroup>
53
- <RadioGroup label="Best way to reach Ana" required name="contact" value={contact} onChange={setContact}
54
- error={error} errorMessage="Choose how to reach Ana">
55
- <Radio value="phone" text="Phone" />
56
- <Radio value="email" text="Email" />
57
- <Radio value="none" text="Don’t contact" />
58
- </RadioGroup>
59
- <div><Button text="Save gift" type="Primary" buttonStyle="Fill" onClick={() => setTried(true)} /></div>
60
- </section>
61
- );
62
- }
63
-
64
- function Playground() {
65
- const [size, setSize] = useState("Base");
66
- const [orientation, setOrientation] = useState("Vertical");
67
- const [state, setState] = useState("Default");
68
- const [required, setRequired] = useState("off");
69
- return (
70
- <section style={card}>
71
- <h2 style={h2}>Playground</h2>
72
- <div style={{ display: "flex", gap: U("gap-relaxed"), flexWrap: "wrap" }}>
73
- <div><div style={label}>Size</div><Pick list={SIZES} value={size} onPick={setSize} /></div>
74
- <div><div style={label}>Orientation</div><Pick list={ORIENTATIONS} value={orientation} onPick={setOrientation} /></div>
75
- <div><div style={label}>State</div><Pick list={["Default", "Error", "Disabled"]} value={state} onPick={setState} /></div>
76
- <div><div style={label}>Required</div><Pick list={["off", "on"]} value={required} onPick={setRequired} /></div>
77
- </div>
78
- <RadioGroup label="Gift type" size={size} orientation={orientation} required={required === "on"}
79
- error={state === "Error"} errorMessage="Choose a gift type" disabled={state === "Disabled"}
80
- helpText="You can change this later.">
81
- <Radio value="one" text="One-time gift" />
82
- <Radio value="recurring" text="Recurring gift" />
83
- <Radio value="pledge" text="Pledge payment" />
84
- </RadioGroup>
85
- </section>
86
- );
87
- }
88
-
89
- function Matrix() {
90
- return (
91
- <section style={card}>
92
- <h2 style={h2}>Every Type, State and Size</h2>
93
- <div style={{ overflowX: "auto" }}>
94
- <table style={{ borderCollapse: "collapse", fontSize: Y("font-size-xs"), color: C("foreground-static-neutral-subtle") }}>
95
- <thead><tr><th />{STATES.map((s) => <th key={s} style={{ padding: U("padding-xtight"), textAlign: "left", fontWeight: Y("weight-medium") }}>{s}</th>)}</tr></thead>
96
- <tbody>
97
- {SIZES.flatMap((size) => TYPES.map((t) => (
98
- <tr key={size + t}>
99
- <th style={{ padding: U("padding-xtight"), textAlign: "left", fontWeight: Y("weight-medium") }}>{t}, {size}</th>
100
- {STATES.map((s) => (
101
- <td key={s} style={{ padding: U("padding-xtight") }}>
102
- <RadioGroup label={`${t}, ${s}, ${size}`} showLabel={false} size={size} error={t.startsWith("Error")}
103
- value={t.endsWith("Selected") && !t.endsWith("Unselected") ? "x" : null} onChange={() => {}}>
104
- <Radio value="x" text="Label" disabled={s === "Disabled"} forceState={["Hover", "Focused", "Pressed"].includes(s) ? s : undefined} />
105
- </RadioGroup>
106
- </td>
107
- ))}
108
- </tr>
109
- )))}
110
- </tbody>
111
- </table>
112
- </div>
113
- </section>
114
- );
115
- }
116
-
117
- function Content() {
118
- return (
119
- <section style={card}>
120
- <h2 style={h2}>Long and translated labels</h2>
121
- <div style={{ display: "flex", gap: U("gap-relaxed"), flexWrap: "wrap", alignItems: "flex-start" }}>
122
- <RadioGroup label="Gift acknowledgement" defaultValue="letter">
123
- <Radio value="email" text="Send a thank-you email" />
124
- <Radio value="letter" text="Send a printed thank-you letter with the year-end giving statement, and copy the primary contact" />
125
- <Radio value="none" text="Don’t acknowledge" />
126
- </RadioGroup>
127
- <RadioGroup label="Frecuencia" helpText="La primera donación se cobra en la fecha de inicio." defaultValue="m">
128
- <Radio value="o" text="Una vez" />
129
- <Radio value="w" text="Cada semana" />
130
- <Radio value="b" text="Cada dos semanas" />
131
- <Radio value="m" text="Cada mes" />
132
- </RadioGroup>
133
- </div>
134
- </section>
135
- );
136
- }
137
-
138
- function InMenu() {
139
- const options = ["Date, newest first", "Amount, largest first", "Donor name, A to Z"];
140
- const [picked, setPicked] = useState(options[0]);
141
- return (
142
- <section style={card}>
143
- <h2 style={h2}>RadioMark inside a menu</h2>
144
- <span style={label}>The Menu owns the menuitemradio role and draws RadioMark (Base) as its leading visual.</span>
145
- <div role="menu" aria-label="Sort by" style={{ display: "flex", flexDirection: "column", width: 280,
146
- borderRadius: U("cornerradius-base"), border: `1px solid ${C("stroke-static-neutral-base")}`, background: C("fill-surface-elevated") }}>
147
- {options.map((o) => (
148
- <div key={o} role="menuitemradio" aria-checked={picked === o} tabIndex={0}
149
- onClick={() => setPicked(o)} onKeyDown={(e) => { if (e.key === " " || e.key === "Enter") { e.preventDefault(); setPicked(o); } }}
150
- style={{ display: "flex", alignItems: "center", cursor: "pointer", fontSize: Y("font-size-s"), color: C("foreground-static-neutral-base") }}>
151
- <RadioMark selected={picked === o} />{o}
152
- </div>
153
- ))}
154
- </div>
155
- </section>
156
- );
157
- }
158
-
159
- function App() {
160
- return (
161
- <DemoChrome title="Radio"
162
- blurb={"Pick exactly one option from a short list people can see all at once. Base on desktop, Large on touch. " +
163
- "Tab lands on the picked option, arrows move and pick, Space picks. The focus ring shows for the keyboard only."}>
164
- <div style={{ display: "flex", flexDirection: "column", gap: U("gap-relaxed") }}>
165
- <InContext />
166
- <Playground />
167
- <Matrix />
168
- <Content />
169
- <InMenu />
170
- </div>
171
- </DemoChrome>
172
- );
173
- }
174
-
175
- mount(<App />);
@@ -1,125 +0,0 @@
1
- /**
2
- * Scroll-to-Enable's standalone demo. Imports the real module, so there is
3
- * nothing here that can fall out of step with the shipped component: see
4
- * components/_demo/harness.jsx for why the newer demos are generated.
5
- */
6
- import React, { useState } from "react";
7
- import { DemoChrome, isEmbed, mount } from "../_demo/harness.jsx";
8
- import { ScrollToEnable } from "./scroll-to-enable.jsx";
9
-
10
- const U = (n) => `var(--semantic-layout-units-${n})`;
11
- const C = (n) => `var(--semantic-color-${n})`;
12
- const Y = (n) => `var(--semantic-type-${n})`;
13
-
14
- const FCRA = [
15
- "In connection with your employment application with [Org Name] (the “Organization”), this notice is intended to inform you that a consumer report will be obtained on you from a consumer reporting agency for employment purposes. These purposes may include hiring, promotion, reassignment or retention.",
16
- "The consumer report may contain information about your character, general reputation, personal characteristics and mode of living. It may include, but is not limited to, criminal history, motor vehicle records, education and employment verification, and credit history, where permitted by law.",
17
- "You have the right, upon written request made within a reasonable time, to request whether a consumer report has been run about you, and to request a copy of your report. You may also request the nature and scope of any investigative consumer report.",
18
- "This online application is protected by a secure certificate authority, which supports up to a TLS 1.2 256-bit encryption process. All information provided on this form is secure.",
19
- "A summary of your rights under the Fair Credit Reporting Act is available on request, and a copy will be provided with any adverse action notice.",
20
- ];
21
- const SHORT = [FCRA[0]];
22
- const LABEL = "I acknowledge receipt of the disclosure regarding background investigation and certify that I have read and understand this document.";
23
-
24
- const body = (paras) => paras.map((p, i) => <p key={i}>{p}</p>);
25
- const card = {
26
- background: C("fill-surface-elevated"), border: `1px solid ${C("stroke-static-neutral-base")}`,
27
- borderRadius: U("cornerradius-large"), padding: U("padding-relaxed"),
28
- display: "flex", flexDirection: "column", gap: U("gap-base"), minWidth: 0,
29
- };
30
- const h2 = { margin: 0, fontSize: Y("font-size-m"), lineHeight: Y("line-height-m-single"), fontWeight: Y("weight-semibold"), color: C("foreground-static-neutral-bold") };
31
- const caption = { fontSize: Y("font-size-xs"), lineHeight: Y("line-height-xs-single"), color: C("foreground-static-neutral-subtle") };
32
-
33
- /* The real screen it lives on: the background check form, step 3. */
34
- function InContext() {
35
- const [short, setShort] = useState(false);
36
- const [done, setDone] = useState(false);
37
- return (
38
- <section style={{ ...card, padding: 0, overflow: "hidden" }}>
39
- <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: `${U("padding-tight")} ${U("padding-relaxed")}`,
40
- background: C("fill-static-brand-strong"), color: C("foreground-static-brand-on-strong"), fontSize: Y("font-size-s") }}>
41
- <span>[Org Name] Background Check</span><span>Step 3 of 6</span>
42
- </div>
43
- <div style={{ padding: U("padding-relaxed"), display: "flex", justifyContent: "center" }}>
44
- <div style={{ width: "100%", maxWidth: 456, display: "flex", flexDirection: "column", gap: U("gap-base") }}>
45
- <div>
46
- <h2 style={{ ...h2, fontSize: Y("font-size-xl") }}>FCRA Notice</h2>
47
- <p style={{ margin: 0, fontSize: Y("font-size-s"), color: C("foreground-static-neutral-base") }}>Please read and acknowledge receipt of the disclosure.</p>
48
- </div>
49
- <ScrollToEnable key={`${short}-${done}`} title="Terms & Conditions" agreementLabel={LABEL}
50
- onContinue={() => setDone(true)} onPrint={() => {}}>
51
- {body(short ? SHORT : FCRA)}
52
- </ScrollToEnable>
53
- {done && <p role="status" style={{ margin: 0, ...caption }}>Continue accepted: the form would move to step 4.</p>}
54
- <div style={{ display: "flex", gap: U("gap-tight"), alignItems: "center", flexWrap: "wrap", ...caption }}>
55
- Notice:
56
- {[["Long", false], ["Short, fits", true]].map(([name, v]) => (
57
- <button key={name} type="button" aria-pressed={short === v} onClick={() => { setShort(v); setDone(false); }}
58
- style={{ font: "inherit", padding: `${U("padding-xxxtight")} ${U("padding-tight")}`, borderRadius: U("cornerradius-base"),
59
- border: `1px solid ${short === v ? C("stroke-action-primary-strong-rest") : C("stroke-static-neutral-base")}`,
60
- background: short === v ? C("fill-action-primary-subtle-rest") : "transparent", color: C("foreground-static-neutral-base"), cursor: "pointer" }}>
61
- {name}
62
- </button>
63
- ))}
64
- </div>
65
- </div>
66
- </div>
67
- </section>
68
- );
69
- }
70
-
71
- function Cell({ label, children }) {
72
- return (
73
- <div style={{ display: "flex", flexDirection: "column", gap: U("gap-tight"), minWidth: 0 }}>
74
- <span style={caption}>{label}</span>
75
- {children}
76
- </div>
77
- );
78
- }
79
-
80
- function States() {
81
- const grid = { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(min(340px, 100%), 1fr))", gap: U("gap-relaxed") };
82
- const inline = ["Unread", "Reading", "Read", "Agreed", "Blocked", "Agreement Missing"];
83
- const page = ["Unread", "Blocked", "Read", "Agreed", "Agreement Missing"];
84
- const sheet = ["Sheet Unread", "Sheet Blocked", "Sheet Read"];
85
- const make = (s, layout) => (
86
- <ScrollToEnable layout={layout} demoState={s} title={layout === "sheet" ? "FCRA Notice" : "Terms & Conditions"}
87
- agreementLabel={LABEL} noticeHeight={layout === "sheet" && s.startsWith("Sheet") ? 180 : undefined} onPrint={() => {}}>
88
- {body(FCRA)}
89
- </ScrollToEnable>
90
- );
91
- return (
92
- <>
93
- <section style={card}>
94
- <h2 style={h2}>Inline (viewport 768 and up)</h2>
95
- <div style={grid}>{inline.map((s) => <Cell key={s} label={s}>{make(s, "inline")}</Cell>)}</div>
96
- </section>
97
- <section style={card}>
98
- <h2 style={h2}>Sheet: the page (viewport under 768)</h2>
99
- <div style={grid}>{page.map((s) => <Cell key={s} label={s}>{make(s, "sheet")}</Cell>)}</div>
100
- </section>
101
- <section style={card}>
102
- <h2 style={h2}>Sheet: open</h2>
103
- <div style={grid}>{sheet.map((s) => <Cell key={s} label={s}>{make(s, "sheet")}</Cell>)}</div>
104
- </section>
105
- </>
106
- );
107
- }
108
-
109
- function App() {
110
- return (
111
- <DemoChrome title="Scroll-to-Enable"
112
- blurb={"The person reaches the end of a legal notice before agreeing and continuing. Nothing is disabled: " +
113
- "press Continue or the checkbox early and it says what to do and moves focus there. " +
114
- "Inline at 768 and wider; switch to Mobile for the sheet. Tab into the notice and use the arrow keys; Esc closes the sheet."}>
115
- <div style={{ display: "flex", flexDirection: "column", gap: U("gap-relaxed") }}>
116
- <InContext />
117
- {/* The Tablet and Mobile frames show the real screen only: the state
118
- grids force each layout and are reference, not responsive. */}
119
- {!isEmbed() && <States />}
120
- </div>
121
- </DemoChrome>
122
- );
123
- }
124
-
125
- mount(<App />);