Skip to content

Commit e3773e9

Browse files
authored
Merge pull request #748 from gadget-inc/feature/shadcn-auto-richtext
Feature/AutoRichTexts
2 parents 91ffa65 + 6199648 commit e3773e9

28 files changed

+631
-97
lines changed

packages/react/cypress/support/auto.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const ONLY_RUN_SUITES = {
2424
"AutoForm - Default model field values",
2525
"AutoForm - FindBy object parameters",
2626
"AutoForm - Global actions",
27-
// "AutoForm - HasManyThrough fields",
27+
"AutoForm - HasManyThrough fields",
2828
"AutoForm - Dynamic form input changes",
2929
"AutoForm - Dynamic form input changes - FindBy object parameters",
3030
"AutoForm - Dynamic form input changes - Global actions",

packages/react/spec/auto/polaris/inputs/PolarisJsonInput.stories.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { PolarisAutoForm } from "../../../../src/auto/polaris/PolarisAutoForm.ts
66
import { PolarisAutoJSONInput } from "../../../../src/auto/polaris/inputs/PolarisAutoJSONInput.tsx";
77
import { FormProvider, useForm } from "../../../../src/useActionForm.ts";
88
import { testApi as api } from "../../../apis.ts";
9+
910
const Component = (props) => {
1011
return (
1112
<PolarisAutoForm action={api.widget.create}>

packages/react/spec/auto/shadcn-defaults/components/Command.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ const CommandItem = React.forwardRef<
8989
/>
9090
));
9191

92+
const CommandLoading = React.forwardRef<
93+
React.ElementRef<typeof CommandPrimitive.Loading>,
94+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Loading>
95+
>((props, ref) => <CommandPrimitive.Loading ref={ref} className="py-6 text-center text-sm" {...props} />);
96+
97+
CommandLoading.displayName = CommandPrimitive.Loading.displayName;
98+
9299
CommandItem.displayName = CommandPrimitive.Item.displayName;
93100

94-
export { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator };
101+
export { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandLoading, CommandSeparator };

packages/react/spec/auto/shadcn-defaults/index.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@ import { Button } from "./components/Button.js";
66
import { Calendar } from "./components/Calendar.js";
77
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./components/Card.js";
88
import { Checkbox } from "./components/Checkbox.js";
9-
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from "./components/Command.js";
9+
import {
10+
Command,
11+
CommandEmpty,
12+
CommandGroup,
13+
CommandInput,
14+
CommandItem,
15+
CommandList,
16+
CommandLoading,
17+
CommandSeparator,
18+
} from "./components/Command.js";
1019
import { Form } from "./components/Form.js";
1120
import { Input } from "./components/Input.js";
1221
import { Label } from "./components/Label.js";
@@ -47,7 +56,7 @@ export const elements: ShadcnElements = {
4756
CommandItem,
4857
CommandList,
4958
CommandSeparator,
50-
59+
CommandLoading,
5160
Popover,
5261
PopoverAnchor,
5362
PopoverContent,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from "react";
2+
import { Provider } from "../../../../src/GadgetProvider.tsx";
3+
import { FormProvider, useForm } from "../../../../src/useActionForm.ts";
4+
import { testApi as api } from "../../../apis.ts";
5+
import { makeAutocomponents } from "../../../../src/auto/shadcn/index.ts";
6+
import { elements } from "../index";
7+
import { makeShadcnAutoJSONInput } from "../../../../src/auto/shadcn/inputs/ShadcnAutoJSONInput.tsx";
8+
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
9+
10+
const ShadcnAutoForm = makeAutocomponents(elements).AutoForm;
11+
const ShadcnAutoJSONInput = makeShadcnAutoJSONInput(elements);
12+
13+
const Component = (props) => {
14+
return (
15+
<ShadcnAutoForm action={api.widget.create}>
16+
<ShadcnAutoJSONInput {...props} />
17+
</ShadcnAutoForm>
18+
);
19+
};
20+
export default {
21+
title: "Shadcn/JsonInput",
22+
component: Component,
23+
decorators: [
24+
(Story, { parameters }) => {
25+
const { theme = "light" } = parameters;
26+
return (
27+
<Provider api={api}>
28+
<FormProvider {...useForm()}>
29+
<elements.Card className="p-6 w-full bg-white shadow-lg rounded-lg">
30+
<Story />
31+
</elements.Card>
32+
</FormProvider>
33+
</Provider>
34+
);
35+
},
36+
],
37+
38+
parameters: {
39+
layout: "centered",
40+
},
41+
tags: ["autodocs"],
42+
};
43+
44+
export const Primary = {
45+
args: {
46+
field: "metafields",
47+
},
48+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from "react";
2+
import { Provider } from "../../../../src/GadgetProvider.tsx";
3+
import { FormProvider, useForm } from "../../../../src/useActionForm.ts";
4+
import { testApi as api } from "../../../apis.ts";
5+
import { makeAutocomponents } from "../../../../src/auto/shadcn/index.ts";
6+
import { elements } from "../index";
7+
import { makeShadcnAutoFileInput } from "../../../../src/auto/shadcn/inputs/ShadcnAutoFileInput.tsx";
8+
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
9+
10+
const ShadcnAutoForm = makeAutocomponents(elements).AutoForm;
11+
const ShadcnAutoFileInput = makeShadcnAutoFileInput(elements);
12+
13+
const Component = (props) => {
14+
return (
15+
<ShadcnAutoForm action={api.widget.create}>
16+
<ShadcnAutoFileInput {...props} />
17+
</ShadcnAutoForm>
18+
);
19+
};
20+
21+
export default {
22+
title: "Shadcn/FileInput",
23+
component: Component,
24+
decorators: [
25+
(Story, { parameters }) => {
26+
const { theme = "light" } = parameters;
27+
return (
28+
<Provider api={api}>
29+
<FormProvider {...useForm()}>
30+
<elements.Card className="p-6 w-full bg-white shadow-lg rounded-lg">
31+
<Story />
32+
</elements.Card>
33+
</FormProvider>
34+
</Provider>
35+
);
36+
},
37+
],
38+
39+
parameters: {
40+
layout: "centered",
41+
},
42+
tags: ["autodocs"],
43+
};
44+
45+
export const Primary = {
46+
args: {
47+
field: "metafields",
48+
},
49+
};

packages/react/src/auto/polaris/inputs/PolarisAutoRichTextInput.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React from "react";
44
import { autoInput } from "../../AutoInput.js";
55
import { useStringInputController } from "../../hooks/useStringInputController.js";
66
import AutoRichTextInput from "../../shared/AutoRichTextInput.js";
7-
import "../styles/rich-text.css";
7+
import "../../shared/styles/rich-text.css";
88

99
/**
1010
* Prefer using the LazyLoadedMUIAutoRichTextInput.tsx variant of this component to reduce the bundle size by default.

packages/react/src/auto/shadcn/ShadcnAutoForm.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const makeAutoForm = <Elements extends ShadcnElements>({
3030
CommandInput,
3131
CommandItem,
3232
CommandList,
33+
CommandLoading,
3334
CommandEmpty,
3435
CommandGroup,
3536
Calendar,
@@ -61,6 +62,7 @@ export const makeAutoForm = <Elements extends ShadcnElements>({
6162
Checkbox,
6263
Badge,
6364
Command,
65+
CommandLoading,
6466
CommandInput,
6567
CommandItem,
6668
CommandList,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from "react";
2+
3+
export const ShadcnRequired = ({ children }: { children: React.ReactNode }) => {
4+
return <span className="text-red-500">{children}</span>;
5+
};

packages/react/src/auto/shadcn/elements.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ export interface ShadcnElements {
139139
/** The Calendar component from shadcn */
140140
Calendar: React.ComponentType<any>;
141141

142+
/** The CommandLoading component from shadcn */
143+
CommandLoading: React.ComponentType<React.HTMLAttributes<HTMLDivElement>>;
144+
142145
/** The CardHeader component from shadcn */
143146
CardHeader: React.ComponentType<CardHeaderProps>;
144147
/** The CardFooter component from shadcn */

packages/react/src/auto/shadcn/inputs/ShadcnAutoBooleanInput.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useController, useFormContext } from "../../../useActionForm.js";
44
import { get } from "../../../utils.js";
55
import { autoInput } from "../../AutoInput.js";
66
import { useFieldMetadata } from "../../hooks/useFieldMetadata.js";
7+
import { ShadcnRequired } from "../ShadcnRequired.js";
78
import type { CheckboxProps, ShadcnElements } from "../elements.js";
89

910
export const makeShadcnAutoBooleanInput = ({ Checkbox, Label }: Pick<ShadcnElements, "Checkbox" | "Label">) => {
@@ -56,7 +57,7 @@ export const makeShadcnAutoBooleanInput = ({ Checkbox, Label }: Pick<ShadcnEleme
5657
<Label htmlFor={path} className={`${props.className ?? ""} ${error ? "text-red-500" : ""}`}>
5758
{label}
5859
</Label>
59-
{error && <span className="text-sm text-red-500">{error.message}</span>}
60+
{error && <ShadcnRequired>{error.message}</ShadcnRequired>}
6061
</div>
6162
);
6263
}

packages/react/src/auto/shadcn/inputs/ShadcnAutoComboInput.tsx

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import React, { useRef, useState } from "react";
22
import type { FieldMetadata } from "../../../metadata.js";
3+
import { useClickOutside } from "../../../useClickOutside.js";
4+
import { useIntersectionObserver } from "../../../useIntersectionObserver.js";
35
import type { AutoRelationshipInputProps } from "../../interfaces/AutoRelationshipInputProps.js";
6+
import { ShadcnRequired } from "../ShadcnRequired.js";
47
import type { ShadcnElements } from "../elements.js";
58
import type { RelatedModelOptionsProps } from "./relationships/RelatedModelOption.js";
69
import { makeRelatedModelOption } from "./relationships/RelatedModelOption.js";
7-
810
interface ShadcnComboInputProps extends AutoRelationshipInputProps, RelatedModelOptionsProps {
911
selectedRecordTag: React.JSX.Element | null;
1012
path: string;
@@ -16,6 +18,9 @@ interface ShadcnComboInputProps extends AutoRelationshipInputProps, RelatedModel
1618
formatOptionText?: (option: string) => React.ReactNode;
1719
emptyMessage?: string;
1820
defaultValue?: string;
21+
onScrolledToBottom?: () => void;
22+
willLoadMoreOptions?: boolean;
23+
onChange?: (value: string) => void;
1924
}
2025

2126
export const makeShadcnAutoComboInput = ({
@@ -24,12 +29,22 @@ export const makeShadcnAutoComboInput = ({
2429
Label,
2530
CommandItem,
2631
CommandList,
32+
CommandLoading,
2733
CommandEmpty,
2834
CommandGroup,
2935
Checkbox,
3036
}: Pick<
3137
ShadcnElements,
32-
"Command" | "CommandInput" | "Label" | "CommandItem" | "CommandList" | "CommandEmpty" | "CommandGroup" | "Checkbox" | "CommandGroup"
38+
| "Command"
39+
| "CommandInput"
40+
| "Label"
41+
| "CommandItem"
42+
| "CommandList"
43+
| "CommandEmpty"
44+
| "CommandGroup"
45+
| "Checkbox"
46+
| "ScrollArea"
47+
| "CommandLoading"
3348
>) => {
3449
const RelatedModelOption = makeRelatedModelOption({
3550
CommandItem,
@@ -38,19 +53,34 @@ export const makeShadcnAutoComboInput = ({
3853
CommandGroup,
3954
Checkbox,
4055
Label,
56+
CommandLoading,
4157
});
4258

4359
function ShadcnAutoComboInput(props: ShadcnComboInputProps) {
4460
const inputRef = useRef<HTMLInputElement>(null);
61+
const outsideBoxRef = useRef<HTMLDivElement>(null);
62+
const sentinelRef = useIntersectionObserver(
63+
() => {
64+
props.onScrolledToBottom?.();
65+
},
66+
outsideBoxRef,
67+
{ threshold: 1.0 }
68+
);
4569
const [open, setOpen] = useState(false);
4670
const [inputValue, setInputValue] = useState(props.defaultValue || "");
4771
const id = props.id || `${props.path}-input`;
4872
const inputLabel = props.label || props.metadata.name;
4973

50-
const requiredIndicator = props.metadata.requiredArgumentForInput ? <span className="text-red-500">*</span> : null;
74+
const requiredIndicator = props.metadata.requiredArgumentForInput ? <ShadcnRequired>*</ShadcnRequired> : null;
75+
76+
useClickOutside(outsideBoxRef, () => {
77+
if (open) {
78+
setOpen(false);
79+
}
80+
});
5181

5282
return (
53-
<div>
83+
<div ref={outsideBoxRef}>
5484
<Label htmlFor={id}>
5585
{inputLabel} {requiredIndicator}
5686
</Label>
@@ -62,34 +92,35 @@ export const makeShadcnAutoComboInput = ({
6292
ref={inputRef}
6393
data-testid={id}
6494
value={inputValue}
65-
onValueChange={setInputValue}
66-
onBlur={() => setOpen(false)}
95+
onValueChange={(value: string) => {
96+
setInputValue(value);
97+
props.onChange?.(value);
98+
}}
6799
onFocus={() => setOpen(true)}
68100
placeholder={"Search"}
69101
className="ml-2 bg-transparent outline-none placeholder:text-muted-foreground flex-1"
70102
/>
71-
<div className="relative">
72-
{open && props.options.length > 0 ? (
73-
<div className="">
74-
<RelatedModelOption
75-
onAddExtraOption={props.onAddExtraOption}
76-
isLoading={props.isLoading}
77-
errorMessage={props.errorMessage}
78-
options={props.options}
79-
records={props.records}
80-
onSelect={props.onSelect}
81-
checkSelected={props.checkSelected}
82-
allowMultiple={props.allowMultiple}
83-
renderOption={props.renderOption}
84-
allowOther={props.allowOther}
85-
searchValue={inputValue}
86-
setSearchValue={setInputValue}
87-
formatOptionText={props.formatOptionText}
88-
emptyMessage={props.emptyMessage ? `${props.emptyMessage} "${inputValue}"` : ""}
89-
/>
90-
</div>
91-
) : null}
92-
</div>
103+
{open && (
104+
<>
105+
<RelatedModelOption
106+
onAddExtraOption={props.onAddExtraOption}
107+
isLoading={props.isLoading}
108+
errorMessage={props.errorMessage}
109+
options={props.options}
110+
records={props.records}
111+
onSelect={props.onSelect}
112+
checkSelected={props.checkSelected}
113+
allowMultiple={props.allowMultiple}
114+
renderOption={props.renderOption}
115+
allowOther={props.allowOther}
116+
searchValue={inputValue}
117+
setSearchValue={setInputValue}
118+
formatOptionText={props.formatOptionText}
119+
emptyMessage={props.emptyMessage ? `${props.emptyMessage} "${inputValue}"` : undefined}
120+
loadMoreRef={props.willLoadMoreOptions ? sentinelRef : undefined}
121+
/>
122+
</>
123+
)}
93124
</Command>
94125
</div>
95126
</div>

packages/react/src/auto/shadcn/inputs/ShadcnAutoDateTimePicker.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { GadgetDateTimeConfig } from "../../../internal/gql/graphql.js";
1313
import { useController } from "../../../useActionForm.js";
1414
import { autoInput } from "../../AutoInput.js";
1515
import { useFieldMetadata } from "../../hooks/useFieldMetadata.js";
16+
import { ShadcnRequired } from "../ShadcnRequired.js";
1617
import type { ShadcnElements } from "../elements.js";
1718

1819
export interface Range {
@@ -137,7 +138,7 @@ export const makeShadcnAutoDateTimePicker = ({
137138
<div>
138139
<Label htmlFor={props.id ? `${props.id}-date` : undefined}>
139140
{props.label ?? metadata.name ?? "Date"}
140-
{metadata.requiredArgumentForInput && <span className="text-red-500 ml-1">*</span>}
141+
{metadata.requiredArgumentForInput && <ShadcnRequired>*</ShadcnRequired>}
141142
</Label>
142143
<Button
143144
id={props.id ? `${props.id}-date` : undefined}

0 commit comments

Comments
 (0)