@rs-x/cli 2.0.0-next.14 → 2.0.0-next.16
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/bin/rsx.cjs +113 -1
- package/package.json +1 -1
- package/{rs-x-vscode-extension-2.0.0-next.14.vsix → rs-x-vscode-extension-2.0.0-next.16.vsix} +0 -0
- package/templates/next-demo/README.md +26 -0
- package/templates/next-demo/app/globals.css +431 -0
- package/templates/next-demo/app/layout.tsx +22 -0
- package/templates/next-demo/app/page.tsx +5 -0
- package/templates/next-demo/components/demo-app.tsx +110 -0
- package/templates/next-demo/components/virtual-table-row.tsx +40 -0
- package/templates/next-demo/components/virtual-table-shell.tsx +86 -0
- package/templates/next-demo/hooks/use-virtual-table-controller.ts +26 -0
- package/templates/next-demo/hooks/use-virtual-table-viewport.ts +41 -0
- package/templates/next-demo/lib/row-data.ts +35 -0
- package/templates/next-demo/lib/row-model.ts +45 -0
- package/templates/next-demo/lib/rsx-bootstrap.ts +46 -0
- package/templates/next-demo/lib/virtual-table-controller.ts +247 -0
- package/templates/next-demo/lib/virtual-table-data.service.ts +126 -0
- package/templates/react-demo/src/rsx-bootstrap.ts +35 -5
- package/templates/react-demo/tsconfig.json +1 -1
package/bin/rsx.cjs
CHANGED
|
@@ -27,6 +27,12 @@ const REACT_DEMO_TEMPLATE_DIR = path.join(
|
|
|
27
27
|
'templates',
|
|
28
28
|
'react-demo',
|
|
29
29
|
);
|
|
30
|
+
const NEXT_DEMO_TEMPLATE_DIR = path.join(
|
|
31
|
+
__dirname,
|
|
32
|
+
'..',
|
|
33
|
+
'templates',
|
|
34
|
+
'next-demo',
|
|
35
|
+
);
|
|
30
36
|
const RUNTIME_PACKAGES = [
|
|
31
37
|
'@rs-x/core',
|
|
32
38
|
'@rs-x/state-manager',
|
|
@@ -1681,6 +1687,112 @@ function applyReactDemoStarter(projectRoot, projectName, pm, flags) {
|
|
|
1681
1687
|
}
|
|
1682
1688
|
}
|
|
1683
1689
|
|
|
1690
|
+
function applyNextDemoStarter(projectRoot, projectName, pm, flags) {
|
|
1691
|
+
const dryRun = Boolean(flags['dry-run']);
|
|
1692
|
+
const tag = resolveInstallTag(flags);
|
|
1693
|
+
const tarballsDir =
|
|
1694
|
+
typeof flags['tarballs-dir'] === 'string'
|
|
1695
|
+
? path.resolve(process.cwd(), flags['tarballs-dir'])
|
|
1696
|
+
: typeof process.env.RSX_TARBALLS_DIR === 'string' &&
|
|
1697
|
+
process.env.RSX_TARBALLS_DIR.trim().length > 0
|
|
1698
|
+
? path.resolve(process.cwd(), process.env.RSX_TARBALLS_DIR)
|
|
1699
|
+
: null;
|
|
1700
|
+
const workspaceRoot = findRepoRoot(projectRoot);
|
|
1701
|
+
const rsxSpecs = resolveProjectRsxSpecs(
|
|
1702
|
+
projectRoot,
|
|
1703
|
+
workspaceRoot,
|
|
1704
|
+
tarballsDir,
|
|
1705
|
+
{ tag, includeReactPackage: true },
|
|
1706
|
+
);
|
|
1707
|
+
|
|
1708
|
+
const templateFiles = ['README.md', 'app', 'components', 'hooks', 'lib'];
|
|
1709
|
+
for (const entry of templateFiles) {
|
|
1710
|
+
copyPathWithDryRun(
|
|
1711
|
+
path.join(NEXT_DEMO_TEMPLATE_DIR, entry),
|
|
1712
|
+
path.join(projectRoot, entry),
|
|
1713
|
+
dryRun,
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
const readmePath = path.join(projectRoot, 'README.md');
|
|
1718
|
+
if (fs.existsSync(readmePath)) {
|
|
1719
|
+
const readmeSource = fs.readFileSync(readmePath, 'utf8');
|
|
1720
|
+
const nextReadme = readmeSource.replace(
|
|
1721
|
+
/^#\s+rsx-next-example/mu,
|
|
1722
|
+
`# ${projectName}`,
|
|
1723
|
+
);
|
|
1724
|
+
if (dryRun) {
|
|
1725
|
+
logInfo(`[dry-run] patch ${readmePath}`);
|
|
1726
|
+
} else {
|
|
1727
|
+
fs.writeFileSync(readmePath, nextReadme, 'utf8');
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
const publicDir = path.join(projectRoot, 'public');
|
|
1732
|
+
removeFileOrDirectoryWithDryRun(publicDir, dryRun);
|
|
1733
|
+
|
|
1734
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
1735
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
1736
|
+
logError(`package.json not found in generated Next.js app: ${packageJsonPath}`);
|
|
1737
|
+
process.exit(1);
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
1741
|
+
packageJson.name = projectName;
|
|
1742
|
+
packageJson.private = true;
|
|
1743
|
+
packageJson.version = '0.1.0';
|
|
1744
|
+
packageJson.scripts = {
|
|
1745
|
+
...packageJson.scripts,
|
|
1746
|
+
'build:rsx': 'rsx build --project tsconfig.json --no-emit --prod',
|
|
1747
|
+
dev: 'npm run build:rsx && next dev',
|
|
1748
|
+
build: 'npm run build:rsx && next build',
|
|
1749
|
+
start: 'next start',
|
|
1750
|
+
};
|
|
1751
|
+
packageJson.rsx = {
|
|
1752
|
+
build: {
|
|
1753
|
+
preparse: true,
|
|
1754
|
+
preparseFile: 'app/rsx-generated/rsx-aot-preparsed.generated.ts',
|
|
1755
|
+
compiled: true,
|
|
1756
|
+
compiledFile: 'app/rsx-generated/rsx-aot-compiled.generated.ts',
|
|
1757
|
+
compiledResolvedEvaluator: false,
|
|
1758
|
+
},
|
|
1759
|
+
};
|
|
1760
|
+
packageJson.dependencies = {
|
|
1761
|
+
...(packageJson.dependencies ?? {}),
|
|
1762
|
+
'@rs-x/core': rsxSpecs['@rs-x/core'],
|
|
1763
|
+
'@rs-x/state-manager': rsxSpecs['@rs-x/state-manager'],
|
|
1764
|
+
'@rs-x/expression-parser': rsxSpecs['@rs-x/expression-parser'],
|
|
1765
|
+
'@rs-x/react': rsxSpecs['@rs-x/react'],
|
|
1766
|
+
};
|
|
1767
|
+
packageJson.devDependencies = {
|
|
1768
|
+
...(packageJson.devDependencies ?? {}),
|
|
1769
|
+
'@rs-x/cli': rsxSpecs['@rs-x/cli'],
|
|
1770
|
+
'@rs-x/compiler': rsxSpecs['@rs-x/compiler'],
|
|
1771
|
+
'@rs-x/typescript-plugin': rsxSpecs['@rs-x/typescript-plugin'],
|
|
1772
|
+
};
|
|
1773
|
+
|
|
1774
|
+
if (dryRun) {
|
|
1775
|
+
logInfo(`[dry-run] patch ${packageJsonPath}`);
|
|
1776
|
+
} else {
|
|
1777
|
+
fs.writeFileSync(
|
|
1778
|
+
packageJsonPath,
|
|
1779
|
+
`${JSON.stringify(packageJson, null, 2)}\n`,
|
|
1780
|
+
'utf8',
|
|
1781
|
+
);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
const tsConfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
1785
|
+
if (fs.existsSync(tsConfigPath)) {
|
|
1786
|
+
upsertTypescriptPluginInTsConfig(tsConfigPath, dryRun);
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
if (!Boolean(flags['skip-install'])) {
|
|
1790
|
+
logInfo(`Refreshing ${pm} dependencies for the RS-X Next.js starter...`);
|
|
1791
|
+
run(pm, ['install'], { dryRun });
|
|
1792
|
+
logOk('Next.js starter dependencies are up to date.');
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1684
1796
|
async function runProjectWithTemplate(template, flags) {
|
|
1685
1797
|
const normalizedTemplate = normalizeProjectTemplate(template);
|
|
1686
1798
|
if (!normalizedTemplate) {
|
|
@@ -1720,7 +1832,7 @@ async function runProjectWithTemplate(template, flags) {
|
|
|
1720
1832
|
return;
|
|
1721
1833
|
}
|
|
1722
1834
|
if (normalizedTemplate === 'nextjs') {
|
|
1723
|
-
|
|
1835
|
+
applyNextDemoStarter(projectRoot, projectName, pm, flags);
|
|
1724
1836
|
return;
|
|
1725
1837
|
}
|
|
1726
1838
|
if (normalizedTemplate === 'vuejs') {
|
package/package.json
CHANGED
package/{rs-x-vscode-extension-2.0.0-next.14.vsix → rs-x-vscode-extension-2.0.0-next.16.vsix}
RENAMED
|
Binary file
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# rsx-next-example
|
|
2
|
+
|
|
3
|
+
Website & docs: https://www.rsxjs.com/
|
|
4
|
+
|
|
5
|
+
This starter shows how to use RS-X in a Next.js app-router project with a
|
|
6
|
+
million-row virtual table that keeps rendering and expression memory bounded.
|
|
7
|
+
|
|
8
|
+
## Scripts
|
|
9
|
+
|
|
10
|
+
- `npm run dev` starts the Next.js dev server after running the RS-X build step
|
|
11
|
+
- `npm run build` generates RS-X artifacts and builds the production app
|
|
12
|
+
- `npm run start` starts the production server
|
|
13
|
+
|
|
14
|
+
## Structure
|
|
15
|
+
|
|
16
|
+
- `app/` contains the Next.js route files and global styles
|
|
17
|
+
- `components/` contains the client-side UI components
|
|
18
|
+
- `hooks/` contains reusable React hooks
|
|
19
|
+
- `lib/` contains the RS-X bootstrap and virtual-table state/data utilities
|
|
20
|
+
|
|
21
|
+
## Notes
|
|
22
|
+
|
|
23
|
+
- The demo defaults to dark mode.
|
|
24
|
+
- The UI uses `@rs-x/react` hooks in a Next.js client component tree.
|
|
25
|
+
- The generated RS-X cache files in `app/rsx-generated` are created by
|
|
26
|
+
`npm run build:rsx`; they are not checked into the starter template.
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--font-sans:
|
|
3
|
+
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
4
|
+
sans-serif;
|
|
5
|
+
--font-mono:
|
|
6
|
+
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Courier New',
|
|
7
|
+
monospace;
|
|
8
|
+
--bg: #f6f8fc;
|
|
9
|
+
--surface: rgba(255, 255, 255, 0.86);
|
|
10
|
+
--surface-2: rgba(255, 255, 255, 0.72);
|
|
11
|
+
--surface-solid: #ffffff;
|
|
12
|
+
--text: #0b1324;
|
|
13
|
+
--muted: rgba(11, 19, 36, 0.66);
|
|
14
|
+
--border: rgba(10, 25, 55, 0.14);
|
|
15
|
+
--border-soft: rgba(10, 25, 55, 0.1);
|
|
16
|
+
--brand: #0b66ff;
|
|
17
|
+
--brand-2: #2bb6a9;
|
|
18
|
+
--focus: rgba(11, 102, 255, 0.35);
|
|
19
|
+
--shadow-1:
|
|
20
|
+
0 1px 2px rgba(16, 24, 40, 0.06), 0 12px 34px rgba(16, 24, 40, 0.1);
|
|
21
|
+
--shadow-2:
|
|
22
|
+
0 2px 10px rgba(16, 24, 40, 0.08), 0 18px 52px rgba(16, 24, 40, 0.12);
|
|
23
|
+
--page-glow-a: rgba(11, 102, 255, 0.06);
|
|
24
|
+
--page-glow-b: rgba(43, 182, 169, 0.05);
|
|
25
|
+
--hero-section-bg: linear-gradient(
|
|
26
|
+
180deg,
|
|
27
|
+
rgba(255, 255, 255, 0.72),
|
|
28
|
+
rgba(255, 255, 255, 0.42)
|
|
29
|
+
);
|
|
30
|
+
--r-xl: 1.75rem;
|
|
31
|
+
--sp-2: 0.5rem;
|
|
32
|
+
--sp-3: 0.75rem;
|
|
33
|
+
--sp-4: 1rem;
|
|
34
|
+
--sp-5: 1.25rem;
|
|
35
|
+
--sp-6: 1.5rem;
|
|
36
|
+
--sp-7: 2rem;
|
|
37
|
+
--sp-8: 2.5rem;
|
|
38
|
+
--sp-9: 3rem;
|
|
39
|
+
--sp-10: 4rem;
|
|
40
|
+
--container: 1120px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
html[data-theme='dark'],
|
|
44
|
+
body[data-theme='dark'] {
|
|
45
|
+
--bg: #070b14;
|
|
46
|
+
--surface: rgba(12, 18, 35, 0.82);
|
|
47
|
+
--surface-2: rgba(12, 18, 35, 0.68);
|
|
48
|
+
--surface-solid: #0c1223;
|
|
49
|
+
--text: #edf2ff;
|
|
50
|
+
--muted: rgba(237, 242, 255, 0.7);
|
|
51
|
+
--border: rgba(237, 242, 255, 0.16);
|
|
52
|
+
--border-soft: rgba(237, 242, 255, 0.12);
|
|
53
|
+
--brand: #7ab6ff;
|
|
54
|
+
--brand-2: #49d9c8;
|
|
55
|
+
--focus: rgba(122, 182, 255, 0.35);
|
|
56
|
+
--shadow-1: 0 1px 2px rgba(0, 0, 0, 0.22), 0 12px 34px rgba(0, 0, 0, 0.32);
|
|
57
|
+
--shadow-2: 0 2px 10px rgba(0, 0, 0, 0.24), 0 18px 52px rgba(0, 0, 0, 0.34);
|
|
58
|
+
--page-glow-a: rgba(122, 182, 255, 0.1);
|
|
59
|
+
--page-glow-b: rgba(73, 217, 200, 0.08);
|
|
60
|
+
--hero-section-bg: linear-gradient(
|
|
61
|
+
180deg,
|
|
62
|
+
rgba(12, 18, 35, 0.56),
|
|
63
|
+
rgba(12, 18, 35, 0.24)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
*,
|
|
68
|
+
*::before,
|
|
69
|
+
*::after {
|
|
70
|
+
box-sizing: border-box;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
html,
|
|
74
|
+
body,
|
|
75
|
+
#root {
|
|
76
|
+
min-height: 100%;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
html {
|
|
80
|
+
color-scheme: dark;
|
|
81
|
+
background: var(--bg);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
html[data-theme='light'] {
|
|
85
|
+
color-scheme: light;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
body {
|
|
89
|
+
margin: 0;
|
|
90
|
+
font-family: var(--font-sans);
|
|
91
|
+
color: var(--text);
|
|
92
|
+
background-color: var(--bg);
|
|
93
|
+
background:
|
|
94
|
+
radial-gradient(circle at top, var(--page-glow-a), transparent 32%),
|
|
95
|
+
radial-gradient(circle at 85% 12%, var(--page-glow-b), transparent 28%),
|
|
96
|
+
var(--bg);
|
|
97
|
+
transition:
|
|
98
|
+
background 180ms ease,
|
|
99
|
+
color 180ms ease;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
button,
|
|
103
|
+
input,
|
|
104
|
+
select,
|
|
105
|
+
textarea {
|
|
106
|
+
font: inherit;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.container {
|
|
110
|
+
max-width: var(--container);
|
|
111
|
+
margin: 0 auto;
|
|
112
|
+
padding: 0 var(--sp-6);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.app-shell {
|
|
116
|
+
min-height: 100vh;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.hero {
|
|
120
|
+
position: relative;
|
|
121
|
+
padding: calc(var(--sp-10) + 1rem) 0 calc(var(--sp-8) + 0.5rem);
|
|
122
|
+
background: var(--hero-section-bg);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.heroGrid {
|
|
126
|
+
display: grid;
|
|
127
|
+
grid-template-columns: minmax(540px, 1.05fr) minmax(300px, 0.95fr);
|
|
128
|
+
column-gap: var(--sp-6);
|
|
129
|
+
row-gap: var(--sp-8);
|
|
130
|
+
align-items: center;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.heroLeft {
|
|
134
|
+
min-width: 0;
|
|
135
|
+
padding-top: var(--sp-4);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.app-eyebrow {
|
|
139
|
+
margin: 0 0 var(--sp-4);
|
|
140
|
+
letter-spacing: 0.2em;
|
|
141
|
+
text-transform: uppercase;
|
|
142
|
+
font-size: 0.82rem;
|
|
143
|
+
font-weight: 700;
|
|
144
|
+
color: var(--brand);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.hTitle {
|
|
148
|
+
margin: 0 0 var(--sp-5);
|
|
149
|
+
font-size: clamp(3.6rem, 5.2vw, 4.8rem);
|
|
150
|
+
line-height: 0.96;
|
|
151
|
+
letter-spacing: -0.052em;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.hSubhead {
|
|
155
|
+
margin: 0 0 var(--sp-2);
|
|
156
|
+
font-size: clamp(1.42rem, 2vw, 1.82rem);
|
|
157
|
+
font-weight: 640;
|
|
158
|
+
letter-spacing: -0.03em;
|
|
159
|
+
line-height: 1.08;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.hSub {
|
|
163
|
+
margin: var(--sp-5) 0 0;
|
|
164
|
+
color: var(--muted);
|
|
165
|
+
font-size: 1.1rem;
|
|
166
|
+
line-height: 1.72;
|
|
167
|
+
max-width: 31.5rem;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.heroActions {
|
|
171
|
+
display: flex;
|
|
172
|
+
flex-wrap: wrap;
|
|
173
|
+
gap: var(--sp-3);
|
|
174
|
+
margin-top: var(--sp-5);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.btn {
|
|
178
|
+
display: inline-flex;
|
|
179
|
+
align-items: center;
|
|
180
|
+
justify-content: center;
|
|
181
|
+
gap: var(--sp-2);
|
|
182
|
+
padding: 0.82rem 1.18rem;
|
|
183
|
+
border-radius: 14px;
|
|
184
|
+
border: 1px solid transparent;
|
|
185
|
+
font-weight: 800;
|
|
186
|
+
line-height: 1;
|
|
187
|
+
white-space: nowrap;
|
|
188
|
+
transition:
|
|
189
|
+
transform 160ms ease,
|
|
190
|
+
background 240ms ease,
|
|
191
|
+
border-color 240ms ease,
|
|
192
|
+
box-shadow 240ms ease;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.btn:active {
|
|
196
|
+
transform: translateY(1px);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.btnGhost {
|
|
200
|
+
background: var(--surface-2);
|
|
201
|
+
color: var(--text);
|
|
202
|
+
border-color: var(--border);
|
|
203
|
+
box-shadow: none;
|
|
204
|
+
text-decoration: none;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.btnGhost:hover {
|
|
208
|
+
box-shadow: var(--shadow-1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.card {
|
|
212
|
+
position: relative;
|
|
213
|
+
min-width: 0;
|
|
214
|
+
border: 1px solid var(--border-soft);
|
|
215
|
+
background: linear-gradient(180deg, var(--surface), var(--surface-2));
|
|
216
|
+
border-radius: var(--r-xl);
|
|
217
|
+
padding: var(--sp-6);
|
|
218
|
+
box-shadow: var(--shadow-1);
|
|
219
|
+
display: flex;
|
|
220
|
+
flex-direction: column;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.cardTitle {
|
|
224
|
+
margin: 0 0 var(--sp-3);
|
|
225
|
+
font-weight: 700;
|
|
226
|
+
font-size: clamp(1.12rem, 0.96vw, 1.22rem);
|
|
227
|
+
letter-spacing: -0.02em;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.cardText {
|
|
231
|
+
margin: 0;
|
|
232
|
+
color: var(--muted);
|
|
233
|
+
line-height: 1.62;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.heroNote {
|
|
237
|
+
align-self: center;
|
|
238
|
+
margin-top: 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.section {
|
|
242
|
+
padding: var(--sp-7) 0 var(--sp-10);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.app-panel {
|
|
246
|
+
backdrop-filter: blur(12px);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.theme-toggle {
|
|
250
|
+
cursor: pointer;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.table-toolbar {
|
|
254
|
+
display: flex;
|
|
255
|
+
align-items: center;
|
|
256
|
+
justify-content: space-between;
|
|
257
|
+
gap: 16px;
|
|
258
|
+
padding-bottom: 16px;
|
|
259
|
+
border-bottom: 1px solid var(--border-soft);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.toolbar-left h2 {
|
|
263
|
+
margin: 0;
|
|
264
|
+
font-size: 20px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.toolbar-left p {
|
|
268
|
+
margin: 4px 0 0;
|
|
269
|
+
color: var(--muted);
|
|
270
|
+
font-size: 13px;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.toolbar-right {
|
|
274
|
+
display: flex;
|
|
275
|
+
flex-wrap: wrap;
|
|
276
|
+
gap: 8px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.toolbar-right button {
|
|
280
|
+
border: 1px solid var(--border);
|
|
281
|
+
background: color-mix(in srgb, var(--surface-solid) 88%, var(--brand) 12%);
|
|
282
|
+
color: var(--text);
|
|
283
|
+
padding: 6px 12px;
|
|
284
|
+
border-radius: 999px;
|
|
285
|
+
font-size: 12px;
|
|
286
|
+
cursor: pointer;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.toolbar-right button:hover {
|
|
290
|
+
border-color: var(--focus);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.table-header {
|
|
294
|
+
display: grid;
|
|
295
|
+
grid-template-columns: 80px 1.4fr 1fr 0.8fr 0.6fr 1fr 0.9fr;
|
|
296
|
+
gap: 8px;
|
|
297
|
+
padding: 12px 8px;
|
|
298
|
+
font-size: 12px;
|
|
299
|
+
font-weight: 600;
|
|
300
|
+
color: var(--muted);
|
|
301
|
+
text-transform: uppercase;
|
|
302
|
+
letter-spacing: 0.06em;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.table-viewport {
|
|
306
|
+
position: relative;
|
|
307
|
+
height: 520px;
|
|
308
|
+
overflow: auto;
|
|
309
|
+
border: 1px solid var(--border-soft);
|
|
310
|
+
border-radius: 12px;
|
|
311
|
+
background: color-mix(in srgb, var(--surface-solid) 94%, transparent);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.table-spacer {
|
|
315
|
+
width: 100%;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.table-row {
|
|
319
|
+
position: absolute;
|
|
320
|
+
top: 0;
|
|
321
|
+
left: 0;
|
|
322
|
+
right: 0;
|
|
323
|
+
height: 36px;
|
|
324
|
+
display: grid;
|
|
325
|
+
grid-template-columns: 80px 1.4fr 1fr 0.8fr 0.6fr 1fr 0.9fr;
|
|
326
|
+
align-items: center;
|
|
327
|
+
gap: 8px;
|
|
328
|
+
padding: 0 8px;
|
|
329
|
+
border-bottom: 1px solid var(--border-soft);
|
|
330
|
+
font-size: 13px;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.table-row:nth-of-type(odd) {
|
|
334
|
+
background: color-mix(in srgb, var(--surface-solid) 84%, transparent);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.table-row .total {
|
|
338
|
+
font-weight: 600;
|
|
339
|
+
color: var(--brand);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.table-footer {
|
|
343
|
+
display: flex;
|
|
344
|
+
justify-content: space-between;
|
|
345
|
+
margin-top: 12px;
|
|
346
|
+
font-size: 12px;
|
|
347
|
+
color: var(--muted);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.table-loading {
|
|
351
|
+
display: grid;
|
|
352
|
+
place-items: center;
|
|
353
|
+
min-height: 520px;
|
|
354
|
+
color: var(--muted);
|
|
355
|
+
font-size: 1rem;
|
|
356
|
+
letter-spacing: 0.03em;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
@media (max-width: 920px) {
|
|
360
|
+
.heroGrid {
|
|
361
|
+
grid-template-columns: 1fr;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
@media (max-width: 900px) {
|
|
366
|
+
.table-header,
|
|
367
|
+
.table-row {
|
|
368
|
+
grid-template-columns: 72px 1.3fr 1fr 0.8fr 0.7fr 1fr;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.table-header span:last-child,
|
|
372
|
+
.table-row span:last-child {
|
|
373
|
+
display: none;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
@media (max-width: 720px) {
|
|
378
|
+
.container {
|
|
379
|
+
padding: 0 var(--sp-4);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.hero {
|
|
383
|
+
padding: calc(var(--sp-8) + 0.5rem) 0 var(--sp-8);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.hTitle {
|
|
387
|
+
font-size: clamp(2.9rem, 14vw, 3.6rem);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.table-toolbar,
|
|
391
|
+
.table-footer {
|
|
392
|
+
flex-direction: column;
|
|
393
|
+
align-items: flex-start;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.table-header {
|
|
397
|
+
display: none;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.table-viewport {
|
|
401
|
+
height: 460px;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.table-row {
|
|
405
|
+
height: 168px;
|
|
406
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
407
|
+
align-content: start;
|
|
408
|
+
gap: 10px 16px;
|
|
409
|
+
padding: 14px 16px;
|
|
410
|
+
border: 1px solid var(--border-soft);
|
|
411
|
+
border-radius: 18px;
|
|
412
|
+
margin: 0 8px;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.table-row span {
|
|
416
|
+
display: flex;
|
|
417
|
+
flex-direction: column;
|
|
418
|
+
gap: 4px;
|
|
419
|
+
min-width: 0;
|
|
420
|
+
font-size: 0.95rem;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.table-row span::before {
|
|
424
|
+
content: attr(data-label);
|
|
425
|
+
color: var(--muted);
|
|
426
|
+
font-size: 0.72rem;
|
|
427
|
+
font-weight: 700;
|
|
428
|
+
letter-spacing: 0.06em;
|
|
429
|
+
text-transform: uppercase;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import './globals.css';
|
|
5
|
+
|
|
6
|
+
export const metadata: Metadata = {
|
|
7
|
+
title: 'RS-X Next.js Demo',
|
|
8
|
+
description:
|
|
9
|
+
'Million-row virtual scrolling with a fixed RS-X expression pool in Next.js.',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type RootLayoutProps = Readonly<{
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
}>;
|
|
15
|
+
|
|
16
|
+
export default function RootLayout({ children }: RootLayoutProps) {
|
|
17
|
+
return (
|
|
18
|
+
<html lang="en" data-theme="dark">
|
|
19
|
+
<body data-theme="dark">{children}</body>
|
|
20
|
+
</html>
|
|
21
|
+
);
|
|
22
|
+
}
|