(
type={type}
{...props}
className={clsx(
- "form-input block w-full rounded-md border-slate-300 text-sm shadow-sm placeholder:text-slate-300 focus:border-indigo-500 focus:ring-indigo-500 active:border-indigo-500 active:ring-indigo-500 disabled:text-slate-400",
+ "form-input block w-full rounded-sm border-gray-300 text-sm shadow-sm placeholder:text-gray-300 focus:border-blue-500 focus:ring-blue-500 active:border-blue-500 active:ring-blue-500 disabled:text-gray-400",
size === "normal" ? "h-10" : "h-8"
)}
/>
diff --git a/packages/ui/src/forms/styled/StyledRadio.tsx b/packages/ui/src/forms/styled/StyledRadio.tsx
index 6dbc9add2e..b0492b45cd 100644
--- a/packages/ui/src/forms/styled/StyledRadio.tsx
+++ b/packages/ui/src/forms/styled/StyledRadio.tsx
@@ -28,7 +28,7 @@ export function StyledRadio({ value, options, onChange }: StyledRadioProps) {
onChange={() => onChange(option.id)}
checked={option.id === value}
className={clsx(
- "form-radio text-indigo-500 focus:ring-transparent",
+ "form-radio text-blue-500 focus:ring-transparent",
option.disabled ? "cursor-not-allowed" : "cursor-pointer"
)}
disabled={option.disabled}
@@ -40,7 +40,7 @@ export function StyledRadio({ value, options, onChange }: StyledRadioProps) {
"text-sm font-medium",
option.disabled
? "cursor-not-allowed text-gray-400"
- : "cursor-pointer text-gray-600 group-hover:text-gray-900"
+ : "cursor-pointer text-gray-600 group-hover:text-gray-800"
)}
>
{option.name}
diff --git a/packages/ui/src/forms/styled/StyledTextArea.tsx b/packages/ui/src/forms/styled/StyledTextArea.tsx
index 6c21be99ab..80514c257a 100644
--- a/packages/ui/src/forms/styled/StyledTextArea.tsx
+++ b/packages/ui/src/forms/styled/StyledTextArea.tsx
@@ -16,8 +16,8 @@ export const StyledTextArea = forwardRef<
ref={ref}
{...props}
className={clsx(
- "form-input block w-full rounded-md border-slate-300 text-sm shadow-sm placeholder:text-slate-300 focus:border-indigo-500 focus:ring-indigo-500 active:border-indigo-500 active:ring-indigo-500"
- // disabled && "text-slate-400"
+ "form-input block w-full rounded-sm border-gray-300 text-sm shadow-sm placeholder:text-gray-300 focus:border-blue-500 focus:ring-blue-500 active:border-blue-500 active:ring-blue-500"
+ // disabled && "text-gray-400"
)}
/>
);
diff --git a/packages/ui/src/forms/styled/StyledToggle.tsx b/packages/ui/src/forms/styled/StyledToggle.tsx
new file mode 100644
index 0000000000..3d18973e6d
--- /dev/null
+++ b/packages/ui/src/forms/styled/StyledToggle.tsx
@@ -0,0 +1,52 @@
+import { Switch } from "@headlessui/react";
+import clsx from "clsx";
+
+export type StyledToggleProps = {
+ checked: boolean;
+ showFocusRing?: boolean;
+ size?: "medium" | "tiny";
+ onChange(newValue: boolean): void;
+};
+
+export function StyledToggle({
+ checked,
+ showFocusRing = true,
+ size = "medium",
+ onChange,
+}: StyledToggleProps) {
+ const sizeClasses = {
+ medium: {
+ toggle: "h-6 w-11",
+ circle: "h-5 w-5",
+ translate: "translate-x-5",
+ },
+ tiny: {
+ toggle: "h-3 w-5",
+ circle: "h-2 w-2",
+ translate: "translate-x-2",
+ },
+ };
+
+ const { toggle, circle, translate } = sizeClasses[size];
+
+ return (
+
+ Toggle
+
+
+ );
+}
diff --git a/packages/ui/src/icons/HeroIcons.tsx b/packages/ui/src/icons/HeroIcons.tsx
index d2efb02a19..9bc2a5348a 100644
--- a/packages/ui/src/icons/HeroIcons.tsx
+++ b/packages/ui/src/icons/HeroIcons.tsx
@@ -165,21 +165,33 @@ export const LinkIcon: FC
= (props) => (
);
export const ChevronRightIcon: FC = (props) => (
-
+
);
export const ChevronLeftIcon: FC = (props) => (
-
+
);
diff --git a/packages/ui/src/icons/Icon.tsx b/packages/ui/src/icons/Icon.tsx
index 5014a2698c..2c035f7b68 100644
--- a/packages/ui/src/icons/Icon.tsx
+++ b/packages/ui/src/icons/Icon.tsx
@@ -4,6 +4,11 @@ export type IconProps = PropsWithChildren<{
size?: number;
className?: string;
onClick?: () => void;
+ strokeWidth?: number;
+ stroke?: string;
+ strokeLinecap?: "butt" | "round" | "square";
+ strokeLinejoin?: "miter" | "round" | "bevel";
+ fill?: string;
}>;
export const Icon: FC = ({
@@ -11,15 +16,24 @@ export const Icon: FC = ({
className,
onClick,
viewBox = "0 0 20 20",
+ strokeWidth,
+ stroke,
+ strokeLinecap,
+ strokeLinejoin,
+ fill = "currentColor",
children,
}) => (
{children}
diff --git a/packages/ui/src/icons/IconByName.tsx b/packages/ui/src/icons/IconByName.tsx
new file mode 100644
index 0000000000..4872075a7d
--- /dev/null
+++ b/packages/ui/src/icons/IconByName.tsx
@@ -0,0 +1,153 @@
+import { FC } from "react";
+
+import {
+ Die1Icon,
+ Die2Icon,
+ Die3Icon,
+ Die4Icon,
+ Die5Icon,
+ Die6Icon,
+} from "./DieIcons.js";
+import { DotsHorizontalIcon } from "./DotsHorizontalIcon.js";
+import { EditIcon } from "./EditIcon.js";
+import { EmptyIcon } from "./EmptyIcon.js";
+import { ErrorIcon } from "./ErrorIcon.js";
+import { ExternalLinkIcon } from "./ExternalLinkIcon.js";
+import { FocusIcon } from "./FocusIcon.js";
+import {
+ AdjustmentsHorizontalIcon,
+ AdjustmentsVerticalIcon,
+ ArchiveBoxIcon,
+ BackwardIcon,
+ BarChartIcon,
+ Bars3CenterLeftIcon,
+ Bars4Icon,
+ BoltIcon,
+ BookOpenIcon,
+ CalculatorIcon,
+ CheckIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ ClipboardCopyIcon,
+ CodeBracketIcon,
+ CodeBracketSquareIcon,
+ Cog8ToothIcon,
+ CommandLineIcon,
+ CommentIcon,
+ CubeTransparentIcon,
+ CurlyBracketsIcon,
+ DocumentTextIcon,
+ FireIcon,
+ GlobeIcon,
+ GroupIcon,
+ HashIcon,
+ HelpIcon,
+ LinkIcon,
+ ListBulletIcon,
+ LockIcon,
+ PauseIcon,
+ PlayIcon,
+ PuzzleIcon,
+ RectangleStackIcon,
+ ResetIcon,
+ RightArrowIcon,
+ ScaleIcon,
+ ScatterPlotIcon,
+ SearchIcon,
+ ShareIcon,
+ SquareBracketIcon,
+ TableCellsIcon,
+ UserCircleIcon,
+ UserIcon,
+ VariableIcon,
+ WrenchIcon,
+} from "./HeroIcons.js";
+import type { IconProps } from "./Icon.js";
+import { PlusIcon } from "./PlusIcon.js";
+import { RefreshIcon } from "./RefreshIcon.js";
+import { SignOutIcon } from "./SignOutIcon.js";
+import { TrashIcon } from "./TrashIcon.js";
+import { TriangleIcon } from "./TriangleIcon.js";
+import { XIcon } from "./XIcon.js";
+
+export const allIconsMap: Record | null> = {
+ AdjustmentsHorizontalIcon,
+ AdjustmentsVerticalIcon,
+ ArchiveBoxIcon,
+ BackwardIcon,
+ BarChartIcon,
+ Bars3CenterLeftIcon,
+ Bars4Icon,
+ BoltIcon,
+ BookOpenIcon,
+ CalculatorIcon,
+ CheckIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ ClipboardCopyIcon,
+ CodeBracketIcon,
+ CodeBracketSquareIcon,
+ Cog8ToothIcon,
+ CommandLineIcon,
+ CommentIcon,
+ CubeTransparentIcon,
+ CurlyBracketsIcon,
+ DocumentTextIcon,
+ DotsHorizontalIcon,
+ EditIcon,
+ EmptyIcon,
+ ErrorIcon,
+ ExternalLinkIcon,
+ FireIcon,
+ FocusIcon,
+ GlobeIcon,
+ GroupIcon,
+ HashIcon,
+ HelpIcon,
+ LinkIcon,
+ ListBulletIcon,
+ LockIcon,
+ PauseIcon,
+ PlayIcon,
+ PlusIcon,
+ PuzzleIcon,
+ RectangleStackIcon,
+ RefreshIcon,
+ ResetIcon,
+ RightArrowIcon,
+ ScaleIcon,
+ ScatterPlotIcon,
+ SearchIcon,
+ ShareIcon,
+ SignOutIcon,
+ SquareBracketIcon,
+ TableCellsIcon,
+ TrashIcon,
+ TriangleIcon,
+ UserCircleIcon,
+ UserIcon,
+ VariableIcon,
+ WrenchIcon,
+ XIcon,
+ Die1Icon,
+ Die2Icon,
+ Die3Icon,
+ Die4Icon,
+ Die5Icon,
+ Die6Icon,
+};
+
+const findByName = (name: string) => allIconsMap[name] || null;
+
+export const IconByName: FC = ({
+ name,
+ ...props
+}) => {
+ const IconComponent = findByName(name);
+
+ if (!IconComponent) {
+ return null;
+ }
+
+ return ;
+};
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index ecbe4acdee..11ec3103fb 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -38,6 +38,7 @@ export { StyledCheckbox } from "./forms/styled/StyledCheckbox.js";
export { StyledColorInput } from "./forms/styled/StyledColorInput.js";
export { StyledInput } from "./forms/styled/StyledInput.js";
export { StyledTextArea } from "./forms/styled/StyledTextArea.js";
+// export { StyledToggle } from "./forms/styled/StyledToggle.js"; // For some reason, this breaks the Hub, in dev.
export { DotsHorizontalIcon } from "./icons/DotsHorizontalIcon.js";
export { EditIcon } from "./icons/EditIcon.js";
@@ -108,6 +109,7 @@ export {
Die6Icon,
} from "./icons/DieIcons.js";
export { ErrorIcon } from "./icons/ErrorIcon.js";
+export { IconByName } from "./icons/IconByName.js";
export { useToast, WithToasts } from "./components/WithToasts/index.js";
diff --git a/packages/ui/src/stories/StyledTab.stories.tsx b/packages/ui/src/stories/Forms/StyledTab.stories.tsx
similarity index 87%
rename from packages/ui/src/stories/StyledTab.stories.tsx
rename to packages/ui/src/stories/Forms/StyledTab.stories.tsx
index f7cab6e3ba..1a2df0fd45 100644
--- a/packages/ui/src/stories/StyledTab.stories.tsx
+++ b/packages/ui/src/stories/Forms/StyledTab.stories.tsx
@@ -1,7 +1,7 @@
import type { Meta, StoryObj } from "@storybook/react";
-import { StyledTab } from "../components/StyledTab.js";
-import { BoltIcon, FireIcon } from "../index.js";
+import { StyledTab } from "../../components/StyledTab.js";
+import { BoltIcon, FireIcon } from "../../icons/HeroIcons.js";
/**
* StyledTab component wraps the [\](https://headlessui.com/react/tabs) component from Headless UI.
@@ -21,7 +21,7 @@ export const Default: Story = {
-
+
Code panel
Settings panel
diff --git a/packages/ui/src/stories/Forms/StyledToggle.stories.tsx b/packages/ui/src/stories/Forms/StyledToggle.stories.tsx
new file mode 100644
index 0000000000..3d04c8e700
--- /dev/null
+++ b/packages/ui/src/stories/Forms/StyledToggle.stories.tsx
@@ -0,0 +1,51 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { useState } from "react";
+
+import {
+ StyledToggle,
+ StyledToggleProps,
+} from "../../forms/styled/StyledToggle.js";
+
+const meta = { component: StyledToggle } satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+const renderToggle = (args: StyledToggleProps) => {
+ const [checked, setChecked] = useState(false);
+ return ;
+};
+
+export const Default: Story = {
+ args: {
+ checked: false,
+ onChange: (newValue: boolean) => {
+ console.log("Toggle value changed to:", newValue);
+ },
+ },
+
+ render: (args) => renderToggle(args),
+};
+
+export const WithoutFocusRing: Story = {
+ args: {
+ checked: false,
+ showFocusRing: false,
+ onChange: (newValue: boolean) => {
+ console.log("Toggle value changed to:", newValue);
+ },
+ },
+ render: (args) => renderToggle(args),
+};
+
+export const Tiny: Story = {
+ args: {
+ checked: false,
+ size: "tiny",
+ onChange: (newValue: boolean) => {
+ console.log("Toggle value changed to:", newValue);
+ },
+ },
+ render: (args) => renderToggle(args),
+};
diff --git a/packages/ui/src/stories/Icons/IconByName.stories.tsx b/packages/ui/src/stories/Icons/IconByName.stories.tsx
new file mode 100644
index 0000000000..9df946861d
--- /dev/null
+++ b/packages/ui/src/stories/Icons/IconByName.stories.tsx
@@ -0,0 +1,40 @@
+import type { Meta, StoryObj } from "@storybook/react";
+
+import { allIconsMap, IconByName } from "../../icons/IconByName.js";
+
+const meta = { component: IconByName } satisfies Meta;
+export default meta;
+type Story = StoryObj;
+
+export const AllIcons: Story = {
+ args: {
+ name: "DocumentTextIcon",
+ },
+ render: ({ size }) => (
+
+ {Object.keys(allIconsMap).map((name) => (
+
+ ))}
+
+ ),
+};
diff --git a/packages/versioned-components/src/SquigglePlaygroundVersionPicker.tsx b/packages/versioned-components/src/SquigglePlaygroundVersionPicker.tsx
deleted file mode 100644
index 7e51980e42..0000000000
--- a/packages/versioned-components/src/SquigglePlaygroundVersionPicker.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-"use client";
-import { FC } from "react";
-
-import {
- Button,
- Dropdown,
- DropdownMenu,
- DropdownMenuActionItem,
- DropdownMenuHeader,
- DropdownMenuLinkItem,
- DropdownMenuModalActionItem,
- DropdownMenuSeparator,
- HelpIcon,
- Modal,
-} from "@quri/ui";
-
-import {
- SquiggleVersionShower,
- versionIcon,
- versionTitle,
-} from "./SquiggleVersionShower.js";
-import { SquiggleVersion, squiggleVersions } from "./versions.js";
-
-const CHANGELOG_URL = "https://www.squiggle-language.com/docs/Changelog";
-
-export const SquigglePlaygroundVersionPicker: FC<{
- version: string;
- onChange: (newVersion: SquiggleVersion) => void;
- // This is mostly Squiggle Hub specific, but we might later decide to do auto-updates in Squiggle Playground too.
- showUpdatePolicy?: boolean;
- // "medium" was intended for "New Model" form in Squiggle Hub, but it's currently unused
- size: "small" | "medium";
-}> = ({ version, onChange, showUpdatePolicy, size }) => {
- return (
-
-
(
-
- Squiggle Version
- {squiggleVersions.map((version) => (
- {
- onChange(version);
- close();
- }}
- />
- ))}
-
- {showUpdatePolicy ? (
- (
-
-
-
-
- Version Update Policy
-
-
-
- {/* Markdown here would be nice, but we don't have it available in @quri/ui. */}
-
-
- We’ll auto-update your model to the most recent
- non-breaking version of Squiggle.
-
-
- However, if you use the{" "}
- Next version,
- it will always stay on the Beta - so only do this if
- you’re okay fixing it if it breaks, or temporarily if
- you need to use a feature that’s not been released
- yet.
-
-
-
-
- )}
- />
- ) : null}
-
-
- )}
- >
-
-
-
-
-
- );
-};
diff --git a/packages/versioned-components/src/SquigglePlaygroundVersionPickerDropdown.tsx b/packages/versioned-components/src/SquigglePlaygroundVersionPickerDropdown.tsx
new file mode 100644
index 0000000000..7eb89d35d9
--- /dev/null
+++ b/packages/versioned-components/src/SquigglePlaygroundVersionPickerDropdown.tsx
@@ -0,0 +1,112 @@
+"use client";
+import { FC, PropsWithChildren } from "react";
+
+import {
+ CodeBracketIcon,
+ Dropdown,
+ DropdownMenu,
+ DropdownMenuActionItem,
+ DropdownMenuHeader,
+ DropdownMenuLinkItem,
+ DropdownMenuModalActionItem,
+ DropdownMenuSeparator,
+ HelpIcon,
+ Modal,
+ WrenchIcon,
+} from "@quri/ui";
+
+import {
+ checkSquiggleVersion,
+ SquiggleVersion,
+ squiggleVersions,
+} from "./versions.js";
+
+export function versionTitle(version: SquiggleVersion) {
+ return version === "dev" ? "next" : `v${version}`;
+}
+
+export function uncheckedVersionTitle(version: string) {
+ const versionIsValid = checkSquiggleVersion(version);
+ if (versionIsValid) {
+ return versionTitle(version);
+ } else {
+ return `${version} (unknown)`;
+ }
+}
+
+export function versionIcon(version: string) {
+ return version === "dev" ? WrenchIcon : CodeBracketIcon;
+}
+
+const CHANGELOG_URL = "https://www.squiggle-language.com/docs/Changelog";
+
+export const SquigglePlaygroundVersionPickerDropdown: FC<
+ PropsWithChildren<{
+ onChange: (newVersion: SquiggleVersion) => void;
+ // This is mostly Squiggle Hub specific, but we might later decide to do auto-updates in Squiggle Playground too.
+ showUpdatePolicy?: boolean;
+ }>
+> = ({ onChange, showUpdatePolicy, children }) => {
+ return (
+ (
+
+ Squiggle Version
+ {squiggleVersions.map((version) => (
+ {
+ onChange(version);
+ close();
+ }}
+ />
+ ))}
+
+ {showUpdatePolicy ? (
+ (
+
+
+
+
+ Version Update Policy
+
+
+
+ {/* Markdown here would be nice, but we don't have it available in @quri/ui. */}
+
+
+ We’ll auto-update your model to the most recent
+ non-breaking version of Squiggle.
+
+
+ However, if you use the{" "}
+ Next version, it
+ will always stay on the Beta - so only do this if you’re
+ okay fixing it if it breaks, or temporarily if you need
+ to use a feature that’s not been released yet.
+
+
+
+
+ )}
+ />
+ ) : null}
+
+
+ )}
+ >
+ {children}
+
+ );
+};
diff --git a/packages/versioned-components/src/SquiggleVersionShower.tsx b/packages/versioned-components/src/SquiggleVersionShower.tsx
deleted file mode 100644
index 7e6e33e473..0000000000
--- a/packages/versioned-components/src/SquiggleVersionShower.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-"use client";
-import { FC } from "react";
-
-import { CodeBracketIcon, WrenchIcon } from "@quri/ui";
-
-import { checkSquiggleVersion, SquiggleVersion } from "./versions.js";
-
-export function versionTitle(version: SquiggleVersion) {
- return version === "dev" ? "Next" : version;
-}
-
-export function uncheckedVersionTitle(version: string) {
- const versionIsValid = checkSquiggleVersion(version);
- if (versionIsValid) {
- return versionTitle(version);
- } else {
- return `${version} (unknown)`;
- }
-}
-
-export function versionIcon(version: string) {
- return version === "dev" ? WrenchIcon : CodeBracketIcon;
-}
-
-export const SquiggleVersionShower: FC<{ version: string }> = ({ version }) => {
- const CurrentIcon = versionIcon(version);
-
- return (
-
-
-
- {uncheckedVersionTitle(version)}
-
-
- );
-};
diff --git a/packages/versioned-components/src/index.ts b/packages/versioned-components/src/index.ts
index a53155908c..ef9c73930f 100644
--- a/packages/versioned-components/src/index.ts
+++ b/packages/versioned-components/src/index.ts
@@ -1,5 +1,7 @@
-export { SquigglePlaygroundVersionPicker } from "./SquigglePlaygroundVersionPicker.js";
-export { SquiggleVersionShower } from "./SquiggleVersionShower.js";
+export {
+ SquigglePlaygroundVersionPickerDropdown,
+ uncheckedVersionTitle,
+} from "./SquigglePlaygroundVersionPickerDropdown.js";
export { versionedSquigglePackages } from "./versionedSquigglePackages.js";
export {
diff --git a/packages/website/src/components/PlaygroundPage/index.tsx b/packages/website/src/components/PlaygroundPage/index.tsx
index aa68e91f3d..49255ce14b 100644
--- a/packages/website/src/components/PlaygroundPage/index.tsx
+++ b/packages/website/src/components/PlaygroundPage/index.tsx
@@ -2,11 +2,13 @@ import { fromByteArray, toByteArray } from "base64-js";
import { deflate, inflate } from "pako";
import { FC, useEffect, useState } from "react";
+import { PlaygroundToolbarItem } from "@quri/squiggle-components";
import {
defaultSquiggleVersion,
- SquigglePlaygroundVersionPicker,
+ SquigglePlaygroundVersionPickerDropdown,
type SquiggleVersion,
squiggleVersions,
+ uncheckedVersionTitle,
versionedSquigglePackages,
} from "@quri/versioned-squiggle-components";
@@ -112,11 +114,14 @@ export const PlaygroundPage: FC<{ version: string | null }> = (props) => {
renderExtraControls={() => (
-
+ showUpdatePolicy
+ >
+
+ {uncheckedVersionTitle(version)}
+
+
)}
onCodeChange={(code) => updateUrl({ defaultCode: code }, version)}