mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-06 05:07:36 -05:00
Form with all fields to /components
This commit is contained in:
parent
97ef317ae8
commit
b6d9c1cec3
157
app/features/components-showcase/form-examples-schema.ts
Normal file
157
app/features/components-showcase/form-examples-schema.ts
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import { z } from "zod";
|
||||
import {
|
||||
checkboxGroup,
|
||||
customField,
|
||||
datetimeOptional,
|
||||
datetimeRequired,
|
||||
dayMonthYearRequired,
|
||||
dualSelectOptional,
|
||||
numberFieldOptional,
|
||||
radioGroup,
|
||||
select,
|
||||
selectDynamicOptional,
|
||||
selectOptional,
|
||||
stageSelect,
|
||||
textAreaOptional,
|
||||
textAreaRequired,
|
||||
textFieldOptional,
|
||||
textFieldRequired,
|
||||
timeRangeOptional,
|
||||
toggle,
|
||||
userSearchOptional,
|
||||
weaponPool,
|
||||
weaponSelectOptional,
|
||||
} from "~/form/fields";
|
||||
|
||||
export const formFieldsShowcaseSchema = z.object({
|
||||
// Text fields
|
||||
requiredText: textFieldRequired({
|
||||
label: "labels.name",
|
||||
maxLength: 100,
|
||||
}),
|
||||
optionalText: textFieldOptional({
|
||||
label: "labels.bio",
|
||||
maxLength: 200,
|
||||
}),
|
||||
optionalNumber: numberFieldOptional({
|
||||
label: "labels.vodTeamSize",
|
||||
}),
|
||||
|
||||
// Text areas
|
||||
requiredTextArea: textAreaRequired({
|
||||
label: "labels.description",
|
||||
maxLength: 500,
|
||||
}),
|
||||
optionalTextArea: textAreaOptional({
|
||||
label: "labels.text",
|
||||
maxLength: 1000,
|
||||
}),
|
||||
|
||||
// Toggles
|
||||
isPublic: toggle({
|
||||
label: "labels.buildPrivate",
|
||||
}),
|
||||
enableNotifications: toggle({
|
||||
label: "labels.isEstablished",
|
||||
}),
|
||||
|
||||
// Selects
|
||||
requiredSelect: select({
|
||||
label: "labels.voiceChat",
|
||||
items: [
|
||||
{ label: "options.voiceChat.yes", value: "YES" },
|
||||
{ label: "options.voiceChat.no", value: "NO" },
|
||||
{ label: "options.voiceChat.listenOnly", value: "LISTEN_ONLY" },
|
||||
],
|
||||
}),
|
||||
optionalSelect: selectOptional({
|
||||
label: "labels.vodType",
|
||||
items: [
|
||||
{ label: "vodTypes.TOURNAMENT", value: "TOURNAMENT" },
|
||||
{ label: "vodTypes.CAST", value: "CAST" },
|
||||
{ label: "vodTypes.SCRIM", value: "SCRIM" },
|
||||
],
|
||||
}),
|
||||
dynamicSelect: selectDynamicOptional({
|
||||
label: "labels.orgSeries",
|
||||
}),
|
||||
divisionRange: dualSelectOptional({
|
||||
fields: [
|
||||
{
|
||||
label: "labels.scrimMaxDiv",
|
||||
items: [
|
||||
{ label: () => "S+", value: "S+" },
|
||||
{ label: () => "S", value: "S" },
|
||||
{ label: () => "A", value: "A" },
|
||||
{ label: () => "B", value: "B" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "labels.scrimMinDiv",
|
||||
items: [
|
||||
{ label: () => "S+", value: "S+" },
|
||||
{ label: () => "S", value: "S" },
|
||||
{ label: () => "A", value: "A" },
|
||||
{ label: () => "B", value: "B" },
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
// Radio & Checkbox groups
|
||||
matchType: radioGroup({
|
||||
label: "labels.scrimMaps",
|
||||
items: [
|
||||
{ label: "options.scrimMaps.noPreference", value: "NO_PREFERENCE" },
|
||||
{ label: "options.scrimMaps.szOnly", value: "SZ_ONLY" },
|
||||
{ label: "options.scrimMaps.rankedOnly", value: "RANKED_ONLY" },
|
||||
],
|
||||
}),
|
||||
selectedModes: checkboxGroup({
|
||||
label: "labels.buildModes",
|
||||
items: [
|
||||
{ label: "modes.SZ", value: "SZ" },
|
||||
{ label: "modes.TC", value: "TC" },
|
||||
{ label: "modes.RM", value: "RM" },
|
||||
{ label: "modes.CB", value: "CB" },
|
||||
],
|
||||
}),
|
||||
|
||||
// Date & Time
|
||||
requiredDatetime: datetimeRequired({
|
||||
label: "labels.startTime",
|
||||
}),
|
||||
optionalDatetime: datetimeOptional({
|
||||
label: "labels.vodDate",
|
||||
}),
|
||||
birthDate: dayMonthYearRequired({
|
||||
label: "labels.banUserExpiresAt",
|
||||
}),
|
||||
availableTime: timeRangeOptional({
|
||||
label: "labels.weekdayTimes",
|
||||
startLabel: "labels.start",
|
||||
endLabel: "labels.end",
|
||||
}),
|
||||
|
||||
// Game-specific fields
|
||||
weapons: weaponPool({
|
||||
label: "labels.weaponPool",
|
||||
maxCount: 5,
|
||||
minCount: 0,
|
||||
}),
|
||||
stage: stageSelect({
|
||||
label: "labels.vodStage",
|
||||
}),
|
||||
weapon: weaponSelectOptional({
|
||||
label: "labels.vodWeapon",
|
||||
}),
|
||||
user: userSearchOptional({
|
||||
label: "labels.banUserPlayer",
|
||||
}),
|
||||
|
||||
// Custom field
|
||||
customValue: customField(
|
||||
{ initialValue: "custom initial value" },
|
||||
z.string().optional(),
|
||||
),
|
||||
});
|
||||
|
|
@ -47,8 +47,11 @@ import { SubmitButton } from "~/components/SubmitButton";
|
|||
import { SubNav, SubNavLink } from "~/components/SubNav";
|
||||
import { Table } from "~/components/Table";
|
||||
import { WeaponSelect } from "~/components/WeaponSelect";
|
||||
import type { CustomFieldRenderProps } from "~/form/FormField";
|
||||
import { SendouForm } from "~/form/SendouForm";
|
||||
import type { MainWeaponId, StageId } from "~/modules/in-game-lists/types";
|
||||
import styles from "../components-showcase.module.css";
|
||||
import { formFieldsShowcaseSchema } from "../form-examples-schema";
|
||||
|
||||
export const SECTIONS = [
|
||||
{ title: "Buttons", id: "buttons", component: ButtonsSection },
|
||||
|
|
@ -90,6 +93,7 @@ export const SECTIONS = [
|
|||
{ title: "Placements", id: "placements", component: PlacementSection },
|
||||
{ title: "Badges", id: "badges", component: BadgeSection },
|
||||
{ title: "Game Selects", id: "game-selects", component: GameSelectSection },
|
||||
{ title: "Form Fields", id: "form-fields", component: FormFieldsSection },
|
||||
{ title: "Miscellaneous", id: "miscellaneous", component: MiscSection },
|
||||
] as const;
|
||||
|
||||
|
|
@ -2039,6 +2043,151 @@ function GameSelectSection({ id }: { id: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
const DYNAMIC_SELECT_OPTIONS = [
|
||||
{ value: "1", label: "Tournament A" },
|
||||
{ value: "2", label: "Tournament B" },
|
||||
{ value: "3", label: "Tournament C" },
|
||||
];
|
||||
|
||||
function FormFieldsSection({ id }: { id: string }) {
|
||||
return (
|
||||
<Section>
|
||||
<SectionTitle id={id}>Form Fields</SectionTitle>
|
||||
<p className="mb-4" style={{ fontSize: "var(--fonts-sm)", opacity: 0.8 }}>
|
||||
Schema-based form fields using SendouForm. Each field type is defined
|
||||
with Zod schemas that generate both UI and validation.
|
||||
</p>
|
||||
|
||||
<SendouForm schema={formFieldsShowcaseSchema} autoSubmit>
|
||||
{({ FormField }) => (
|
||||
<div className="stack lg">
|
||||
<Divider smallText>Text Fields</Divider>
|
||||
|
||||
<ComponentRow label="textFieldRequired">
|
||||
<FormField name="requiredText" />
|
||||
</ComponentRow>
|
||||
|
||||
<ComponentRow label="textFieldOptional">
|
||||
<FormField name="optionalText" />
|
||||
</ComponentRow>
|
||||
|
||||
<ComponentRow label="numberFieldOptional">
|
||||
<FormField name="optionalNumber" />
|
||||
</ComponentRow>
|
||||
|
||||
<Divider smallText>Text Areas</Divider>
|
||||
|
||||
<ComponentRow label="textAreaRequired">
|
||||
<FormField name="requiredTextArea" />
|
||||
</ComponentRow>
|
||||
|
||||
<ComponentRow label="textAreaOptional">
|
||||
<FormField name="optionalTextArea" />
|
||||
</ComponentRow>
|
||||
|
||||
<Divider smallText>Toggle (Switch)</Divider>
|
||||
|
||||
<ComponentRow label="toggle">
|
||||
<div className="stack sm">
|
||||
<FormField name="isPublic" />
|
||||
<FormField name="enableNotifications" />
|
||||
</div>
|
||||
</ComponentRow>
|
||||
|
||||
<Divider smallText>Select Fields</Divider>
|
||||
|
||||
<ComponentRow label="select (required)">
|
||||
<FormField name="requiredSelect" />
|
||||
</ComponentRow>
|
||||
|
||||
<ComponentRow label="selectOptional (clearable)">
|
||||
<FormField name="optionalSelect" />
|
||||
</ComponentRow>
|
||||
|
||||
<ComponentRow label="selectDynamicOptional">
|
||||
<FormField
|
||||
name="dynamicSelect"
|
||||
options={DYNAMIC_SELECT_OPTIONS}
|
||||
/>
|
||||
</ComponentRow>
|
||||
|
||||
<ComponentRow label="dualSelectOptional">
|
||||
<FormField name="divisionRange" />
|
||||
</ComponentRow>
|
||||
|
||||
<Divider smallText>Radio & Checkbox Groups</Divider>
|
||||
|
||||
<ComponentRow label="radioGroup">
|
||||
<FormField name="matchType" />
|
||||
</ComponentRow>
|
||||
|
||||
<ComponentRow label="checkboxGroup">
|
||||
<FormField name="selectedModes" />
|
||||
</ComponentRow>
|
||||
|
||||
<Divider smallText>Date & Time</Divider>
|
||||
|
||||
<ComponentRow label="datetimeRequired">
|
||||
<FormField name="requiredDatetime" />
|
||||
</ComponentRow>
|
||||
|
||||
<ComponentRow label="datetimeOptional">
|
||||
<FormField name="optionalDatetime" />
|
||||
</ComponentRow>
|
||||
|
||||
<ComponentRow label="dayMonthYearRequired">
|
||||
<FormField name="birthDate" />
|
||||
</ComponentRow>
|
||||
|
||||
<ComponentRow label="timeRangeOptional">
|
||||
<FormField name="availableTime" />
|
||||
</ComponentRow>
|
||||
|
||||
<Divider smallText>Game-specific Fields</Divider>
|
||||
|
||||
<ComponentRow label="weaponPool">
|
||||
<FormField name="weapons" />
|
||||
</ComponentRow>
|
||||
|
||||
<ComponentRow label="stageSelect">
|
||||
<FormField name="stage" />
|
||||
</ComponentRow>
|
||||
|
||||
<ComponentRow label="weaponSelectOptional">
|
||||
<FormField name="weapon" />
|
||||
</ComponentRow>
|
||||
|
||||
<ComponentRow label="userSearchOptional">
|
||||
<FormField name="user" />
|
||||
</ComponentRow>
|
||||
|
||||
<Divider smallText>Custom Field</Divider>
|
||||
|
||||
<ComponentRow label="customField">
|
||||
<FormField name="customValue">
|
||||
{(props: CustomFieldRenderProps) => (
|
||||
<div className="stack sm">
|
||||
<Label htmlFor="custom-input">Custom Field</Label>
|
||||
<Input
|
||||
id="custom-input"
|
||||
value={(props.value as string) ?? ""}
|
||||
onChange={(e) => props.onChange(e.target.value)}
|
||||
aria-invalid={Boolean(props.error)}
|
||||
/>
|
||||
{props.error ? (
|
||||
<FormMessage type="error">{props.error}</FormMessage>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</FormField>
|
||||
</ComponentRow>
|
||||
</div>
|
||||
)}
|
||||
</SendouForm>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
function MiscSection({ id }: { id: string }) {
|
||||
const [rangeValue, setRangeValue] = useState(50);
|
||||
const [colorValue, setColorValue] = useState("#3b82f6");
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user