Skip to content
edmi-ui

Environment Variables

Environment variable list with masked values, visibility switch and copy.

StackBlitz ↗StackBlitz ↗StackBlitz ↗

Environment variables

Show values
SOLANA_RPC_URLRequired••••••••••••
JUPITER_API_KEYRequired••••••••••••
KEEPER_MAX_SLIPPAGE••••••••••••

Environment variables

Show values
SOLANA_RPC_URL
Required
••••••••••••
JUPITER_API_KEY
Required
••••••••••••
KEEPER_MAX_SLIPPAGE••••••••••••

Environment variables

Show values
SOLANA_RPC_URL Required ••••••••••••
JUPITER_API_KEY Required ••••••••••••
KEEPER_MAX_SLIPPAGE ••••••••••••

Built on badge, button and switch.

Terminal window
npx shadcn@latest add @edmi-ui/ai-environment-variables
Terminal window
pnpm dlx shadcn@latest add @edmi-ui/ai-environment-variables
Terminal window
yarn dlx shadcn@latest add @edmi-ui/ai-environment-variables
Terminal window
bunx shadcn@latest add @edmi-ui/ai-environment-variables

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-vue@latest add @edmi-ui/ai-environment-variables
Terminal window
pnpm dlx shadcn-vue@latest add @edmi-ui/ai-environment-variables
Terminal window
yarn dlx shadcn-vue@latest add @edmi-ui/ai-environment-variables
Terminal window
bunx shadcn-vue@latest add @edmi-ui/ai-environment-variables

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-environment-variables.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-environment-variables.json
Terminal window
yarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-environment-variables.json
Terminal window
bunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-environment-variables.json

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

import {
EnvironmentVariables,
EnvironmentVariablesContent,
EnvironmentVariablesHeader,
EnvironmentVariablesTitle,
EnvironmentVariablesToggle,
} from "@/components/ai/environment-variables";
<EnvironmentVariables className="max-w-lg">
<EnvironmentVariablesHeader>
<EnvironmentVariablesTitle />
<EnvironmentVariablesToggle />
</EnvironmentVariablesHeader>
<EnvironmentVariablesContent>
</EnvironmentVariablesContent>
</EnvironmentVariables>
<script setup lang="ts">
import {
EnvironmentVariable,
EnvironmentVariableCopyButton,
EnvironmentVariableName,
EnvironmentVariableRequired,
EnvironmentVariables,
EnvironmentVariablesContent,
EnvironmentVariablesHeader,
EnvironmentVariablesTitle,
EnvironmentVariablesToggle,
EnvironmentVariableValue,
} from "@/components/ai/environment-variables";
</script>
<template>
<EnvironmentVariables class="max-w-lg">
<EnvironmentVariablesHeader>
<EnvironmentVariablesTitle />
<EnvironmentVariablesToggle />
</EnvironmentVariablesHeader>
<EnvironmentVariablesContent>
<EnvironmentVariable :key="v.name" :name="v.name" :value="v.value">
<EnvironmentVariableName />
<EnvironmentVariableRequired v-if="v.required" />
<EnvironmentVariableValue />
<EnvironmentVariableCopyButton copy-format="value" />
</EnvironmentVariable>
</EnvironmentVariablesContent>
</EnvironmentVariables>
</template>
<script lang="ts">
import {
EnvironmentVariable,
EnvironmentVariableCopyButton,
EnvironmentVariableName,
EnvironmentVariableRequired,
EnvironmentVariables,
EnvironmentVariablesContent,
EnvironmentVariablesHeader,
EnvironmentVariablesTitle,
EnvironmentVariablesToggle,
EnvironmentVariableValue,
} from "$lib/components/ai/environment-variables";
</script>
<EnvironmentVariables class="max-w-lg">
<EnvironmentVariablesHeader>
<EnvironmentVariablesTitle />
<EnvironmentVariablesToggle />
</EnvironmentVariablesHeader>
<EnvironmentVariablesContent>
{#each vars as v (v.name)}
<EnvironmentVariable name={v.name} value={v.value}>
<EnvironmentVariableName />
{#if v.required}<EnvironmentVariableRequired />{/if}
<EnvironmentVariableValue />
<EnvironmentVariableCopyButton copyFormat="value" />
</EnvironmentVariable>
{/each}
</EnvironmentVariablesContent>
</EnvironmentVariables>
registry/components/ai/environment-variables.tsx
"use client";
import { cn } from "cn";
import type { ComponentProps, HTMLAttributes } from "react";
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { EyeIcon, EyeSlashIcon, CheckIcon, CopyIcon } from "@phosphor-icons/react";
import { Badge } from "@/registry/edmi/ui/badge";
import { Button } from "@/registry/edmi/ui/button";
import { Switch } from "@/registry/edmi/ui/switch";
interface EnvironmentVariablesContextType {
showValues: boolean;
setShowValues: (show: boolean) => void;
}
const noop = () => {};
const EnvironmentVariablesContext =
createContext<EnvironmentVariablesContextType>({
setShowValues: noop,
showValues: false,
});
export type EnvironmentVariablesProps = HTMLAttributes<HTMLDivElement> & {
showValues?: boolean;
defaultShowValues?: boolean;
onShowValuesChange?: (show: boolean) => void;
};
export const EnvironmentVariables = ({
showValues: controlledShowValues,
defaultShowValues = false,
onShowValuesChange,
className,
children,
...props
}: EnvironmentVariablesProps) => {
const [internalShowValues, setInternalShowValues] =
useState(defaultShowValues);
const showValues = controlledShowValues ?? internalShowValues;
const setShowValues = useCallback(
(show: boolean) => {
setInternalShowValues(show);
onShowValuesChange?.(show);
},
[onShowValuesChange],
);
const contextValue = useMemo(
() => ({ setShowValues, showValues }),
[setShowValues, showValues],
);
return (
<EnvironmentVariablesContext.Provider value={contextValue}>
<div
data-slot="ai-environment-variables"
className={cn(
"overflow-hidden rounded-xl border border-border bg-card text-card-foreground",
className,
)}
{...props}
>
{children}
</div>
</EnvironmentVariablesContext.Provider>
);
};
export type EnvironmentVariablesHeaderProps = HTMLAttributes<HTMLDivElement>;
export const EnvironmentVariablesHeader = ({
className,
children,
...props
}: EnvironmentVariablesHeaderProps) => (
<div
className={cn("flex items-center justify-between px-3.5 py-2.5", className)}
{...props}
>
{children}
</div>
);
export type EnvironmentVariablesTitleProps = HTMLAttributes<HTMLHeadingElement>;
export const EnvironmentVariablesTitle = ({
className,
children,
...props
}: EnvironmentVariablesTitleProps) => (
<h3 className={cn("text-[13.5px] font-semibold", className)} {...props}>
{children ?? "Environment variables"}
</h3>
);
export type EnvironmentVariablesToggleProps = ComponentProps<typeof Switch>;
export const EnvironmentVariablesToggle = ({
className,
...props
}: EnvironmentVariablesToggleProps) => {
const { showValues, setShowValues } = useContext(EnvironmentVariablesContext);
return (
<div className={cn("flex items-center gap-2 text-[12.5px]", className)}>
<span className="text-muted-foreground">
{showValues ? (
<EyeIcon
className="size-3.5"
/>
) : (
<EyeSlashIcon
className="size-3.5"
/>
)}
</span>
<span>Show values</span>
<Switch
aria-label="Toggle value visibility"
checked={showValues}
onCheckedChange={setShowValues}
size="sm"
{...props}
/>
</div>
);
};
export type EnvironmentVariablesContentProps = HTMLAttributes<HTMLDivElement>;
export const EnvironmentVariablesContent = ({
className,
children,
...props
}: EnvironmentVariablesContentProps) => (
<div
className={cn(
"grid grid-cols-[190px_auto_minmax(0,1fr)_auto] gap-x-2.5",
className,
)}
{...props}
>
{children}
</div>
);
interface EnvironmentVariableContextType {
name: string;
value: string;
}
const EnvironmentVariableContext =
createContext<EnvironmentVariableContextType>({
name: "",
value: "",
});
export type EnvironmentVariableGroupProps = HTMLAttributes<HTMLDivElement>;
export const EnvironmentVariableGroup = ({
className,
children,
...props
}: EnvironmentVariableGroupProps) => (
<div className={cn("flex items-center gap-2", className)} {...props}>
{children}
</div>
);
export type EnvironmentVariableNameProps = HTMLAttributes<HTMLSpanElement>;
export const EnvironmentVariableName = ({
className,
children,
...props
}: EnvironmentVariableNameProps) => {
const { name } = useContext(EnvironmentVariableContext);
return (
<span
className={cn(
"col-start-1 w-[190px] shrink-0 truncate font-mono text-[12.5px]",
className,
)}
{...props}
>
{children ?? name}
</span>
);
};
export type EnvironmentVariableValueProps = HTMLAttributes<HTMLSpanElement>;
export const EnvironmentVariableValue = ({
className,
children,
...props
}: EnvironmentVariableValueProps) => {
const { value } = useContext(EnvironmentVariableContext);
const { showValues } = useContext(EnvironmentVariablesContext);
// Masked values use a fixed length so the real length never leaks.
const displayValue = showValues ? value : "•".repeat(12);
return (
<span
className={cn(
"col-start-3 min-w-0 truncate font-mono text-[12.5px]",
!showValues && "tracking-[2px] text-muted-foreground select-none",
className,
)}
{...props}
>
{children ?? displayValue}
</span>
);
};
export type EnvironmentVariableProps = HTMLAttributes<HTMLDivElement> & {
name: string;
value: string;
};
export const EnvironmentVariable = ({
name,
value,
className,
children,
...props
}: EnvironmentVariableProps) => {
const envVarContextValue = useMemo(() => ({ name, value }), [name, value]);
return (
<EnvironmentVariableContext.Provider value={envVarContextValue}>
<div
data-slot="ai-environment-variable"
className={cn(
"col-span-full grid grid-cols-subgrid items-center border-t border-border-2 px-3.5 py-[9px]",
className,
)}
{...props}
>
{children ?? (
<>
<EnvironmentVariableName />
<EnvironmentVariableValue />
</>
)}
</div>
</EnvironmentVariableContext.Provider>
);
};
export type EnvironmentVariableCopyButtonProps = ComponentProps<
typeof Button
> & {
onCopy?: () => void;
onError?: (error: Error) => void;
timeout?: number;
copyFormat?: "name" | "value" | "export";
};
export const EnvironmentVariableCopyButton = ({
onCopy,
onError,
timeout = 2000,
copyFormat = "value",
children,
className,
...props
}: EnvironmentVariableCopyButtonProps) => {
const [isCopied, setIsCopied] = useState(false);
const timeoutRef = useRef<number>(0);
const { name, value } = useContext(EnvironmentVariableContext);
const getTextToCopy = useCallback((): string => {
const formatMap = {
export: () => `export ${name}="${value}"`,
name: () => name,
value: () => value,
};
return formatMap[copyFormat]();
}, [name, value, copyFormat]);
const copyToClipboard = useCallback(async () => {
if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
onError?.(new Error("Clipboard API not available"));
return;
}
try {
await navigator.clipboard.writeText(getTextToCopy());
setIsCopied(true);
onCopy?.();
timeoutRef.current = window.setTimeout(() => setIsCopied(false), timeout);
} catch (error) {
onError?.(error as Error);
}
}, [getTextToCopy, onCopy, onError, timeout]);
useEffect(
() => () => {
window.clearTimeout(timeoutRef.current);
},
[],
);
return (
<Button
aria-label={`Copy ${copyFormat}`}
className={cn("col-start-4 shrink-0", className)}
onClick={copyToClipboard}
size="icon-xs"
variant="ghost"
{...props}
>
{children ??
(isCopied ? (
<CheckIcon
/>
) : (
<CopyIcon
/>
))}
</Button>
);
};
export type EnvironmentVariableRequiredProps = ComponentProps<typeof Badge>;
export const EnvironmentVariableRequired = ({
className,
children,
...props
}: EnvironmentVariableRequiredProps) => (
<Badge
className={cn("col-start-2 h-[18px] text-[10.5px]", className)}
variant="warning"
{...props}
>
{children ?? "Required"}
</Badge>
);
registry/components/ai/environment-variables/EnvironmentVariable.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { computed, provide } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { EnvironmentVariableKey } from "./context"
import EnvironmentVariableName from "./EnvironmentVariableName.vue"
import EnvironmentVariableValue from "./EnvironmentVariableValue.vue"
const props = defineProps<{
name: string
value: string
class?: HTMLAttributes["class"]
}>()
provide(EnvironmentVariableKey, {
name: computed(() => props.name),
value: computed(() => props.value),
})
</script>
<template>
<div
data-slot="ai-environment-variable"
:class="cn('col-span-full grid grid-cols-subgrid items-center border-t border-border-2 px-3.5 py-[9px]', props.class)"
>
<slot>
<EnvironmentVariableName />
<EnvironmentVariableValue />
</slot>
</div>
</template>
registry/components/ai/environment-variables/EnvironmentVariableCopyButton.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { CheckIcon, CopyIcon } from "@lucide/vue"
import { onBeforeUnmount, ref } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { Button } from "@/registry/edmi/ui/button"
import { useEnvironmentVariableContext } from "./context"
const props = withDefaults(defineProps<{
copyFormat?: "name" | "value" | "export"
timeout?: number
class?: HTMLAttributes["class"]
}>(), {
copyFormat: "value",
timeout: 2000,
})
const emit = defineEmits<{
(event: "copy"): void
(event: "error", error: Error): void
}>()
const { name, value } = useEnvironmentVariableContext()
const isCopied = ref(false)
let resetTimer: ReturnType<typeof setTimeout> | undefined
function textToCopy() {
if (props.copyFormat === "export")
return `export ${name.value}="${value.value}"`
return props.copyFormat === "name" ? name.value : value.value
}
async function copyToClipboard() {
if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
emit("error", new Error("Clipboard API not available"))
return
}
try {
await navigator.clipboard.writeText(textToCopy())
isCopied.value = true
emit("copy")
if (resetTimer) {
clearTimeout(resetTimer)
}
resetTimer = setTimeout(() => {
isCopied.value = false
}, props.timeout)
}
catch (error) {
emit("error", error instanceof Error ? error : new Error("Copy failed"))
}
}
onBeforeUnmount(() => {
if (resetTimer) {
clearTimeout(resetTimer)
}
})
</script>
<template>
<Button
:aria-label="`Copy ${props.copyFormat}`"
:class="cn('col-start-4 shrink-0', props.class)"
size="icon-xs"
type="button"
variant="ghost"
@click="copyToClipboard"
>
<slot>
<CheckIcon v-if="isCopied" />
<CopyIcon v-else />
</slot>
</Button>
</template>
registry/components/ai/environment-variables/EnvironmentVariableGroup.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<div :class="cn('flex items-center gap-2', props.class)">
<slot />
</div>
</template>
registry/components/ai/environment-variables/EnvironmentVariableName.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { useEnvironmentVariableContext } from "./context"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
const { name } = useEnvironmentVariableContext()
</script>
<template>
<span :class="cn('col-start-1 w-[190px] shrink-0 truncate font-mono text-[12.5px]', props.class)">
<slot>{{ name }}</slot>
</span>
</template>
registry/components/ai/environment-variables/EnvironmentVariableRequired.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { Badge } from "@/registry/edmi/ui/badge"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<Badge variant="warning" :class="cn('col-start-2 h-[18px] text-[10.5px]', props.class)">
<slot>Required</slot>
</Badge>
</template>
registry/components/ai/environment-variables/EnvironmentVariableValue.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { computed } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { useEnvironmentVariableContext, useEnvironmentVariablesContext } from "./context"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
const { value } = useEnvironmentVariableContext()
const { showValues } = useEnvironmentVariablesContext()
// Masked values use a fixed length so the real length never leaks.
const displayValue = computed(() => (showValues.value ? value.value : "•".repeat(12)))
</script>
<template>
<span
:class="cn(
'col-start-3 min-w-0 truncate font-mono text-[12.5px]',
!showValues && 'tracking-[2px] text-muted-foreground select-none',
props.class,
)"
>
<slot>{{ displayValue }}</slot>
</span>
</template>
registry/components/ai/environment-variables/EnvironmentVariables.vue
<script setup lang="ts">
import type { HTMLAttributes, Ref } from "vue"
import { useVModel } from "@vueuse/core"
import { provide } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { EnvironmentVariablesKey } from "./context"
const props = withDefaults(defineProps<{
showValues?: boolean
defaultShowValues?: boolean
class?: HTMLAttributes["class"]
}>(), {
showValues: undefined,
defaultShowValues: false,
})
const emit = defineEmits<{
(e: "update:showValues", show: boolean): void
}>()
// defaultValue guarantees a boolean at runtime.
const showValues = useVModel(props, "showValues", emit, {
passive: true,
defaultValue: props.defaultShowValues,
}) as Ref<boolean>
provide(EnvironmentVariablesKey, {
showValues,
setShowValues: (show: boolean) => {
showValues.value = show
},
})
</script>
<template>
<div
data-slot="ai-environment-variables"
:class="cn('overflow-hidden rounded-xl border border-border bg-card text-card-foreground', props.class)"
>
<slot />
</div>
</template>
registry/components/ai/environment-variables/EnvironmentVariablesContent.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<div :class="cn('grid grid-cols-[190px_auto_minmax(0,1fr)_auto] gap-x-2.5', props.class)">
<slot />
</div>
</template>
registry/components/ai/environment-variables/EnvironmentVariablesHeader.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<div :class="cn('flex items-center justify-between px-3.5 py-2.5', props.class)">
<slot />
</div>
</template>
registry/components/ai/environment-variables/EnvironmentVariablesTitle.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<h3 :class="cn('text-[13.5px] font-semibold', props.class)">
<slot>Environment variables</slot>
</h3>
</template>
registry/components/ai/environment-variables/EnvironmentVariablesToggle.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { EyeIcon, LockIcon } from "@lucide/vue"
import { cn } from "@/registry/edmi/lib/utils"
import { Switch } from "@/registry/edmi/ui/switch"
import { useEnvironmentVariablesContext } from "./context"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
const { showValues, setShowValues } = useEnvironmentVariablesContext()
</script>
<template>
<div :class="cn('flex items-center gap-2 text-[12.5px]', props.class)">
<span class="text-muted-foreground">
<EyeIcon v-if="showValues" class="size-3.5" />
<LockIcon v-else class="size-3.5" />
</span>
<span>Show values</span>
<Switch
aria-label="Toggle value visibility"
size="sm"
:model-value="showValues"
@update:model-value="(v: boolean) => setShowValues(v)"
/>
</div>
</template>
registry/components/ai/environment-variables/context.ts
import type { ComputedRef, InjectionKey, Ref } from "vue";
import { inject } from "vue";
export interface EnvironmentVariablesContext {
showValues: Ref<boolean>;
setShowValues: (show: boolean) => void;
}
export const EnvironmentVariablesKey: InjectionKey<EnvironmentVariablesContext> =
Symbol("EnvironmentVariables");
export function useEnvironmentVariablesContext() {
const ctx = inject(EnvironmentVariablesKey);
if (!ctx)
throw new Error(
"Environment variable parts must be used within <EnvironmentVariables />",
);
return ctx;
}
export interface EnvironmentVariableContext {
name: ComputedRef<string>;
value: ComputedRef<string>;
}
export const EnvironmentVariableKey: InjectionKey<EnvironmentVariableContext> =
Symbol("EnvironmentVariable");
export function useEnvironmentVariableContext() {
const ctx = inject(EnvironmentVariableKey);
if (!ctx)
throw new Error(
"EnvironmentVariable parts must be used within <EnvironmentVariable />",
);
return ctx;
}
registry/components/ai/environment-variables/index.ts
export * from "./context";
export { default as EnvironmentVariable } from "./EnvironmentVariable.vue";
export { default as EnvironmentVariableCopyButton } from "./EnvironmentVariableCopyButton.vue";
export { default as EnvironmentVariableGroup } from "./EnvironmentVariableGroup.vue";
export { default as EnvironmentVariableName } from "./EnvironmentVariableName.vue";
export { default as EnvironmentVariableRequired } from "./EnvironmentVariableRequired.vue";
export { default as EnvironmentVariables } from "./EnvironmentVariables.vue";
export { default as EnvironmentVariablesContent } from "./EnvironmentVariablesContent.vue";
export { default as EnvironmentVariablesHeader } from "./EnvironmentVariablesHeader.vue";
export { default as EnvironmentVariablesTitle } from "./EnvironmentVariablesTitle.vue";
export { default as EnvironmentVariablesToggle } from "./EnvironmentVariablesToggle.vue";
export { default as EnvironmentVariableValue } from "./EnvironmentVariableValue.vue";
src/lib/registry/ai/environment-variables/environment-variable-copy-button.svelte
<script lang="ts">
import CheckIcon from "phosphor-svelte/lib/Check";
import CopyIcon from "phosphor-svelte/lib/Copy";
import { Button, type ButtonProps } from "$lib/registry/ui/button/index.js";
import { cn } from "$lib/utils.js";
import { onDestroy } from "svelte";
import { useEnvironmentVariableContext } from "./use-environment-variables.svelte.js";
let {
onCopy,
onError,
timeout = 2000,
copyFormat = "value",
class: className,
children,
...restProps
}: Omit<ButtonProps, "onclick" | "href"> & {
onCopy?: () => void;
onError?: (error: Error) => void;
timeout?: number;
copyFormat?: "name" | "value" | "export";
} = $props();
const item = useEnvironmentVariableContext();
let isCopied = $state(false);
let timer: ReturnType<typeof setTimeout> | undefined;
function textToCopy() {
if (copyFormat === "export") return `export ${item.name}="${item.value}"`;
return copyFormat === "name" ? item.name : item.value;
}
async function copy() {
if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
onError?.(new Error("Clipboard API not available"));
return;
}
try {
await navigator.clipboard.writeText(textToCopy());
isCopied = true;
onCopy?.();
clearTimeout(timer);
timer = setTimeout(() => (isCopied = false), timeout);
} catch (error) {
onError?.(error as Error);
}
}
onDestroy(() => clearTimeout(timer));
</script>
<Button
aria-label={`Copy ${copyFormat}`}
class={cn("col-start-4 shrink-0", className)}
onclick={copy}
size="icon-xs"
type="button"
variant="ghost"
{...restProps}
>
{#if children}
{@render children()}
{:else if isCopied}
<CheckIcon
/>
{:else}
<CopyIcon
/>
{/if}
</Button>
src/lib/registry/ai/environment-variables/environment-variable-group.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
let {
class: className,
children,
...restProps
}: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
</script>
<div class={cn("flex items-center gap-2", className)} {...restProps}>
{@render children?.()}
</div>
src/lib/registry/ai/environment-variables/environment-variable-name.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
import { useEnvironmentVariableContext } from "./use-environment-variables.svelte.js";
let {
class: className,
children,
...restProps
}: HTMLAttributes<HTMLSpanElement> & { children?: Snippet } = $props();
const item = useEnvironmentVariableContext();
</script>
<span class={cn("col-start-1 w-[190px] shrink-0 truncate font-mono text-[12.5px]", className)} {...restProps}>
{#if children}{@render children()}{:else}{item.name}{/if}
</span>
src/lib/registry/ai/environment-variables/environment-variable-required.svelte
<script lang="ts">
import { Badge } from "$lib/registry/ui/badge/index.js";
import { cn } from "$lib/utils.js";
import type { ComponentProps } from "svelte";
let { class: className, children, ...restProps }: ComponentProps<typeof Badge> = $props();
</script>
<Badge variant="warning" class={cn("col-start-2 h-[18px] text-[10.5px]", className)} {...restProps}>
{#if children}{@render children()}{:else}Required{/if}
</Badge>
src/lib/registry/ai/environment-variables/environment-variable-value.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
import {
useEnvironmentVariableContext,
useEnvironmentVariablesContext,
} from "./use-environment-variables.svelte.js";
let {
class: className,
children,
...restProps
}: HTMLAttributes<HTMLSpanElement> & { children?: Snippet } = $props();
const item = useEnvironmentVariableContext();
const list = useEnvironmentVariablesContext();
// Masked values use a fixed length so the real length never leaks.
const display = $derived(list.showValues ? item.value : "•".repeat(12));
</script>
<span
class={cn(
"col-start-3 min-w-0 truncate font-mono text-[12.5px]",
!list.showValues && "tracking-[2px] text-muted-foreground select-none",
className
)}
{...restProps}
>
{#if children}{@render children()}{:else}{display}{/if}
</span>
src/lib/registry/ai/environment-variables/environment-variable.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
import EnvironmentVariableName from "./environment-variable-name.svelte";
import EnvironmentVariableValue from "./environment-variable-value.svelte";
import { setEnvironmentVariableContext } from "./use-environment-variables.svelte.js";
let {
name,
value,
class: className,
children,
...restProps
}: HTMLAttributes<HTMLDivElement> & { name: string; value: string; children?: Snippet } =
$props();
setEnvironmentVariableContext({
get name() {
return name;
},
get value() {
return value;
},
});
</script>
<div
data-slot="ai-environment-variable"
class={cn("col-span-full grid grid-cols-subgrid items-center border-t border-border-2 px-3.5 py-[9px]", className)}
{...restProps}
>
{#if children}
{@render children()}
{:else}
<EnvironmentVariableName />
<EnvironmentVariableValue />
{/if}
</div>
src/lib/registry/ai/environment-variables/environment-variables-content.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
let {
class: className,
children,
...restProps
}: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
</script>
<div class={cn("grid grid-cols-[190px_auto_minmax(0,1fr)_auto] gap-x-2.5", className)} {...restProps}>
{@render children?.()}
</div>
src/lib/registry/ai/environment-variables/environment-variables-header.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
let {
class: className,
children,
...restProps
}: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
</script>
<div class={cn("flex items-center justify-between px-3.5 py-2.5", className)} {...restProps}>
{@render children?.()}
</div>
src/lib/registry/ai/environment-variables/environment-variables-title.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
let {
class: className,
children,
...restProps
}: HTMLAttributes<HTMLHeadingElement> & { children?: Snippet } = $props();
</script>
<h3 class={cn("text-[13.5px] font-semibold", className)} {...restProps}>
{#if children}{@render children()}{:else}Environment variables{/if}
</h3>
src/lib/registry/ai/environment-variables/environment-variables-toggle.svelte
<script lang="ts">
import EyeIcon from "phosphor-svelte/lib/Eye";
import EyeSlashIcon from "phosphor-svelte/lib/EyeSlash";
import { Switch } from "$lib/registry/ui/switch/index.js";
import { cn } from "$lib/utils.js";
import { useEnvironmentVariablesContext } from "./use-environment-variables.svelte.js";
let { class: className }: { class?: string } = $props();
const list = useEnvironmentVariablesContext();
</script>
<div class={cn("flex items-center gap-2 text-[12.5px]", className)}>
<span class="text-muted-foreground">
{#if list.showValues}
<EyeIcon
class="size-3.5"
/>
{:else}
<EyeSlashIcon
class="size-3.5"
/>
{/if}
</span>
<span>Show values</span>
<Switch
aria-label="Toggle value visibility"
size="sm"
checked={list.showValues}
onCheckedChange={(v) => list.setShowValues(v)}
/>
</div>
src/lib/registry/ai/environment-variables/environment-variables.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
import { setEnvironmentVariablesContext } from "./use-environment-variables.svelte.js";
let {
showValues = $bindable(false),
class: className,
children,
...restProps
}: HTMLAttributes<HTMLDivElement> & { showValues?: boolean; children?: Snippet } = $props();
setEnvironmentVariablesContext({
get showValues() {
return showValues;
},
setShowValues(show: boolean) {
showValues = show;
},
});
</script>
<div
data-slot="ai-environment-variables"
class={cn(
"overflow-hidden rounded-xl border border-border bg-card text-card-foreground",
className
)}
{...restProps}
>
{@render children?.()}
</div>
src/lib/registry/ai/environment-variables/use-environment-variables.svelte.ts
import { getContext, setContext } from "svelte";
/** Getter objects keep the contexts reactive. */
export interface EnvironmentVariablesContextValue {
readonly showValues: boolean;
setShowValues: (show: boolean) => void;
}
export interface EnvironmentVariableContextValue {
readonly name: string;
readonly value: string;
}
const LIST_KEY = Symbol("ai-environment-variables");
const ITEM_KEY = Symbol("ai-environment-variable");
export function setEnvironmentVariablesContext(
value: EnvironmentVariablesContextValue,
) {
return setContext(LIST_KEY, value);
}
export function useEnvironmentVariablesContext(): EnvironmentVariablesContextValue {
const ctx = getContext<EnvironmentVariablesContextValue | undefined>(
LIST_KEY,
);
if (!ctx) {
throw new Error(
"Environment variable parts must be used within <EnvironmentVariables>",
);
}
return ctx;
}
export function setEnvironmentVariableContext(
value: EnvironmentVariableContextValue,
) {
return setContext(ITEM_KEY, value);
}
export function useEnvironmentVariableContext(): EnvironmentVariableContextValue {
const ctx = getContext<EnvironmentVariableContextValue | undefined>(ITEM_KEY);
if (!ctx) {
throw new Error(
"EnvironmentVariable parts must be used within <EnvironmentVariable>",
);
}
return ctx;
}
src/lib/registry/ai/environment-variables/index.ts
import Variable from "./environment-variable.svelte";
import VariableCopyButton from "./environment-variable-copy-button.svelte";
import VariableGroup from "./environment-variable-group.svelte";
import VariableName from "./environment-variable-name.svelte";
import VariableRequired from "./environment-variable-required.svelte";
import VariableValue from "./environment-variable-value.svelte";
import Root from "./environment-variables.svelte";
import Content from "./environment-variables-content.svelte";
import Header from "./environment-variables-header.svelte";
import Title from "./environment-variables-title.svelte";
import Toggle from "./environment-variables-toggle.svelte";
export {
useEnvironmentVariableContext,
useEnvironmentVariablesContext,
} from "./use-environment-variables.svelte.js";
export {
Content as EnvironmentVariablesContent,
Header as EnvironmentVariablesHeader,
Root as EnvironmentVariables,
Title as EnvironmentVariablesTitle,
Toggle as EnvironmentVariablesToggle,
Variable as EnvironmentVariable,
VariableCopyButton as EnvironmentVariableCopyButton,
VariableGroup as EnvironmentVariableGroup,
VariableName as EnvironmentVariableName,
VariableRequired as EnvironmentVariableRequired,
VariableValue as EnvironmentVariableValue,
};
Part Prop Type Default
EnvironmentVariables showValues, defaultShowValues, onShowValuesChange boolean, boolean, (show) => void false
EnvironmentVariable name, value string, string
EnvironmentVariableCopyButton copyFormat name | value | export value
EnvironmentVariableRequired Badge props warning
  • Masked values use a fixed length so the real length never leaks.
  • The toggle shows a “Show values” label next to the eye icon.