Skip to content

Commit dd4dba7

Browse files
committed
ui: add TypeForm first version
ui: add DarkModeButton
1 parent 5a17fc9 commit dd4dba7

File tree

14 files changed

+565
-9
lines changed

14 files changed

+565
-9
lines changed
Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,38 @@
1+
import { t } from "@alepha/core";
2+
import { useForm } from "@alepha/react-form";
3+
import { DarkModeButton, Flex, TypeForm, useToast } from "@alepha/ui";
4+
15
const Home = () => {
2-
return <div>Welcome to the Playground Home Page</div>;
6+
const toast = useToast();
7+
const form = useForm({
8+
schema: t.partial(
9+
t.object({
10+
firstName: t.text(),
11+
lastName: t.text(),
12+
email: t.email(),
13+
password: t.text(),
14+
birthday: t.date(),
15+
}),
16+
),
17+
handler: (values, args) => {
18+
console.log("Form submitted with values:", values);
19+
toast.success({
20+
title: "Form Submitted",
21+
message: `Hello, ${values.firstName} ${values.lastName}!`,
22+
});
23+
},
24+
});
25+
26+
return (
27+
<Flex p={"lg"} direction={"column"}>
28+
<Flex>
29+
<DarkModeButton mode={"segmented"} />
30+
</Flex>
31+
<Flex p={"lg"}>
32+
<TypeForm form={form} />
33+
</Flex>
34+
</Flex>
35+
);
336
};
437

538
export default Home;

packages/react-form/src/services/FormModel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export class FormModel<T extends TObject> {
1818

1919
constructor(
2020
public readonly id: string,
21-
protected readonly options: FormCtrlOptions<T>,
21+
public readonly options: FormCtrlOptions<T>,
2222
) {
2323
this.options = options;
2424

packages/ui/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@
2323
"@alepha/react": "0.10.6",
2424
"@alepha/react-form": "0.10.6",
2525
"@mantine/core": "^8.3.5",
26+
"@mantine/dates": "^8.3.5",
2627
"@mantine/hooks": "^8.3.5",
2728
"@mantine/modals": "^8.3.5",
2829
"@mantine/notifications": "^8.3.5",
2930
"@mantine/nprogress": "^8.3.5",
30-
"@mantine/spotlight": "^8.3.5"
31+
"@mantine/spotlight": "^8.3.5",
32+
"@tabler/icons-react": "^3.35.0"
3133
},
3234
"devDependencies": {
3335
"@biomejs/biome": "^2.2.6",

packages/ui/src/components/Action.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ const ActionSubmit = (props: ActionSubmitProps) => {
103103
<Button
104104
{...buttonProps}
105105
loading={state.loading}
106-
disabled={state.loading || !state.dirty}
106+
disabled={state.loading}
107107
type={"submit"}
108108
>
109109
{props.children}

packages/ui/src/components/AlephaMantineProvider.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Notifications, type NotificationsProps } from "@mantine/notifications";
99
import type { NavigationProgressProps } from "@mantine/nprogress";
1010
import { NavigationProgress, nprogress } from "@mantine/nprogress";
1111
import type { ReactNode } from "react";
12+
import Omnibar, { type OmnibarProps } from "./Omnibar";
1213

1314
export interface AlephaMantineProviderProps {
1415
children?: ReactNode;
@@ -17,6 +18,7 @@ export interface AlephaMantineProviderProps {
1718
navigationProgress?: NavigationProgressProps;
1819
notifications?: NotificationsProps;
1920
modals?: ModalsProviderProps;
21+
omnibar?: OmnibarProps;
2022
}
2123

2224
const AlephaMantineProvider = (props: AlephaMantineProviderProps) => {
@@ -39,6 +41,7 @@ const AlephaMantineProvider = (props: AlephaMantineProviderProps) => {
3941
<Notifications {...props.notifications} />
4042
<NavigationProgress {...props.navigationProgress} />
4143
<ModalsProvider {...props.modals}>
44+
<Omnibar {...props.omnibar} />
4245
{props.children ?? <NestedView />}
4346
</ModalsProvider>
4447
</MantineProvider>

packages/ui/src/components/Control.tsx

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ import {
1818
TextInput,
1919
type TextInputProps,
2020
} from "@mantine/core";
21+
import {
22+
DateInput,
23+
type DateInputProps,
24+
DateTimePicker,
25+
type DateTimePickerProps,
26+
TimeInput,
27+
type TimeInputProps,
28+
} from "@mantine/dates";
2129
import type { ComponentType, ReactNode } from "react";
2230

2331
export interface ControlProps {
@@ -35,6 +43,9 @@ export interface ControlProps {
3543
password?: boolean | PasswordInputProps;
3644
switch?: boolean | SwitchProps;
3745
segmented?: boolean | Partial<SegmentedControlProps>;
46+
date?: boolean | DateInputProps;
47+
datetime?: boolean | DateTimePickerProps;
48+
time?: boolean | TimeInputProps;
3849

3950
custom?: ComponentType<CustomControlProps>;
4051
}
@@ -50,6 +61,9 @@ export interface ControlProps {
5061
* - PasswordInput
5162
* - Switch (for boolean types)
5263
* - SegmentedControl (for enum types)
64+
* - DateInput (for date format)
65+
* - DateTimePicker (for date-time format)
66+
* - TimeInput (for time format)
5367
* - Custom component
5468
*
5569
* Automatically handles labels, descriptions, error messages, and required state.
@@ -215,7 +229,7 @@ const Control = (props: ControlProps) => {
215229
// endregion
216230

217231
// region <PasswordInput/>
218-
if (props.password) {
232+
if (props.password || props.input.props.name?.includes("password")) {
219233
const passwordInputProps =
220234
typeof props.password === "object" ? props.password : {};
221235
return (
@@ -245,6 +259,92 @@ const Control = (props: ControlProps) => {
245259
}
246260
//endregion
247261

262+
// region <DateTimePicker/>
263+
if (
264+
props.datetime ||
265+
(props.input.schema &&
266+
"type" in props.input.schema &&
267+
props.input.schema.type === "string" &&
268+
"format" in props.input.schema &&
269+
props.input.schema.format === "date-time")
270+
) {
271+
const dateTimePickerProps =
272+
typeof props.datetime === "object" ? props.datetime : {};
273+
return (
274+
<DateTimePicker
275+
{...inputProps}
276+
id={id}
277+
leftSection={icon}
278+
defaultValue={
279+
props.input.props.defaultValue
280+
? new Date(props.input.props.defaultValue)
281+
: undefined
282+
}
283+
onChange={(value) => {
284+
props.input.set(value ? new Date(value).toISOString() : undefined);
285+
}}
286+
{...dateTimePickerProps}
287+
/>
288+
);
289+
}
290+
//endregion
291+
292+
// region <DateInput/>
293+
if (
294+
props.date ||
295+
(props.input.schema &&
296+
"type" in props.input.schema &&
297+
props.input.schema.type === "string" &&
298+
"format" in props.input.schema &&
299+
props.input.schema.format === "date")
300+
) {
301+
const dateInputProps = typeof props.date === "object" ? props.date : {};
302+
return (
303+
<DateInput
304+
{...inputProps}
305+
id={id}
306+
leftSection={icon}
307+
defaultValue={
308+
props.input.props.defaultValue
309+
? new Date(props.input.props.defaultValue)
310+
: undefined
311+
}
312+
onChange={(value) => {
313+
props.input.set(
314+
value ? new Date(value).toISOString().slice(0, 10) : undefined,
315+
);
316+
}}
317+
{...dateInputProps}
318+
/>
319+
);
320+
}
321+
//endregion
322+
323+
// region <TimeInput/>
324+
if (
325+
props.time ||
326+
(props.input.schema &&
327+
"type" in props.input.schema &&
328+
props.input.schema.type === "string" &&
329+
"format" in props.input.schema &&
330+
props.input.schema.format === "time")
331+
) {
332+
const timeInputProps = typeof props.time === "object" ? props.time : {};
333+
return (
334+
<TimeInput
335+
{...inputProps}
336+
id={id}
337+
leftSection={icon}
338+
defaultValue={props.input.props.defaultValue}
339+
onChange={(event) => {
340+
props.input.set(event.currentTarget.value);
341+
}}
342+
{...timeInputProps}
343+
/>
344+
);
345+
}
346+
//endregion
347+
248348
// region <TextInput/>
249349
const textInputProps = typeof props.text === "object" ? props.text : {};
250350
return (
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {
2+
ActionIcon,
3+
Flex,
4+
SegmentedControl,
5+
useComputedColorScheme,
6+
useMantineColorScheme,
7+
} from "@mantine/core";
8+
import { IconMoon, IconSun } from "@tabler/icons-react";
9+
10+
export interface DarkModeButtonProps {
11+
mode?: "minimal" | "segmented";
12+
size?: string | number;
13+
variant?:
14+
| "filled"
15+
| "light"
16+
| "outline"
17+
| "default"
18+
| "subtle"
19+
| "transparent";
20+
}
21+
22+
const DarkModeButton = (props: DarkModeButtonProps) => {
23+
const { setColorScheme } = useMantineColorScheme();
24+
const computedColorScheme = useComputedColorScheme("light");
25+
const mode = props.mode ?? "minimal";
26+
27+
const toggleColorScheme = () => {
28+
setColorScheme(computedColorScheme === "dark" ? "light" : "dark");
29+
};
30+
31+
if (mode === "segmented") {
32+
return (
33+
<SegmentedControl
34+
value={computedColorScheme}
35+
onChange={(value) => setColorScheme(value as "light" | "dark")}
36+
data={[
37+
{
38+
value: "light",
39+
label: (
40+
<Flex h={20} align="center" justify="center">
41+
<IconSun size={16} />
42+
</Flex>
43+
),
44+
},
45+
{
46+
value: "dark",
47+
label: (
48+
<Flex h={20} align="center" justify="center">
49+
<IconMoon size={16} />
50+
</Flex>
51+
),
52+
},
53+
]}
54+
/>
55+
);
56+
}
57+
58+
return (
59+
<ActionIcon
60+
onClick={toggleColorScheme}
61+
variant={props.variant ?? "default"}
62+
size={props.size ?? "lg"}
63+
aria-label="Toggle color scheme"
64+
>
65+
{computedColorScheme === "dark" ? (
66+
<IconSun size={20} />
67+
) : (
68+
<IconMoon size={20} />
69+
)}
70+
</ActionIcon>
71+
);
72+
};
73+
74+
export default DarkModeButton;
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { Spotlight, type SpotlightActionData } from "@mantine/spotlight";
2+
import {
3+
IconDashboard,
4+
IconFileText,
5+
IconHome,
6+
IconSearch,
7+
IconSettings,
8+
IconUser,
9+
} from "@tabler/icons-react";
10+
import type { ReactNode } from "react";
11+
12+
export interface OmnibarProps {
13+
actions?: SpotlightActionData[];
14+
shortcut?: string | string[];
15+
searchPlaceholder?: string;
16+
nothingFound?: ReactNode;
17+
}
18+
19+
const defaultActions: SpotlightActionData[] = [
20+
{
21+
id: "home",
22+
label: "Home",
23+
description: "Go to home page",
24+
onClick: () => console.log("Home"),
25+
leftSection: <IconHome size={20} />,
26+
},
27+
{
28+
id: "dashboard",
29+
label: "Dashboard",
30+
description: "View your dashboard",
31+
onClick: () => console.log("Dashboard"),
32+
leftSection: <IconDashboard size={20} />,
33+
},
34+
{
35+
id: "documents",
36+
label: "Documents",
37+
description: "Browse all documents",
38+
onClick: () => console.log("Documents"),
39+
leftSection: <IconFileText size={20} />,
40+
},
41+
{
42+
id: "profile",
43+
label: "Profile",
44+
description: "View and edit your profile",
45+
onClick: () => console.log("Profile"),
46+
leftSection: <IconUser size={20} />,
47+
},
48+
{
49+
id: "settings",
50+
label: "Settings",
51+
description: "Manage application settings",
52+
onClick: () => console.log("Settings"),
53+
leftSection: <IconSettings size={20} />,
54+
},
55+
];
56+
57+
const Omnibar = (props: OmnibarProps) => {
58+
const actions = props.actions ?? defaultActions;
59+
const shortcut = props.shortcut ?? "mod+K";
60+
const searchPlaceholder = props.searchPlaceholder ?? "Search...";
61+
const nothingFound = props.nothingFound ?? "Nothing found...";
62+
63+
return (
64+
<Spotlight
65+
actions={actions}
66+
shortcut={shortcut}
67+
searchProps={{
68+
leftSection: <IconSearch size={20} />,
69+
placeholder: searchPlaceholder,
70+
}}
71+
nothingFound={nothingFound}
72+
/>
73+
);
74+
};
75+
76+
export default Omnibar;

0 commit comments

Comments
 (0)