@peerbots/core 0.1.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/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/publish.yml +42 -0
- package/.github/workflows/storybook.yml +46 -0
- package/.storybook/main.ts +28 -0
- package/.storybook/preview.ts +22 -0
- package/README.md +9 -0
- package/dist/index.css +1 -0
- package/dist/index.d.mts +704 -0
- package/dist/index.d.ts +704 -0
- package/dist/index.js +5 -0
- package/dist/index.mjs +5 -0
- package/package.json +60 -0
- package/src/charts/DistributionBarChart.stories.tsx +41 -0
- package/src/charts/DistributionBarChart.tsx +170 -0
- package/src/charts/DistributionHistogram.stories.tsx +56 -0
- package/src/charts/DistributionHistogram.tsx +193 -0
- package/src/charts/index.ts +10 -0
- package/src/global.d.ts +1 -0
- package/src/helpers/SEO.tsx +41 -0
- package/src/index.ts +6 -0
- package/src/styles/theme.css +60 -0
- package/src/ui/Alert.stories.tsx +41 -0
- package/src/ui/Alert.tsx +72 -0
- package/src/ui/Anchor.stories.tsx +25 -0
- package/src/ui/Anchor.tsx +32 -0
- package/src/ui/AuthFormUI.stories.tsx +67 -0
- package/src/ui/AuthFormUI.tsx +217 -0
- package/src/ui/BasePanel.stories.tsx +36 -0
- package/src/ui/BasePanel.tsx +59 -0
- package/src/ui/Button.stories.tsx +108 -0
- package/src/ui/Button.tsx +121 -0
- package/src/ui/Checkbox.stories.tsx +61 -0
- package/src/ui/Checkbox.tsx +45 -0
- package/src/ui/Collapsible.stories.tsx +91 -0
- package/src/ui/Collapsible.tsx +52 -0
- package/src/ui/Colors.stories.tsx +67 -0
- package/src/ui/Dialog.stories.tsx +29 -0
- package/src/ui/Dialog.tsx +56 -0
- package/src/ui/Dropdown.tsx +66 -0
- package/src/ui/Field.stories.tsx +181 -0
- package/src/ui/Field.tsx +108 -0
- package/src/ui/Icon.stories.tsx +192 -0
- package/src/ui/Icon.tsx +42 -0
- package/src/ui/IconRegistry.tsx +189 -0
- package/src/ui/Input.stories.tsx +67 -0
- package/src/ui/Input.tsx +43 -0
- package/src/ui/Label.stories.tsx +42 -0
- package/src/ui/Label.tsx +26 -0
- package/src/ui/NumberField.stories.tsx +86 -0
- package/src/ui/NumberField.tsx +116 -0
- package/src/ui/Popover.tsx +42 -0
- package/src/ui/Select.stories.tsx +74 -0
- package/src/ui/Select.tsx +122 -0
- package/src/ui/Separator.stories.tsx +61 -0
- package/src/ui/Separator.tsx +28 -0
- package/src/ui/SettingsPanel.stories.tsx +83 -0
- package/src/ui/SettingsPanel.tsx +81 -0
- package/src/ui/Skeleton.stories.tsx +43 -0
- package/src/ui/Skeleton.tsx +15 -0
- package/src/ui/Slider.stories.tsx +140 -0
- package/src/ui/Slider.tsx +95 -0
- package/src/ui/SliderWithNumberField.stories.tsx +101 -0
- package/src/ui/SliderWithNumberField.tsx +88 -0
- package/src/ui/Switch.stories.tsx +81 -0
- package/src/ui/Switch.tsx +60 -0
- package/src/ui/TabRadio.stories.tsx +153 -0
- package/src/ui/TabRadio.tsx +68 -0
- package/src/ui/TabSelection.stories.tsx +44 -0
- package/src/ui/TabSelection.tsx +91 -0
- package/src/ui/TextArea.stories.tsx +64 -0
- package/src/ui/TextArea.tsx +24 -0
- package/src/ui/Tooltip.stories.tsx +84 -0
- package/src/ui/Tooltip.tsx +61 -0
- package/src/ui/Typography.stories.tsx +87 -0
- package/src/ui/Typography.tsx +80 -0
- package/src/ui/index.ts +28 -0
- package/src/ui/utils.ts +6 -0
- package/tsconfig.json +12 -0
- package/vitest.config.ts +36 -0
- package/vitest.shims.d.ts +1 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VictoryChart,
|
|
3
|
+
VictoryAxis,
|
|
4
|
+
VictoryLabel,
|
|
5
|
+
VictoryLine,
|
|
6
|
+
VictoryHistogram,
|
|
7
|
+
VictoryTooltip,
|
|
8
|
+
VictoryVoronoiContainer,
|
|
9
|
+
} from "victory";
|
|
10
|
+
|
|
11
|
+
export interface DistributionHistogramData {
|
|
12
|
+
x: number;
|
|
13
|
+
text: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DistributionHistogramProps {
|
|
17
|
+
data: DistributionHistogramData[];
|
|
18
|
+
alt: string;
|
|
19
|
+
label: string;
|
|
20
|
+
average?: number;
|
|
21
|
+
referenceLineValue?: number;
|
|
22
|
+
referenceLineLabel?: string;
|
|
23
|
+
chartWidth?: number;
|
|
24
|
+
chartHeight?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function DistributionHistogram({
|
|
28
|
+
data,
|
|
29
|
+
alt,
|
|
30
|
+
label,
|
|
31
|
+
average,
|
|
32
|
+
referenceLineValue,
|
|
33
|
+
referenceLineLabel = "Reference",
|
|
34
|
+
chartWidth = 600,
|
|
35
|
+
chartHeight = 400,
|
|
36
|
+
}: DistributionHistogramProps) {
|
|
37
|
+
if (data.length === 0) return null;
|
|
38
|
+
|
|
39
|
+
const binSize = 10;
|
|
40
|
+
const maxLength = Math.max(
|
|
41
|
+
...data.map((r) => r.x),
|
|
42
|
+
referenceLineValue ?? 0,
|
|
43
|
+
binSize,
|
|
44
|
+
);
|
|
45
|
+
const maxX = Math.ceil(maxLength / binSize) * binSize + binSize;
|
|
46
|
+
|
|
47
|
+
const boundaries = [];
|
|
48
|
+
for (let i = 0; i <= maxX; i += binSize) {
|
|
49
|
+
boundaries.push(i);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const padding = { left: 60, right: 30, top: 20, bottom: 70 };
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className="mb-6" role="img" aria-label={alt}>
|
|
56
|
+
<label className="text-xs uppercase font-bold text-gray-400 block mb-2 px-1">
|
|
57
|
+
{label}
|
|
58
|
+
</label>
|
|
59
|
+
<VictoryChart
|
|
60
|
+
width={chartWidth}
|
|
61
|
+
height={chartHeight}
|
|
62
|
+
padding={padding}
|
|
63
|
+
containerComponent={<VictoryVoronoiContainer />}
|
|
64
|
+
>
|
|
65
|
+
<VictoryAxis
|
|
66
|
+
label="Length"
|
|
67
|
+
tickValues={boundaries}
|
|
68
|
+
tickFormat={(x: number | string) =>
|
|
69
|
+
typeof x === "number" ? Math.round(x) : x
|
|
70
|
+
}
|
|
71
|
+
style={{
|
|
72
|
+
axis: { stroke: "#cbd5e1" },
|
|
73
|
+
axisLabel: {
|
|
74
|
+
fontSize: 18,
|
|
75
|
+
padding: 45,
|
|
76
|
+
fill: "#334155",
|
|
77
|
+
fontWeight: "bold",
|
|
78
|
+
},
|
|
79
|
+
tickLabels: {
|
|
80
|
+
fontSize: 18,
|
|
81
|
+
padding: 5,
|
|
82
|
+
fill: "#334155",
|
|
83
|
+
angle: 45,
|
|
84
|
+
textAnchor: "start",
|
|
85
|
+
},
|
|
86
|
+
}}
|
|
87
|
+
/>
|
|
88
|
+
<VictoryAxis
|
|
89
|
+
dependentAxis
|
|
90
|
+
label="Count"
|
|
91
|
+
tickFormat={(x: number | string) =>
|
|
92
|
+
typeof x === "number" ? Math.round(x) : x
|
|
93
|
+
}
|
|
94
|
+
style={{
|
|
95
|
+
axis: { stroke: "#cbd5e1" },
|
|
96
|
+
axisLabel: {
|
|
97
|
+
fontSize: 18,
|
|
98
|
+
padding: 40,
|
|
99
|
+
fill: "#334155",
|
|
100
|
+
fontWeight: "bold",
|
|
101
|
+
},
|
|
102
|
+
tickLabels: {
|
|
103
|
+
fontSize: 18,
|
|
104
|
+
padding: 5,
|
|
105
|
+
fill: "#334155",
|
|
106
|
+
},
|
|
107
|
+
grid: { stroke: "#f1f5f9" },
|
|
108
|
+
}}
|
|
109
|
+
/>
|
|
110
|
+
<VictoryHistogram
|
|
111
|
+
data={data}
|
|
112
|
+
bins={boundaries}
|
|
113
|
+
x="x"
|
|
114
|
+
labels={({
|
|
115
|
+
datum,
|
|
116
|
+
}: {
|
|
117
|
+
datum?: {
|
|
118
|
+
y: number;
|
|
119
|
+
binnedData?: { text: string }[];
|
|
120
|
+
};
|
|
121
|
+
}) => {
|
|
122
|
+
if (!datum || datum.y === 0 || !datum.binnedData) return "";
|
|
123
|
+
const items = datum.binnedData.map((d: { text: string }) => d.text);
|
|
124
|
+
const displayItems = items.slice(0, 5);
|
|
125
|
+
let labelText = displayItems.join("\n");
|
|
126
|
+
if (items.length > 5)
|
|
127
|
+
labelText += `\n...and ${items.length - 5} more`;
|
|
128
|
+
return labelText;
|
|
129
|
+
}}
|
|
130
|
+
labelComponent={
|
|
131
|
+
<VictoryTooltip
|
|
132
|
+
style={{ fontSize: 18 }}
|
|
133
|
+
flyoutStyle={{ fill: "white", stroke: "#cbd5e1" }}
|
|
134
|
+
pointerLength={5}
|
|
135
|
+
cornerRadius={2}
|
|
136
|
+
constrainToVisibleArea
|
|
137
|
+
/>
|
|
138
|
+
}
|
|
139
|
+
style={{
|
|
140
|
+
data: {
|
|
141
|
+
fill: "#46d9d9",
|
|
142
|
+
stroke: "#fff",
|
|
143
|
+
strokeWidth: 1,
|
|
144
|
+
cursor: "pointer",
|
|
145
|
+
},
|
|
146
|
+
}}
|
|
147
|
+
/>
|
|
148
|
+
{average !== undefined && (
|
|
149
|
+
<VictoryLine
|
|
150
|
+
x={() => average}
|
|
151
|
+
style={{
|
|
152
|
+
data: {
|
|
153
|
+
stroke: "#9ca3af",
|
|
154
|
+
strokeWidth: 2,
|
|
155
|
+
strokeDasharray: "4, 4",
|
|
156
|
+
},
|
|
157
|
+
}}
|
|
158
|
+
labels={["Average"]}
|
|
159
|
+
labelComponent={
|
|
160
|
+
<VictoryLabel
|
|
161
|
+
y={45}
|
|
162
|
+
style={{ fill: "#475569", fontSize: 18, fontWeight: "bold" }}
|
|
163
|
+
backgroundStyle={{ fill: "white" }}
|
|
164
|
+
backgroundPadding={12}
|
|
165
|
+
/>
|
|
166
|
+
}
|
|
167
|
+
/>
|
|
168
|
+
)}
|
|
169
|
+
{referenceLineValue !== undefined && (
|
|
170
|
+
<VictoryLine
|
|
171
|
+
x={() => referenceLineValue}
|
|
172
|
+
labels={[referenceLineLabel]}
|
|
173
|
+
labelComponent={
|
|
174
|
+
<VictoryLabel
|
|
175
|
+
y={45}
|
|
176
|
+
style={{ fill: "#ef4444", fontSize: 18, fontWeight: "bold" }}
|
|
177
|
+
backgroundStyle={{ fill: "white" }}
|
|
178
|
+
backgroundPadding={12}
|
|
179
|
+
/>
|
|
180
|
+
}
|
|
181
|
+
style={{
|
|
182
|
+
data: {
|
|
183
|
+
stroke: "#ef4444",
|
|
184
|
+
strokeWidth: 2,
|
|
185
|
+
strokeDasharray: "4, 4",
|
|
186
|
+
},
|
|
187
|
+
}}
|
|
188
|
+
/>
|
|
189
|
+
)}
|
|
190
|
+
</VictoryChart>
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { DistributionBarChart } from "./DistributionBarChart";
|
|
2
|
+
export type {
|
|
3
|
+
DistributionBarChartProps,
|
|
4
|
+
DistributionBarData,
|
|
5
|
+
} from "./DistributionBarChart";
|
|
6
|
+
export { DistributionHistogram } from "./DistributionHistogram";
|
|
7
|
+
export type {
|
|
8
|
+
DistributionHistogramProps,
|
|
9
|
+
DistributionHistogramData,
|
|
10
|
+
} from "./DistributionHistogram";
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module "*.css" {}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
interface SEOProps {
|
|
2
|
+
title: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
image?: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
type?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function SEO({
|
|
10
|
+
title,
|
|
11
|
+
description = "Peerbots platform: social robots for everyone, powered by experts.",
|
|
12
|
+
image = "/peerbots-logo.png", // Default image if one exists, customizable
|
|
13
|
+
url,
|
|
14
|
+
type = "website",
|
|
15
|
+
}: SEOProps) {
|
|
16
|
+
const siteTitle = "Peerbots App";
|
|
17
|
+
const fullTitle = title === "Home" ? siteTitle : `${title} | ${siteTitle}`;
|
|
18
|
+
const currentUrl =
|
|
19
|
+
url || (typeof window !== "undefined" ? window.location.href : "");
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<title>{fullTitle}</title>
|
|
24
|
+
<meta name="description" content={description} />
|
|
25
|
+
|
|
26
|
+
{/* Open Graph */}
|
|
27
|
+
<meta property="og:title" content={fullTitle} />
|
|
28
|
+
<meta property="og:description" content={description} />
|
|
29
|
+
<meta property="og:image" content={image} />
|
|
30
|
+
<meta property="og:url" content={currentUrl} />
|
|
31
|
+
<meta property="og:type" content={type} />
|
|
32
|
+
<meta property="og:site_name" content={siteTitle} />
|
|
33
|
+
|
|
34
|
+
{/* Twitter Card */}
|
|
35
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
36
|
+
<meta name="twitter:title" content={fullTitle} />
|
|
37
|
+
<meta name="twitter:description" content={description} />
|
|
38
|
+
<meta name="twitter:image" content={image} />
|
|
39
|
+
</>
|
|
40
|
+
);
|
|
41
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
@theme {
|
|
4
|
+
--color-primary: #46d9d9;
|
|
5
|
+
/* --color-old-dark-primary: #5fc7cc; */
|
|
6
|
+
--color-dark-primary: var(--color-teal-700);
|
|
7
|
+
--color-secondary: #e86e8a;
|
|
8
|
+
--color-accent: #d9e021;
|
|
9
|
+
--color-accent-hc: #c4cc24;
|
|
10
|
+
--color-accent-two: #4273ff;
|
|
11
|
+
--color-accent-two-hc: #516eb5;
|
|
12
|
+
--color-accent-three: #3f8588;
|
|
13
|
+
--color-danger: #e86e8a;
|
|
14
|
+
--color-light-bg: #d8e7eb;
|
|
15
|
+
--sidebar-bg: #f9ffff;
|
|
16
|
+
|
|
17
|
+
--background-image-gradient-radial: radial-gradient(var(--tw-gradient-stops));
|
|
18
|
+
--background-image-gradient-conic: conic-gradient(from 180deg at 50% 50%,
|
|
19
|
+
var(--tw-gradient-stops));
|
|
20
|
+
|
|
21
|
+
--font-sans: "Avenir", "Nunito", sans-serif;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@utility input-base {
|
|
25
|
+
@apply bg-primary/10 border border-b-2 border-gray-300 focus:border-gray-600 py-2 px-2;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@utility btn-primary {
|
|
29
|
+
@apply bg-primary hover:bg-dark-primary text-gray-900 font-bold py-1 px-2 my-1 mx-2 rounded-md disabled:bg-gray-400 disabled:hover:bg-gray-300 disabled:cursor-not-allowed cursor-pointer;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@utility btn-secondary {
|
|
33
|
+
@apply bg-gray-100 hover:bg-gray-200 text-gray-800 font-normal py-1 px-2 my-1 mx-2 rounded-md disabled:bg-gray-400 disabled:hover:bg-gray-300 disabled:cursor-not-allowed cursor-pointer;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@utility btn-danger {
|
|
37
|
+
@apply bg-danger hover:opacity-80 text-gray-900 font-bold py-1 px-2 my-1 mx-2 rounded-md disabled:bg-gray-400 disabled:hover:bg-gray-300 disabled:cursor-not-allowed cursor-pointer;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@utility range-primary {
|
|
41
|
+
@apply accent-primary bg-gray-100 rounded-md cursor-pointer;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.range-vertical {
|
|
45
|
+
writing-mode: vertical-lr;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.tooltip,
|
|
49
|
+
.tooltip-slow {
|
|
50
|
+
@apply absolute invisible opacity-0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Using siblings so that hovering over the invisible tooltip doesn't trigger the tooltip */
|
|
54
|
+
.has-tooltip:hover+.tooltip {
|
|
55
|
+
@apply visible opacity-100 z-50 transition-opacity delay-300 ease-in-out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.has-tooltip:hover+.tooltip-slow {
|
|
59
|
+
@apply visible opacity-100 z-50 transition-opacity delay-1000 ease-in-out;
|
|
60
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Alert } from "./Alert";
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Alert> = {
|
|
6
|
+
title: "UI/Alert",
|
|
7
|
+
component: Alert,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: "centered",
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof Alert>;
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
args: {
|
|
19
|
+
level: "Info",
|
|
20
|
+
message: "This is a simple informational alert.",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const Variations: Story = {
|
|
25
|
+
render: () => (
|
|
26
|
+
<div className="flex flex-col gap-4 w-[600px] bg-gray-100 p-8">
|
|
27
|
+
<Alert
|
|
28
|
+
level="Error"
|
|
29
|
+
message="Critical connection failure!"
|
|
30
|
+
action={{ name: "Retry", callback: () => alert("Retrying...") }}
|
|
31
|
+
/>
|
|
32
|
+
<Alert level="Warning" message="Your session is about to expire." />
|
|
33
|
+
<Alert level="Success" message="Settings saved successfully." />
|
|
34
|
+
<Alert
|
|
35
|
+
level="Info"
|
|
36
|
+
message="New firmware update available."
|
|
37
|
+
action={{ name: "View", callback: () => alert("Viewing update...") }}
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
),
|
|
41
|
+
};
|
package/src/ui/Alert.tsx
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { Button, Icon } from ".";
|
|
3
|
+
|
|
4
|
+
export type AlertLevel = "Error" | "Warning" | "Success" | "Info";
|
|
5
|
+
|
|
6
|
+
export interface AlertAction {
|
|
7
|
+
name: string;
|
|
8
|
+
callback: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface AlertUIProps {
|
|
12
|
+
level: AlertLevel;
|
|
13
|
+
message: React.ReactNode;
|
|
14
|
+
action?: AlertAction;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function Alert({ level, message, action }: AlertUIProps) {
|
|
18
|
+
const [showAlert, setShowAlert] = useState(true);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
className={
|
|
23
|
+
"flex w-full p-4 m-1 text-sm rounded-lg bg-white border border-solid transition-opacity ease-in-out delay-150 duration-300 " +
|
|
24
|
+
(showAlert ? " " : " hidden")
|
|
25
|
+
}
|
|
26
|
+
>
|
|
27
|
+
<span>
|
|
28
|
+
{level === "Error" && (
|
|
29
|
+
<Icon
|
|
30
|
+
name="exclamationCircle"
|
|
31
|
+
size="lg"
|
|
32
|
+
className="m-2"
|
|
33
|
+
stroke="red"
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
36
|
+
{level === "Warning" && (
|
|
37
|
+
<Icon
|
|
38
|
+
name="exclamationTriangle"
|
|
39
|
+
size="lg"
|
|
40
|
+
className="m-2"
|
|
41
|
+
stroke="yellow"
|
|
42
|
+
/>
|
|
43
|
+
)}
|
|
44
|
+
{level === "Success" && (
|
|
45
|
+
<Icon name="checkCircle" size="lg" className="m-2" stroke="green" />
|
|
46
|
+
)}
|
|
47
|
+
{level === "Info" && (
|
|
48
|
+
<Icon name="megaphone" size="lg" className="m-2" />
|
|
49
|
+
)}
|
|
50
|
+
</span>
|
|
51
|
+
<span className="flex flex-1 ml-2">{message}</span>
|
|
52
|
+
{action && (
|
|
53
|
+
<Button
|
|
54
|
+
onClick={() => {
|
|
55
|
+
action.callback();
|
|
56
|
+
setShowAlert(false);
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
{action.name}
|
|
60
|
+
</Button>
|
|
61
|
+
)}
|
|
62
|
+
<span
|
|
63
|
+
className="cursor-pointer"
|
|
64
|
+
onClick={() => {
|
|
65
|
+
setShowAlert(false);
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<Icon name="xCircle" size="lg" className="m-1" />
|
|
69
|
+
</span>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Anchor } from "./Anchor";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "UI/Anchor",
|
|
6
|
+
component: Anchor,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: "centered",
|
|
9
|
+
},
|
|
10
|
+
tags: ["autodocs"],
|
|
11
|
+
argTypes: {
|
|
12
|
+
href: { control: "text" },
|
|
13
|
+
children: { control: "text" },
|
|
14
|
+
},
|
|
15
|
+
} satisfies Meta<typeof Anchor>;
|
|
16
|
+
|
|
17
|
+
export default meta;
|
|
18
|
+
type Story = StoryObj<typeof meta>;
|
|
19
|
+
|
|
20
|
+
export const Default: Story = {
|
|
21
|
+
args: {
|
|
22
|
+
href: "https://peerbots.org",
|
|
23
|
+
children: "Visit Peerbots",
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Link } from "react-router-dom";
|
|
3
|
+
import { cn } from "./utils";
|
|
4
|
+
|
|
5
|
+
export type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>;
|
|
6
|
+
|
|
7
|
+
const Anchor = React.forwardRef<HTMLAnchorElement, AnchorProps>(
|
|
8
|
+
({ className, href, target, ...props }, ref) => {
|
|
9
|
+
const isInternal = href && !href.startsWith("http") && !target;
|
|
10
|
+
const commonClass = cn(
|
|
11
|
+
"font-medium text-teal-700 underline underline-offset-4 hover:text-teal-900 cursor-pointer",
|
|
12
|
+
className,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
if (isInternal) {
|
|
16
|
+
return <Link to={href} ref={ref} className={commonClass} {...props} />;
|
|
17
|
+
} else {
|
|
18
|
+
return (
|
|
19
|
+
<a
|
|
20
|
+
href={href}
|
|
21
|
+
target={target}
|
|
22
|
+
ref={ref}
|
|
23
|
+
className={commonClass}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
Anchor.displayName = "Anchor";
|
|
31
|
+
|
|
32
|
+
export { Anchor };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { AuthFormUI } from "./AuthFormUI";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof AuthFormUI> = {
|
|
6
|
+
title: "UI/AuthFormUI",
|
|
7
|
+
component: AuthFormUI,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: "centered",
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof AuthFormUI>;
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
render: function Render() {
|
|
19
|
+
const [mode, setMode] = useState<
|
|
20
|
+
"signing up" | "signing in" | "resetting password"
|
|
21
|
+
>("signing in");
|
|
22
|
+
return (
|
|
23
|
+
<div className="w-[400px]">
|
|
24
|
+
<AuthFormUI
|
|
25
|
+
mode={mode}
|
|
26
|
+
onModeChange={setMode}
|
|
27
|
+
formAction={async () => {
|
|
28
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
29
|
+
}}
|
|
30
|
+
actionState={{ error: "", message: "" }}
|
|
31
|
+
onGoogleSignIn={() => alert("Google Sign In clicked")}
|
|
32
|
+
description="Sign in to simplify connecting to robots, and synchronize your work across devices."
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const Variations: Story = {
|
|
40
|
+
render: () => {
|
|
41
|
+
return (
|
|
42
|
+
<div className="flex flex-col gap-10 w-[400px]">
|
|
43
|
+
<AuthFormUI
|
|
44
|
+
mode="signing up"
|
|
45
|
+
onModeChange={() => {}}
|
|
46
|
+
formAction={async () => {}}
|
|
47
|
+
actionState={{ error: "", message: "" }}
|
|
48
|
+
onGoogleSignIn={() => {}}
|
|
49
|
+
description="Sign up description"
|
|
50
|
+
/>
|
|
51
|
+
<AuthFormUI
|
|
52
|
+
mode="resetting password"
|
|
53
|
+
onModeChange={() => {}}
|
|
54
|
+
formAction={async () => {}}
|
|
55
|
+
actionState={{ error: "", message: "Email sent!" }}
|
|
56
|
+
/>
|
|
57
|
+
<AuthFormUI
|
|
58
|
+
mode="signing in"
|
|
59
|
+
onModeChange={() => {}}
|
|
60
|
+
formAction={async () => {}}
|
|
61
|
+
actionState={{ error: "Invalid email or password", message: "" }}
|
|
62
|
+
description="Sign in description"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
};
|