Skip to content

Commit f9c1205

Browse files
authored
Add first shadcdn component (#62)
1 parent 3ab9cfb commit f9c1205

16 files changed

+1428
-142
lines changed

components.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema.json",
3+
"style": "new-york",
4+
"rsc": false,
5+
"tsx": true,
6+
"tailwind": {
7+
"config": "",
8+
"css": "src/index.css",
9+
"baseColor": "stone",
10+
"cssVariables": true,
11+
"prefix": ""
12+
},
13+
"aliases": {
14+
"components": "@/components",
15+
"utils": "@/lib/utils",
16+
"ui": "@/components/ui",
17+
"lib": "@/lib",
18+
"hooks": "@/hooks"
19+
},
20+
"iconLibrary": "lucide"
21+
}

package.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,14 @@
1818
"@monaco-editor/react": "^4.3.1",
1919
"@protobuf-ts/grpcweb-transport": "^2.9.4",
2020
"@protobuf-ts/plugin": "^2.6.0",
21+
"@radix-ui/react-alert-dialog": "^1.1.11",
22+
"@radix-ui/react-select": "^2.2.2",
23+
"@radix-ui/react-slot": "^1.2.0",
24+
"@radix-ui/react-tabs": "^1.1.9",
25+
"@tailwindcss/vite": "^4.1.5",
2126
"ansi-to-html": "^0.7.2",
27+
"class-variance-authority": "^0.7.1",
28+
"clsx": "^2.1.1",
2229
"color-hash": "^2.0.2",
2330
"d3-scale-chromatic": "^2.0.0",
2431
"dequal": "^2.0.2",
@@ -29,6 +36,7 @@
2936
"graphql": "16.6.0",
3037
"install": "^0.13.0",
3138
"line-column": "^1.0.2",
39+
"lucide-react": "^0.503.0",
3240
"markdown-it": "^13.0.1",
3341
"marked": "^4.0.10",
3442
"monaco-editor": "~0.40.0",
@@ -44,6 +52,8 @@
4452
"string-to-color": "^2.2.2",
4553
"string.prototype.replaceall": "^1.0.6",
4654
"styled-components": "^6.1.14",
55+
"tailwind-merge": "^3.2.0",
56+
"tailwindcss": "^4.1.5",
4757
"typeface-roboto-mono": "^1.1.13",
4858
"use-deep-compare": "^1.1.0",
4959
"use-deep-compare-effect": "^1.8.1",
@@ -61,7 +71,7 @@
6171
"@types/jest": "^29.5.0",
6272
"@types/line-column": "^1.0.0",
6373
"@types/markdown-it": "^12.2.3",
64-
"@types/node": "^20.3.3",
74+
"@types/node": "^22.15.3",
6575
"@types/parsimmon": "^1.10.6",
6676
"@types/react": "^18.3.1",
6777
"@types/react-copy-to-clipboard": "^5.0.4",
@@ -80,6 +90,7 @@
8090
"globals": "^15.14.0",
8191
"postcss-safe-parser": "7.0.0",
8292
"prettier": "3.4.2",
93+
"tw-animate-css": "^1.2.8",
8394
"typescript": "~5.7.3",
8495
"typescript-eslint": "^8.20.0",
8596
"vite": "^6.0.7",

src/App.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { FullPlayground } from "./components/FullPlayground";
1212
import { InlinePlayground } from "./components/InlinePlayground";
1313
import AppConfig from "./services/configservice";
1414
import { PLAYGROUND_UI_COLORS } from "./theme";
15+
import { ThemeProvider } from "@/components/ThemeProvider";
1516

1617
export interface AppProps {
1718
/**
@@ -64,16 +65,18 @@ function App(props: AppProps) {
6465

6566
const isEmbeddedPlayground = window.location.pathname.indexOf("/e/") >= 0;
6667
return (
67-
<PlaygroundUIThemed
68-
{...PLAYGROUND_UI_COLORS}
69-
forceDarkMode={isEmbeddedPlayground}
70-
>
71-
<AlertProvider>
72-
<ConfirmDialogProvider>
73-
<ThemedApp forTesting={props.forTesting} />
74-
</ConfirmDialogProvider>
75-
</AlertProvider>
76-
</PlaygroundUIThemed>
68+
<ThemeProvider>
69+
<PlaygroundUIThemed
70+
{...PLAYGROUND_UI_COLORS}
71+
forceDarkMode={isEmbeddedPlayground}
72+
>
73+
<AlertProvider>
74+
<ConfirmDialogProvider>
75+
<ThemedApp forTesting={props.forTesting} />
76+
</ConfirmDialogProvider>
77+
</AlertProvider>
78+
</PlaygroundUIThemed>
79+
</ThemeProvider>
7780
);
7881
}
7982

src/components/ExamplesDropdown.tsx

Lines changed: 72 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,35 @@
11
import { Example, LoadExamples } from "../spicedb-common/examples";
2-
import { faCaretDown } from "@fortawesome/free-solid-svg-icons";
3-
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4-
import { CircularProgress, MenuItem } from "@material-ui/core";
5-
import Button from "@material-ui/core/Button";
6-
import ListItemText from "@material-ui/core/ListItemText";
7-
import Menu from "@material-ui/core/Menu";
8-
import { Theme, createStyles, makeStyles } from "@material-ui/core/styles";
9-
import React, { useEffect, useState } from "react";
102

11-
const ITEM_HEIGHT = 68;
3+
import {
4+
Select,
5+
SelectContent,
6+
SelectItem,
7+
SelectTrigger,
8+
} from "@/components/ui/select";
129

13-
const useStyles = makeStyles((theme: Theme) =>
14-
createStyles({
15-
loading: {
16-
margin: theme.spacing(2),
17-
display: "flex",
18-
alignItems: "center",
19-
},
20-
}),
21-
);
10+
import {
11+
AlertDialog,
12+
AlertDialogAction,
13+
AlertDialogCancel,
14+
AlertDialogContent,
15+
AlertDialogDescription,
16+
AlertDialogFooter,
17+
AlertDialogHeader,
18+
AlertDialogTitle,
19+
} from "@/components/ui/alert-dialog";
2220

23-
export function ExamplesDropdown(props: {
24-
className?: string;
25-
disabled?: boolean;
26-
exampleSelected: (example: Example) => void;
27-
}) {
28-
const classes = useStyles();
29-
30-
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
31-
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
32-
setAnchorEl(event.currentTarget);
33-
};
34-
35-
const handleClose = () => {
36-
setAnchorEl(null);
37-
};
21+
import { useEffect, useState } from "react";
3822

39-
const [examples, setExamples] = useState<Example[] | undefined>(undefined);
23+
export function ExamplesDropdown({
24+
disabled,
25+
loadExample,
26+
}: {
27+
disabled: boolean;
28+
loadExample: (example: Example) => void;
29+
}) {
30+
const [examples, setExamples] = useState<Example[]>();
31+
const [promptOpen, setPromptOpen] = useState(false);
32+
const [selectedExample, setSelectedExample] = useState<Example>();
4033

4134
useEffect(() => {
4235
const fetchExamples = async () => {
@@ -47,64 +40,58 @@ export function ExamplesDropdown(props: {
4740
fetchExamples();
4841
}, [examples]);
4942

50-
const exampleSelected = (ex: Example) => {
51-
props.exampleSelected(ex);
52-
setAnchorEl(null);
53-
};
54-
5543
return (
5644
<>
57-
<Button
58-
size="small"
59-
disabled={props.disabled}
60-
className={props.className}
61-
onClick={handleClick}
62-
>
63-
Select Example Schema&nbsp;
64-
<FontAwesomeIcon icon={faCaretDown} />
65-
</Button>
66-
<Menu
67-
anchorEl={anchorEl}
68-
getContentAnchorEl={null}
69-
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
70-
transformOrigin={{ vertical: "top", horizontal: "left" }}
71-
keepMounted
72-
open={Boolean(anchorEl)}
73-
onClose={handleClose}
74-
PaperProps={{
75-
style: {
76-
maxHeight: ITEM_HEIGHT * 4.5,
77-
width: "50vw",
78-
maxWidth: "500px",
79-
},
45+
<Select
46+
onValueChange={(value) => {
47+
const example = examples?.find(({ id }) => id === value);
48+
if (example) {
49+
setSelectedExample(example);
50+
setPromptOpen(true);
51+
}
8052
}}
53+
disabled={disabled || !examples}
8154
>
82-
{examples === undefined && (
83-
<div className={classes.loading}>
84-
<CircularProgress />
85-
</div>
86-
)}
87-
{examples !== undefined &&
88-
examples.map((example) => {
55+
<SelectTrigger>
56+
{selectedExample ? selectedExample.title : "Select Example Schema"}
57+
</SelectTrigger>
58+
<SelectContent>
59+
{examples?.map((example) => {
8960
return (
90-
<MenuItem
91-
onClick={() => exampleSelected(example)}
92-
key={example.id}
93-
>
94-
<ListItemText
95-
primary={example.title}
96-
secondary={example.subtitle}
97-
secondaryTypographyProps={{
98-
style: {
99-
overflow: "hidden",
100-
textOverflow: "ellipsis",
101-
},
102-
}}
103-
/>
104-
</MenuItem>
61+
<SelectItem value={example.id} key={example.id}>
62+
{example.title}
63+
<br />
64+
{example.subtitle}
65+
</SelectItem>
10566
);
10667
})}
107-
</Menu>
68+
</SelectContent>
69+
</Select>
70+
<AlertDialog open={promptOpen} onOpenChange={setPromptOpen}>
71+
<AlertDialogContent>
72+
<AlertDialogHeader>
73+
<AlertDialogTitle>
74+
Replace contents with "{selectedExample?.title}"?
75+
</AlertDialogTitle>
76+
<AlertDialogDescription>
77+
This will replace all current Playground data with the example
78+
data for "{selectedExample?.title}"
79+
</AlertDialogDescription>
80+
</AlertDialogHeader>
81+
<AlertDialogFooter>
82+
<AlertDialogCancel>Cancel</AlertDialogCancel>
83+
<AlertDialogAction
84+
onClick={() => {
85+
if (selectedExample) {
86+
loadExample(selectedExample);
87+
}
88+
}}
89+
>
90+
Replace with Example
91+
</AlertDialogAction>
92+
</AlertDialogFooter>
93+
</AlertDialogContent>
94+
</AlertDialog>
10895
</>
10996
);
11097
}

src/components/FullPlayground.tsx

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { useAlert } from "../playground-ui/AlertProvider";
2-
import { useConfirmDialog } from "../playground-ui/ConfirmDialogProvider";
32
import { DiscordChatCrate } from "../playground-ui/DiscordChatCrate";
43
import { useGoogleAnalytics } from "../playground-ui/GoogleAnalyticsHook";
54
import TabLabel from "../playground-ui/TabLabel";
@@ -377,7 +376,6 @@ function ApolloedPlayground() {
377376
export function ThemedAppView(props: { datastore: DataStore }) {
378377
const { pushEvent } = useGoogleAnalytics();
379378
const { showAlert } = useAlert();
380-
const { showConfirm } = useConfirmDialog();
381379

382380
const [sharingState, setSharingState] = useState<SharingState>({
383381
status: SharingStatus.NOT_RUN,
@@ -556,25 +554,7 @@ export function ThemedAppView(props: { datastore: DataStore }) {
556554
}
557555
};
558556

559-
const askLoadExampleData = async (ex: Example) => {
560-
const [result] = await showConfirm({
561-
title: `Replace contents with "${ex.title}"?`,
562-
content: `This will replace all current Playground data with the example data for "${ex.title}"`,
563-
buttons: [
564-
{ title: "Cancel", value: "undefined" },
565-
{
566-
title: `Replace With Example`,
567-
variant: "contained",
568-
color: "primary",
569-
value: "load",
570-
},
571-
],
572-
});
573-
574-
if (result !== "load") {
575-
return;
576-
}
577-
557+
const loadExampleData = async (ex: Example) => {
578558
pushEvent("load-example", {
579559
id: ex.id,
580560
});
@@ -756,12 +736,11 @@ export function ThemedAppView(props: { datastore: DataStore }) {
756736
{!isOutOfDate && (
757737
<>
758738
<ExamplesDropdown
759-
className={TourElementClass.browse}
760739
disabled={
761740
sharingState.status === SharingStatus.SHARING ||
762741
validationState.status === ValidationStatus.RUNNING
763742
}
764-
exampleSelected={askLoadExampleData}
743+
loadExample={loadExampleData}
765744
/>
766745
<div>
767746
{sharingState.status === SharingStatus.SHARED && (

0 commit comments

Comments
 (0)