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 = (