From 3cdaf7b553f6f4826c17cf391d4b7ea7e01c96ed Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Wed, 2 Jul 2025 15:14:48 +0000 Subject: [PATCH 1/6] feat: add all form types to snippets --- src/client/Editor.tsx | 47 +++------- src/client/snippets.ts | 204 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 192 insertions(+), 59 deletions(-) diff --git a/src/client/Editor.tsx b/src/client/Editor.tsx index 87c5fc8..0b635cf 100644 --- a/src/client/Editor.tsx +++ b/src/client/Editor.tsx @@ -14,8 +14,7 @@ import { TooltipTrigger, } from "@/client/components/Tooltip"; import { useTheme } from "@/client/contexts/theme"; -import { multiSelect, radio, switchInput, textInput } from "@/client/snippets"; -import type { ParameterFormType } from "@/gen/types"; +import { snippets } from "@/client/snippets"; import { cn } from "@/utils/cn"; import { Editor as MonacoEditor } from "@monaco-editor/react"; import { @@ -23,11 +22,7 @@ import { ChevronDownIcon, CopyIcon, FileJsonIcon, - RadioIcon, SettingsIcon, - SquareMousePointerIcon, - TextCursorInputIcon, - ToggleLeftIcon, ZapIcon, } from "lucide-react"; import { type FC, useEffect, useRef, useState } from "react"; @@ -54,18 +49,6 @@ export const Editor: FC = ({ code, setCode }) => { setCodeCopied(() => true); }; - const onAddSnippet = (formType: ParameterFormType) => { - if (formType === "input") { - setCode(`${code.trimEnd()}\n\n${textInput}\n`); - } else if (formType === "radio") { - setCode(`${code.trimEnd()}\n\n${radio}\n`); - } else if (formType === "multi-select") { - setCode(`${code.trimEnd()}\n\n${multiSelect}\n`); - } else if (formType === "switch") { - setCode(`${code.trimEnd()}\n\n${switchInput}\n`); - } - }; - useEffect(() => { if (!codeCopied) { return; @@ -116,23 +99,17 @@ export const Editor: FC = ({ code, setCode }) => { - onAddSnippet("input")}> - - Text input - - onAddSnippet("multi-select")} - > - - Multi-select - - onAddSnippet("radio")}> - - Radio - - onAddSnippet("switch")}> - Switches - + {snippets.map(({ label, icon: Icon, snippet }, index) => ( + + setCode(`${code.trimEnd()}\n\n${snippet()}\n`) + } + > + + {label} + + ))} diff --git a/src/client/snippets.ts b/src/client/snippets.ts index 26b8fef..8ff879d 100644 --- a/src/client/snippets.ts +++ b/src/client/snippets.ts @@ -1,3 +1,14 @@ +import { + ChevronDownIcon, + LetterTextIcon, + RadioIcon, + Settings2Icon, + SquareMousePointerIcon, + TagIcon, + TextCursorInputIcon, + ToggleLeftIcon, +} from "lucide-react"; + export const defaultCode = `terraform { required_providers { coder = { @@ -7,25 +18,91 @@ export const defaultCode = `terraform { } }`; -export const textInput = `data "coder_parameter" "project-name" { - display_name = "An input" - name = "an-input" - description = "What is the name of your project?" - order = 4 +type SnippetFunc = (name?: string, order?: number) => string; +type Snippet = { + label: string; + icon: typeof RadioIcon; + snippet: SnippetFunc; +}; + +export const input: SnippetFunc = ( + name = "input", + order = 1, +) => `data "coder_parameter" "text-input" { + name = "${name}" + display_name = "A text input" + description = "This parameter can be used to input text." + order = ${order} form_type = "input" type = "string" default = "An input value" }`; -export const radio = `data "coder_parameter" "radio" { - name = "radio" - display_name = "An example of a radio input" - description = "The next parameter supports a single value." - type = "string" - form_type = "radio" - order = 1 - default = "option-1" +export const textarea: SnippetFunc = ( + name = "textarea", + order = 1, +) => `data "coder_parameter" "textarea" { + name = "${name}" + display_name = "A textarea input" + description = "This parameter can be used to input multiple lines of text" + order = ${order} + + form_type = "textarea" + type = "string" + default = "An input value" +}`; + +export const radio: SnippetFunc = ( + name = "radio", + order = 1, +) => `data "coder_parameter" "radio" { + name = "${name}" + display_name = "A radio input" + description = "This parameter supports selecting a single value out of a list of options" + order = ${order} + + type = "string" + form_type = "radio" + default = "option-1" + + option { + name = "Option 1" + value = "option-1" + description = "A description for Option 1" + } + + option { + name = "Option 2" + value = "option-2" + description = "A description for Option 2" + } + + option { + name = "Option 3" + value = "option-3" + description = "A description for Option 3" + } + + option { + name = "Option 4" + value = "option-4" + description = "A description for Option 4" + } +}`; + +export const dropdown: SnippetFunc = ( + name = "dropdown", + order = 1, +) => `data "coder_parameter" "dropdown" { + name = "${name}" + display_name = "A dropdown input" + description = "This parameter supports selecting a single value out of a list of options. Especially useful when you have a lot of options." + order = ${order} + + type = "string" + form_type = "dropdown" + default = "option-1" option { name = "Option 1" @@ -52,13 +129,16 @@ export const radio = `data "coder_parameter" "radio" { } }`; -export const multiSelect = `data "coder_parameter" "multi-select" { - name = "multi-select" - display_name = "An example of a multi-select" - description = "The next parameter supports multiple values." +export const multiSelect: SnippetFunc = ( + name = "multi-select", + order = 1, +) => `data "coder_parameter" "multi-select" { + name = "${name}" + display_name = "A multi-select input" + description = "This parameter supports selecting multiple values from a list of options" type = "list(string)" form_type = "multi-select" - order = 1 + order = ${order} option { name = "Option 1" @@ -85,15 +165,91 @@ export const multiSelect = `data "coder_parameter" "multi-select" { } }`; -export const switchInput = `data "coder_parameter" "switch" { - name = "switch" - display_name = "An example of a switch" - description = "The next parameter can be on or off" +export const tagSelect: SnippetFunc = ( + name = "tag-select", + order = 1, +) => `data "coder_parameter" "tag-select" { + name = "${name}" + display_name = "A tag-select input" + description = "This parameter supports selecting multiple user inputed values at once" + type = "list(string)" + form_type = "tag-select" + order = ${order} +}`; + +export const switchInput: SnippetFunc = ( + name = "switch", + order = 1, +) => `data "coder_parameter" "switch" { + name = "${name}" + display_name = "A switch input" + description = "This parameter can be toggled between true and false" type = "bool" form_type = "switch" default = true - order = 1 -}` + order = ${order} +}`; + +export const slider: SnippetFunc = ( + name = "slider", + order = 1, +) => `data "coder_parameter" "slider" { + name = "${name}" + display_name = "A slider input" + description = "This parameter supports selecting a number within a given range" + type = "number" + form_type = "slider" + default = 6 + order = ${order} + + validation { + min = 1 + max = 10 + } +}`; + +export const snippets: Snippet[] = [ + { + label: "Text Input", + icon: TextCursorInputIcon, + snippet: input, + }, + { + label: "Textarea", + icon: LetterTextIcon, + snippet: textarea, + }, + { + label: "Radio", + icon: RadioIcon, + snippet: radio, + }, + { + label: "Multi-select", + icon: SquareMousePointerIcon, + snippet: multiSelect, + }, + { + label: "Tag-select", + icon: TagIcon, + snippet: tagSelect, + }, + { + label: "Switch", + icon: ToggleLeftIcon, + snippet: switchInput, + }, + { + label: "Dropdown", + icon: ChevronDownIcon, + snippet: dropdown, + }, + { + label: "Slider", + icon: Settings2Icon, + snippet: slider, + }, +]; export const checkerModule = ` variable "solutions" { From a5145a098df62a19481b5604ec5dfe209600b0c3 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Wed, 2 Jul 2025 15:16:38 +0000 Subject: [PATCH 2/6] chore: add proper docs links --- src/client/App.tsx | 2 +- src/client/Preview.tsx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/App.tsx b/src/client/App.tsx index d440302..b0d9e10 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -181,7 +181,7 @@ export const App = () => { Coder {

Read the docs From 3d0195798013edf92c4a81cc7e2d3652b80d03a7 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Wed, 2 Jul 2025 15:41:28 +0000 Subject: [PATCH 3/6] feat: snippets are aware of existing order and names --- src/client/App.tsx | 2 +- src/client/Editor.tsx | 50 +++++++++++++++++++++++++++++------------- src/client/snippets.ts | 11 +++++++++- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/client/App.tsx b/src/client/App.tsx index b0d9e10..4c1392e 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -195,7 +195,7 @@ export const App = () => { {/* EDITOR */} - + diff --git a/src/client/Editor.tsx b/src/client/Editor.tsx index 0b635cf..9c71c18 100644 --- a/src/client/Editor.tsx +++ b/src/client/Editor.tsx @@ -14,7 +14,7 @@ import { TooltipTrigger, } from "@/client/components/Tooltip"; import { useTheme } from "@/client/contexts/theme"; -import { snippets } from "@/client/snippets"; +import { snippets, type SnippetFunc } from "@/client/snippets"; import { cn } from "@/utils/cn"; import { Editor as MonacoEditor } from "@monaco-editor/react"; import { @@ -27,28 +27,45 @@ import { } from "lucide-react"; import { type FC, useEffect, useRef, useState } from "react"; import { useEditor } from "@/client/contexts/editor"; +import type { ParameterWithSource } from "@/gen/types"; type EditorProps = { code: string; setCode: React.Dispatch>; + parameters: ParameterWithSource[]; }; -export const Editor: FC = ({ code, setCode }) => { +export const Editor: FC = ({ code, setCode, parameters }) => { const { appliedTheme } = useTheme(); const editorRef = useEditor(); + const [tab, setTab] = useState(() => "code"); + const [codeCopied, setCodeCopied] = useState(() => false); const copyTimeoutId = useRef | undefined>( undefined, ); - const [tab, setTab] = useState(() => "code"); - const onCopy = () => { navigator.clipboard.writeText(code); setCodeCopied(() => true); }; + const onAddSnippet = (name: string, snippet: SnippetFunc) => { + const nextInOrder = + parameters.reduce( + (highestOrder, parameter) => + highestOrder < parameter.order ? parameter.order : highestOrder, + 0, + ) + 1; + + const nameCount = parameters.filter((p) => p.name.startsWith(name)).length; + + setCode( + `${code.trimEnd()}\n\n${snippet(nameCount > 0 ? `${name}-${nameCount}` : name, nextInOrder)}\n`, + ); + }; + useEffect(() => { if (!codeCopied) { return; @@ -99,17 +116,20 @@ export const Editor: FC = ({ code, setCode }) => { - {snippets.map(({ label, icon: Icon, snippet }, index) => ( - - setCode(`${code.trimEnd()}\n\n${snippet()}\n`) - } - > - - {label} - - ))} + {snippets.map( + ({ name, label, icon: Icon, snippet }, index) => ( + + // setCode(`${code.trimEnd()}\n\n${snippet()}\n`) + onAddSnippet(name, snippet) + } + > + + {label} + + ), + )} diff --git a/src/client/snippets.ts b/src/client/snippets.ts index 8ff879d..55f46fc 100644 --- a/src/client/snippets.ts +++ b/src/client/snippets.ts @@ -18,8 +18,9 @@ export const defaultCode = `terraform { } }`; -type SnippetFunc = (name?: string, order?: number) => string; +export type SnippetFunc = (name?: string, order?: number) => string; type Snippet = { + name: string; label: string; icon: typeof RadioIcon; snippet: SnippetFunc; @@ -210,41 +211,49 @@ export const slider: SnippetFunc = ( export const snippets: Snippet[] = [ { + name: "text-input", label: "Text Input", icon: TextCursorInputIcon, snippet: input, }, { + name: "textarea", label: "Textarea", icon: LetterTextIcon, snippet: textarea, }, { + name: "radio", label: "Radio", icon: RadioIcon, snippet: radio, }, { + name: "switch", label: "Multi-select", icon: SquareMousePointerIcon, snippet: multiSelect, }, { + name: "tag-select", label: "Tag-select", icon: TagIcon, snippet: tagSelect, }, { + name: "switch", label: "Switch", icon: ToggleLeftIcon, snippet: switchInput, }, { + name: "dropdown", label: "Dropdown", icon: ChevronDownIcon, snippet: dropdown, }, { + name: "slider", label: "Slider", icon: Settings2Icon, snippet: slider, From 285fd2a7110e21465dcefc2c4f31412b4525cc8e Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Wed, 2 Jul 2025 15:43:07 +0000 Subject: [PATCH 4/6] chore: remove commented code --- src/client/Editor.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/client/Editor.tsx b/src/client/Editor.tsx index 9c71c18..b7068e5 100644 --- a/src/client/Editor.tsx +++ b/src/client/Editor.tsx @@ -120,10 +120,7 @@ export const Editor: FC = ({ code, setCode, parameters }) => { ({ name, label, icon: Icon, snippet }, index) => ( - // setCode(`${code.trimEnd()}\n\n${snippet()}\n`) - onAddSnippet(name, snippet) - } + onClick={() => onAddSnippet(name, snippet)} > {label} From aca5049a84a45c8d9cda6615a51e23f1ba3e7e23 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Wed, 2 Jul 2025 15:45:33 +0000 Subject: [PATCH 5/6] chore: use better icon for dropdown menu --- src/client/snippets.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/client/snippets.ts b/src/client/snippets.ts index 55f46fc..bbded15 100644 --- a/src/client/snippets.ts +++ b/src/client/snippets.ts @@ -1,7 +1,7 @@ import { - ChevronDownIcon, LetterTextIcon, RadioIcon, + Rows3Icon, Settings2Icon, SquareMousePointerIcon, TagIcon, @@ -20,7 +20,7 @@ export const defaultCode = `terraform { export type SnippetFunc = (name?: string, order?: number) => string; type Snippet = { - name: string; + name: string; label: string; icon: typeof RadioIcon; snippet: SnippetFunc; @@ -211,49 +211,49 @@ export const slider: SnippetFunc = ( export const snippets: Snippet[] = [ { - name: "text-input", + name: "text-input", label: "Text Input", icon: TextCursorInputIcon, snippet: input, }, { - name: "textarea", + name: "textarea", label: "Textarea", icon: LetterTextIcon, snippet: textarea, }, { - name: "radio", + name: "radio", label: "Radio", icon: RadioIcon, snippet: radio, }, { - name: "switch", + name: "switch", label: "Multi-select", icon: SquareMousePointerIcon, snippet: multiSelect, }, { - name: "tag-select", + name: "tag-select", label: "Tag-select", icon: TagIcon, snippet: tagSelect, }, { - name: "switch", + name: "switch", label: "Switch", icon: ToggleLeftIcon, snippet: switchInput, }, { - name: "dropdown", + name: "dropdown", label: "Dropdown", - icon: ChevronDownIcon, + icon: Rows3Icon, snippet: dropdown, }, { - name: "slider", + name: "slider", label: "Slider", icon: Settings2Icon, snippet: slider, From 684d476fd78cb7d607e3d5f729eaf89c56bfda50 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Wed, 2 Jul 2025 16:23:20 +0000 Subject: [PATCH 6/6] feat: add placeholder example to input snippets --- src/client/snippets.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/client/snippets.ts b/src/client/snippets.ts index bbded15..4c94dbe 100644 --- a/src/client/snippets.ts +++ b/src/client/snippets.ts @@ -35,6 +35,10 @@ export const input: SnippetFunc = ( description = "This parameter can be used to input text." order = ${order} + styling = jsonencode({ + placeholder = "A placeholder that will appear if the input value is empty" + }) + form_type = "input" type = "string" default = "An input value" @@ -49,6 +53,10 @@ export const textarea: SnippetFunc = ( description = "This parameter can be used to input multiple lines of text" order = ${order} + styling = jsonencode({ + placeholder = "A placeholder that will appear if the input value is empty" + }) + form_type = "textarea" type = "string" default = "An input value" @@ -101,6 +109,10 @@ export const dropdown: SnippetFunc = ( description = "This parameter supports selecting a single value out of a list of options. Especially useful when you have a lot of options." order = ${order} + styling = jsonencode({ + placeholder = "A placeholder that will appear if the input value is empty" + }) + type = "string" form_type = "dropdown" default = "option-1" @@ -137,9 +149,10 @@ export const multiSelect: SnippetFunc = ( name = "${name}" display_name = "A multi-select input" description = "This parameter supports selecting multiple values from a list of options" + order = ${order} + type = "list(string)" form_type = "multi-select" - order = ${order} option { name = "Option 1" @@ -173,9 +186,10 @@ export const tagSelect: SnippetFunc = ( name = "${name}" display_name = "A tag-select input" description = "This parameter supports selecting multiple user inputed values at once" + order = ${order} + type = "list(string)" form_type = "tag-select" - order = ${order} }`; export const switchInput: SnippetFunc = ( @@ -185,10 +199,11 @@ export const switchInput: SnippetFunc = ( name = "${name}" display_name = "A switch input" description = "This parameter can be toggled between true and false" + order = ${order} + type = "bool" form_type = "switch" default = true - order = ${order} }`; export const slider: SnippetFunc = (