Skip to content

Commit ff3f641

Browse files
authored
playground: add share button (#356)
1 parent 4281721 commit ff3f641

File tree

5 files changed

+94
-84
lines changed

5 files changed

+94
-84
lines changed

AGENTS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,11 @@ This document summarizes how to work on rslint effectively and consistently.
5353
- rslint loads `rslint.json`/`rslint.jsonc`; rules accept ESLint-style levels/options.
5454
- The linter walks each file once and dispatches to registered listeners; `--singleThreaded` disables parallelism.
5555
- Use `--format github` in CI to emit GitHub workflow annotations.
56+
57+
## Website UI Guidelines (shadcn/ui)
58+
59+
- Prefer shadcn/ui components from `@components/ui/*` (e.g., `button`, `toggle-group`, `alert`, `card`, `table`) over custom elements.
60+
- Minimize custom CSS. Use component variants, utility classes, and existing styles instead of adding new selectors when possible.
61+
- Icons: use `lucide-react` for consistent iconography (e.g., import `{ Share2Icon, CheckIcon } from 'lucide-react'`).
62+
- Keep layout simple: compose shadcn primitives and flex utilities for alignment instead of bespoke CSS blocks.
63+
- Only add custom CSS for domain‑specific visuals that primitives can’t express (e.g., AST tree expanders), and keep it scoped.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"build": "pnpm -r --filter=!@typescript/api --filter=!@typescript/ast --filter='@rslint/test-tools...' --filter='rslint...' build",
1717
"build:npm": "zx scripts/build-npm.mjs",
1818
"build:website": "./scripts/apply-tsgo-patch.sh && pnpm --filter=!@typescript/api --filter=!@typescript/ast --filter=!@rslint/core --filter @rslint/website... -r build",
19-
"check-spell": "pnpx cspell",
19+
"check-spell": "pnpx cspell lint --no-progress --show-context",
2020
"version": "zx scripts/version.mjs",
2121
"release": "pnpm publish -r --no-git-checks",
2222
"publish:vsce": "zx scripts/publish-marketplace.mjs",

scripts/dictionary.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,4 @@ Prettier
144144
ESLint
145145
colocate
146146
co-locate
147+
shadcn

website/theme/components/Playground/ResultPanel.css

Lines changed: 6 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,15 @@
11
.result-header {
22
background: #f6f8fa;
3-
}
4-
5-
.result-tabs {
63
display: flex;
7-
gap: 4px;
8-
padding: 4px;
4+
align-items: center;
5+
justify-content: space-between;
6+
padding: 4px 8px 0 8px;
97
}
108

11-
.result-tab {
12-
font-size: 0.8rem;
13-
padding: 8px 12px;
14-
15-
background: transparent;
16-
cursor: pointer;
17-
border-radius: 6px 6px 0 0;
9+
/* Actions area pinned to top-right of the header */
10+
.result-actions {
1811
display: flex;
1912
align-items: center;
20-
gap: 0.4em;
21-
}
22-
.result-tab:hover {
23-
color: #24292f;
24-
background: #e2e8f0;
25-
}
26-
.result-tab.active {
27-
background: #fff;
28-
color: #0969da;
29-
border-bottom: 2px solid #0969da;
30-
z-index: 1;
31-
}
32-
.tab-icon {
33-
font-size: 1.1em;
3413
}
3514

3615
.spinner {
@@ -69,41 +48,7 @@
6948
height: calc(100% - 8px);
7049
}
7150

72-
/* Messages */
73-
.error-message,
74-
.success-message {
75-
display: flex;
76-
align-items: center;
77-
gap: 0.75rem;
78-
padding: 1rem;
79-
border-radius: 6px;
80-
margin-bottom: 1rem;
81-
}
82-
83-
.error-message {
84-
background: #ffebe9;
85-
border: 1px solid #ff8182;
86-
color: #cf222e;
87-
}
88-
89-
.success-message {
90-
background: #ddf4d8;
91-
border: 1px solid #4ac26b;
92-
color: #116329;
93-
}
94-
95-
.error-icon,
96-
.success-icon {
97-
font-size: 1.25rem;
98-
flex-shrink: 0;
99-
}
100-
101-
.error-text,
102-
.success-text {
103-
flex: 1;
104-
font-size: 0.875rem;
105-
line-height: 1.4;
106-
}
51+
/* Shadcn Alert used for messages */
10752

10853
/* Diagnostics */
10954
.diagnostics-list {

website/theme/components/Playground/ResultPanel.tsx

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
import React, { useEffect, useState } from 'react';
2+
import { Button } from '@components/ui/button';
3+
import { Share2Icon, CheckIcon } from 'lucide-react';
4+
// Removed ToggleGroup in favor of Button to match Share style
5+
import { Alert, AlertDescription, AlertTitle } from '@components/ui/alert';
6+
import { AlertCircleIcon } from 'lucide-react';
27
import './ResultPanel.css';
38

49
export interface Diagnostic {
@@ -209,47 +214,77 @@ export const ResultPanel: React.FC<ResultPanelProps> = props => {
209214
});
210215
}, [astTree]);
211216

217+
// Share button state and handler
218+
const [shareCopied, setShareCopied] = useState(false);
219+
async function copyShareUrl() {
220+
try {
221+
const url = window.location.href;
222+
await copyToClipboard(url);
223+
setShareCopied(true);
224+
window.setTimeout(() => setShareCopied(false), 1500);
225+
} catch (e) {
226+
console.warn('Share failed', e);
227+
}
228+
}
229+
212230
return (
213231
<div className="result-panel">
214232
<div className="result-header">
215-
<div className="result-tabs">
216-
<div
217-
className={`result-tab ${activeTab === 'lint' ? 'active' : ''}`}
233+
<div className="flex items-center gap-2">
234+
<Button
235+
type="button"
236+
variant={activeTab === 'lint' ? 'default' : 'outline'}
237+
size="sm"
218238
onClick={() => setActiveTab('lint')}
219-
title="Errors"
239+
aria-pressed={activeTab === 'lint'}
220240
>
221241
Errors
222-
</div>
223-
<div
224-
className={`result-tab ${activeTab === 'ast' ? 'active' : ''}`}
242+
</Button>
243+
<Button
244+
type="button"
245+
variant={activeTab === 'ast' ? 'default' : 'outline'}
246+
size="sm"
225247
onClick={() => setActiveTab('ast')}
226-
title="AST"
248+
aria-pressed={activeTab === 'ast'}
227249
>
228-
AST(tsgo)
229-
</div>
250+
AST
251+
</Button>
252+
</div>
253+
<div className="result-actions">
254+
<Button
255+
type="button"
256+
variant="outline"
257+
size="sm"
258+
onClick={() => copyShareUrl()}
259+
title={shareCopied ? 'Copied link' : 'Copy shareable link'}
260+
>
261+
{shareCopied ? (
262+
<CheckIcon className="size-4" />
263+
) : (
264+
<Share2Icon className="size-4" />
265+
)}
266+
{shareCopied ? 'Copied' : 'Share'}
267+
</Button>
230268
</div>
231269
</div>
232270

233271
{initialized ? (
234272
<div className="result-content">
235273
{error && (
236-
<div className="error-message">
237-
<div className="error-icon">⚠️</div>
238-
<div className="error-text">
239-
<strong>Error:</strong> {error}
240-
</div>
241-
</div>
274+
<Alert variant="destructive">
275+
<AlertCircleIcon />
276+
<AlertTitle>Error</AlertTitle>
277+
<AlertDescription>{error}</AlertDescription>
278+
</Alert>
242279
)}
243280

244281
{!error && activeTab === 'lint' && (
245282
<div className="lint-results">
246283
{diagnostics.length === 0 ? (
247-
<div className="success-message">
248-
<div className="success-icon"></div>
249-
<div className="success-text">
250-
<strong>No issues found!</strong> Your code looks good.
251-
</div>
252-
</div>
284+
<Alert>
285+
<AlertTitle>No issues found!</AlertTitle>
286+
<AlertDescription>Your code looks good.</AlertDescription>
287+
</Alert>
253288
) : (
254289
<div className="diagnostics-list">
255290
{diagnostics.map((diagnostic, index) => (
@@ -308,3 +343,24 @@ export const ResultPanel: React.FC<ResultPanelProps> = props => {
308343
</div>
309344
);
310345
};
346+
347+
function copyToClipboard(text: string) {
348+
if (navigator.clipboard?.writeText)
349+
return navigator.clipboard.writeText(text);
350+
return new Promise<void>((resolve, reject) => {
351+
try {
352+
const ta = document.createElement('textarea');
353+
ta.value = text;
354+
ta.setAttribute('readonly', '');
355+
ta.style.position = 'absolute';
356+
ta.style.left = '-9999px';
357+
document.body.appendChild(ta);
358+
ta.select();
359+
document.execCommand('copy');
360+
document.body.removeChild(ta);
361+
resolve();
362+
} catch (e) {
363+
reject(e);
364+
}
365+
});
366+
}

0 commit comments

Comments
 (0)