@tfrc/product 1.0.0

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/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # @tfrc/product
2
+
3
+ The product dialect of the company's visual language. Mobile-first,
4
+ restrained, built to be used daily for years. ~40 components, 44px touch
5
+ floor, near-zero novelty tolerance.
6
+
7
+ **This package is not an app.** It is the UI kit every product themes over —
8
+ the finance app is a separate repository that installs this package plus
9
+ `themes/finance` (`decisions/0007.md`). Building a product? Read
10
+ `CONSUMING.md` at the repo root. Renamed from `@tfrc/app` because that name
11
+ was read as "the app"; see `decisions/0008.md`.
12
+
13
+ **Products theme over this layer; they do not fork it.** See
14
+ `src/themes/finance.css` for the pattern — a product theme imports this
15
+ package and adds only what's genuinely product-specific (currently: a
16
+ `--theme-product-hue` identity tag). Everything else — button, field,
17
+ transaction row, balance card, tab bar — is shared by every product that
18
+ will ever theme over this layer.
19
+
20
+ Never imports from `@tfrc/marketing`. If you're tempted to reuse a marketing component
21
+ here, that's a sign it belongs in `@tfrc/foundation` instead, and only
22
+ once a third consumer needs it — see `AGENTS.md` rule 4.
23
+
24
+ ## Reserved tokens
25
+
26
+ `--color-gain` and `--color-loss` are owned by this package. See
27
+ `decisions/0002-reserved-hues-for-financial-semantics.md`. Every amount
28
+ or direction indicator must pair color with a sign or arrow — never
29
+ color alone.
30
+
31
+ ## Adding a new product theme
32
+
33
+ 1. Copy the shape of `src/themes/finance.css`.
34
+ 2. Pick an unused hue from `STATE.md`'s `product_hues_in_use` — check the
35
+ ceiling in `decisions/0005.md` first.
36
+ 3. Only add token overrides if the product genuinely needs different
37
+ values — most won't. The finance theme adds none.
38
+ 4. Update `STATE.md`.
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@tfrc/product",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "description": "The product dialect of the visual language — application UI. Mobile-first, ~40 components, 44px touch floor. Products theme over this layer; they do not fork it. Never imports from @tfrc/marketing.",
6
+ "keywords": ["design-system", "design-tokens", "css", "tailwind", "product-ui"],
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/the-full-remote-company/design-system.git",
10
+ "directory": "packages/product"
11
+ },
12
+ "homepage": "https://github.com/the-full-remote-company/design-system/tree/main/packages/product#readme",
13
+ "bugs": "https://github.com/the-full-remote-company/design-system/issues",
14
+ "main": "src/index.css",
15
+ "exports": {
16
+ ".": "./src/index.css",
17
+ "./themes/*": "./src/themes/*.css"
18
+ },
19
+ "files": ["src", "README.md"],
20
+ "dependencies": {
21
+ "@tfrc/foundation": "1.0.0"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public",
25
+ "registry": "https://registry.npmjs.org"
26
+ }
27
+ }
@@ -0,0 +1,137 @@
1
+ /* ==========================================================================
2
+ @tfrc/product — components
3
+ Reference only tokens from tokens.css. Zero raw color literals.
4
+ Every interactive element clears --touch-min (44px) — see AGENTS.md
5
+ and decisions/0001.md's comparison table.
6
+ ========================================================================== */
7
+
8
+ body { font-family: var(--font-ui); }
9
+ h1, h2, h3 { font-family: var(--font-display); font-weight: 800; margin: 0; letter-spacing: -0.028em; }
10
+
11
+ /* ---- Button — same API/class names as @tfrc/marketing, different values ---- */
12
+ .btn {
13
+ display: inline-flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ gap: 0.5rem;
17
+ min-height: var(--touch-min);
18
+ padding: 0 1rem;
19
+ border: 1px solid transparent;
20
+ border-radius: var(--radius-sm);
21
+ font-family: inherit;
22
+ font-size: var(--step-body);
23
+ font-weight: 600;
24
+ cursor: pointer;
25
+ transition: background var(--duration-fast) var(--ease-quint),
26
+ border-color var(--duration-fast) var(--ease-quint);
27
+ }
28
+ .btn--primary { background: var(--color-accent); color: var(--color-accent-fg); }
29
+ .btn--primary:active { background: var(--color-accent-hover); }
30
+ .btn--secondary { background: var(--color-surface-raised); border-color: var(--color-border-strong); color: var(--color-text-primary); }
31
+ .btn--ghost { background: transparent; color: var(--color-accent); }
32
+ .btn--block { width: 100%; }
33
+
34
+ /* ---- Field — app only ---- */
35
+ .field label { display: block; font-size: var(--step-sm); font-weight: 600; margin-bottom: 0.375rem; }
36
+ .field input {
37
+ width: 100%;
38
+ min-height: var(--touch-min);
39
+ padding: 0 0.75rem;
40
+ background: var(--color-surface-raised);
41
+ border: 1px solid var(--color-border-subtle);
42
+ border-radius: var(--radius-sm);
43
+ font-family: inherit;
44
+ font-size: 1rem;
45
+ color: var(--color-text-primary);
46
+ }
47
+ /* Amounts are a typographic problem, not just an input: tabular numerals,
48
+ right-aligned, decimal-locked. See packages/product README. */
49
+ .field input.num {
50
+ font-variant-numeric: tabular-nums;
51
+ text-align: right;
52
+ font-size: 1.25rem;
53
+ font-weight: 600;
54
+ }
55
+
56
+ /* ---- Badge / pill — app only. Reserved gain/loss variants live here. ---- */
57
+ .pill {
58
+ display: inline-flex;
59
+ align-items: center;
60
+ gap: 0.3125rem;
61
+ padding: 0.1875rem 0.5rem;
62
+ border-radius: 0.375rem;
63
+ font-size: var(--step-xs);
64
+ font-weight: 600;
65
+ text-transform: uppercase;
66
+ letter-spacing: 0.03em;
67
+ background: var(--color-surface-sunken);
68
+ color: var(--color-text-secondary);
69
+ }
70
+ .pill--gain { background: var(--color-gain-tint); color: var(--color-gain); }
71
+ .pill--loss { background: var(--color-loss-tint); color: var(--color-loss); }
72
+
73
+ /* ---- Amount — app only. Direction is never color-only; every consumer
74
+ must pair this with a sign or arrow glyph. See decisions/0002.md. ---- */
75
+ .amt {
76
+ font-variant-numeric: tabular-nums;
77
+ font-weight: 600;
78
+ font-size: var(--step-body);
79
+ white-space: nowrap;
80
+ letter-spacing: -0.01em;
81
+ }
82
+ .amt--gain { color: var(--color-gain); }
83
+ .amt--loss { color: var(--color-text-primary); }
84
+
85
+ /* ---- Transaction row / list — app only, no web equivalent ---- */
86
+ .list { background: var(--color-surface-raised); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); overflow: hidden; }
87
+ .row {
88
+ display: flex;
89
+ align-items: center;
90
+ gap: 0.75rem;
91
+ min-height: var(--touch-min);
92
+ padding: var(--pad-row);
93
+ border-bottom: 1px solid var(--color-border-subtle);
94
+ cursor: pointer;
95
+ transition: background var(--duration-fast) var(--ease-quint);
96
+ }
97
+ .row:last-child { border-bottom: 0; }
98
+ .row:hover, .row:active { background: var(--color-surface-sunken); }
99
+ .row__icon {
100
+ width: 2rem; height: 2rem; border-radius: 0.625rem; flex-shrink: 0;
101
+ display: flex; align-items: center; justify-content: center;
102
+ font-size: 0.875rem; background: var(--color-surface-sunken);
103
+ }
104
+ .row__main { flex: 1; min-width: 0; }
105
+ .row__t { font-size: var(--step-body); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
106
+ .row__s { font-size: var(--step-xs); color: var(--color-text-muted); margin-top: 0.0625rem; }
107
+
108
+ /* ---- Balance card — app only ---- */
109
+ .balance { background: var(--color-accent); color: #fff; border-radius: var(--radius-lg); padding: 1.125rem; }
110
+ .balance__k { font-size: var(--step-xs); text-transform: uppercase; letter-spacing: 0.09em; }
111
+ .balance__v {
112
+ font-family: var(--font-ui); font-size: 2rem; font-weight: 600;
113
+ letter-spacing: -0.02em; font-variant-numeric: tabular-nums; margin-top: 0.25rem; line-height: 1.1;
114
+ }
115
+
116
+ /* ---- Segmented control — app only ---- */
117
+ .seg { display: flex; background: var(--color-surface-sunken); border-radius: var(--radius-sm); padding: 2px; gap: 2px; }
118
+ .seg button {
119
+ flex: 1; min-height: 2.25rem; border: 0; border-radius: calc(var(--radius-sm) - 2px);
120
+ background: transparent; font-family: inherit; font-size: var(--step-sm); font-weight: 600;
121
+ color: var(--color-text-secondary); cursor: pointer;
122
+ transition: background var(--duration-fast) var(--ease-quint), color var(--duration-fast) var(--ease-quint);
123
+ }
124
+ .seg button[aria-pressed="true"] { background: var(--color-surface-raised); color: var(--color-text-primary); }
125
+
126
+ /* ---- Tab bar — app only, no web equivalent ---- */
127
+ .tabs {
128
+ display: flex; background: var(--color-surface-raised);
129
+ border-top: 1px solid var(--color-border-subtle); padding-bottom: var(--safe-bottom);
130
+ }
131
+ .tabs button {
132
+ flex: 1; min-height: var(--touch-min); border: 0; background: transparent; cursor: pointer;
133
+ display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.1875rem;
134
+ font-family: inherit; font-size: 0.625rem; font-weight: 600; color: var(--color-text-muted);
135
+ transition: color var(--duration-fast) var(--ease-quint);
136
+ }
137
+ .tabs button[aria-pressed="true"] { color: var(--color-accent); }
package/src/index.css ADDED
@@ -0,0 +1,11 @@
1
+ /* ==========================================================================
2
+ @tfrc/product — entry point
3
+ Import order: foundation (ramps + reset) -> tokens (semantic layer) ->
4
+ components. A product theme (see src/themes/) imports THIS file first,
5
+ then layers its own overrides on top. Never import anything from
6
+ @tfrc/marketing — see AGENTS.md rule 3 and scripts/lint-boundaries.js.
7
+ ========================================================================== */
8
+
9
+ @import "@tfrc/foundation";
10
+ @import "./tokens.css";
11
+ @import "./components.css";
@@ -0,0 +1,41 @@
1
+ /* ==========================================================================
2
+ @tfrc/product/themes/finance — the first product theme
3
+ Themes over @tfrc/product; does not fork it. See decisions/0001.md — "one
4
+ product system, not one per product" — this file is the proof of that
5
+ principle, not an exception to it.
6
+
7
+ For v1, the finance theme needs no token overrides: the base @tfrc/product
8
+ semantic layer already IS the finance-appropriate one (reserved gain/
9
+ loss, tabular numerals, 44px touch floor). This file exists so future
10
+ product themes have a real example to follow, and so finance-specific
11
+ additions have an obvious home instead of leaking into @tfrc/product itself.
12
+ ========================================================================== */
13
+
14
+ @import "@tfrc/product";
15
+
16
+ :root {
17
+ /* This theme's identity within the product spectrum. See
18
+ decisions/0005-product-spectrum-hue-ceiling.md before reusing this
19
+ hue elsewhere, and before adding a new product theme. */
20
+ --theme-product-hue: var(--product-capital);
21
+ --theme-product-hue-tint: var(--color-accent-bg);
22
+ }
23
+
24
+ /* Finance-specific composition, not present in base @tfrc/product: a labeled
25
+ accent strip for surfaces that need to say "this is the finance
26
+ product" inside a cross-product shell (e.g. a unified dashboard). */
27
+ .theme-tag {
28
+ display: inline-flex;
29
+ align-items: center;
30
+ gap: 0.375rem;
31
+ font-size: var(--step-xs);
32
+ font-weight: 600;
33
+ color: var(--theme-product-hue);
34
+ }
35
+ .theme-tag::before {
36
+ content: "";
37
+ width: 0.5rem;
38
+ height: 0.5rem;
39
+ border-radius: 9999px;
40
+ background: var(--theme-product-hue);
41
+ }
package/src/tokens.css ADDED
@@ -0,0 +1,72 @@
1
+ /* ==========================================================================
2
+ @tfrc/product — semantic tokens
3
+ Same token NAMES as @tfrc/marketing (see CONTRACT.md). Values diverge on
4
+ purpose. Self-contained per decisions/0004.md — does not import
5
+ anything from @tfrc/marketing.
6
+ ========================================================================== */
7
+
8
+ :root {
9
+ --color-surface: oklch(0.980 0.004 270);
10
+ --color-surface-raised: oklch(1 0 0);
11
+ --color-surface-sunken: oklch(0.958 0.006 270);
12
+
13
+ --color-border-subtle: oklch(0.916 0.009 270);
14
+ --color-border-strong: oklch(0.812 0.015 270);
15
+
16
+ --color-text-primary: var(--ramp-neutral-900);
17
+ --color-text-secondary: var(--ramp-neutral-600);
18
+ --color-text-muted: var(--ramp-neutral-400);
19
+
20
+ --color-accent: var(--ramp-brand-600);
21
+ --color-accent-hover: var(--ramp-brand-700);
22
+ --color-accent-fg: #fff;
23
+ --color-accent-bg: var(--ramp-brand-100);
24
+ --color-accent-bg-fg: var(--ramp-brand-800);
25
+
26
+ --color-focus: oklch(0.560 0.150 235);
27
+
28
+ --color-info-bg: oklch(0.940 0.032 235);
29
+ --color-info-fg: oklch(0.360 0.070 237);
30
+ --color-info-line: oklch(0.560 0.150 235);
31
+
32
+ --color-danger-solid: oklch(0.615 0.205 25);
33
+ --color-danger-bg: oklch(0.940 0.035 25);
34
+ --color-danger-fg: oklch(0.475 0.170 25);
35
+ --color-danger-line: oklch(0.615 0.205 25);
36
+
37
+ --color-success-bg: oklch(0.940 0.040 150);
38
+ --color-success-fg: oklch(0.455 0.090 150);
39
+ --color-success-line: oklch(0.590 0.110 150);
40
+
41
+ /* Reserved — this package OWNS these. @tfrc/marketing must never define or
42
+ reference them. See decisions/0002.md. */
43
+ --color-gain: oklch(0.510 0.120 150);
44
+ --color-gain-tint: oklch(0.945 0.045 150);
45
+ --color-loss: oklch(0.530 0.190 25);
46
+ --color-loss-tint: oklch(0.940 0.040 25);
47
+
48
+ /* ---- product-only scale: this is most of the divergence ---- */
49
+ --font-display: "Bricolage Grotesque", system-ui, sans-serif;
50
+ --font-ui: "IBM Plex Sans", system-ui, -apple-system, sans-serif;
51
+ --font-mono: "JetBrains Mono", ui-monospace, monospace;
52
+
53
+ --step-title: 1.75rem; /* web hero is up to 6rem */
54
+ --step-lg: 1.25rem;
55
+ --step-body: 0.9375rem; /* web body is 1.0625rem */
56
+ --step-sm: 0.8125rem;
57
+ --step-xs: 0.6875rem;
58
+
59
+ --touch-min: 2.75rem; /* 44px. A floor, not a guideline. */
60
+ --radius-sm: 0.5rem;
61
+ --radius-md: 0.75rem;
62
+ --radius-lg: 1rem; /* web goes to 1.5rem */
63
+
64
+ --pad-screen: 1rem; /* web gutter goes up to 4rem */
65
+ --pad-row: 0.75rem;
66
+
67
+ --duration-fast: 100ms;
68
+ --duration-base: 180ms; /* web goes to 640ms */
69
+
70
+ --safe-top: env(safe-area-inset-top, 0px);
71
+ --safe-bottom: env(safe-area-inset-bottom, 0px);
72
+ }