Skip to content

[SDK] UI Components

FTCHD edited this page Sep 6, 2024 · 1 revision

⚠️ Note: Do not import components outside of the @/sdk/components folder. This does not refer to your own components that you create in your template folder.

All components are imported from @/sdk/components.

ColorPicker + Background Image Uploader

A comprehensive component that allows users to select and input their own colors, gradients, and background images.

You can also enable or disable options by passing them in the mode prop. The available modes are solid, gradient and image.

If you pass also it an uploadBackground function, users will be able to upload their own images and use as backgrounds.

The upload functionality is not handled by the component, you must build it yourself using the uploadImage function. This is to enable more dynamic behaviors, as you may want to do some additional processing of the uploaded image before it is used, or you may want to use the image in other places in the Frame too.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { ColorPicker } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<ColorPicker
					className="w-full"
					background={config.bg || 'black'}
					setBackground={(value) => updateConfig({ bg: value })}
					uploadBackground={async (base64String, contentType) => {
						const { filePath } = await uploadImage({
							frameId: frameId,
							base64String: base64String,
							contentType: contentType,
						})

						return filePath
					}}
				/>
			</Configuration.Section>
		</Configuration.Root>
	)
}

FontFamilyPicker

A selector component that allows users to select a font family from a predefined list of Google Fonts supported by FrameTrain.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { FontFamilyPicker } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<FontFamilyPicker
					defaultValue={tweet.fontFamily}
					onSelect={(font) => {
						// An example where we change the font family for a tweet
						const newTweets = config?.tweets?.map((t: any) =>
							t.link === tweet.link
								? {
										...t,
										fontFamily: font,
									}
								: t
						)

						updateConfig({ tweets: newTweets })
					}}
				/>
			</Configuration.Section>
		</Configuration.Root>
	)
}

FontWeightPicker

A selector component that allows users to select a font weight from a predefined list.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { FontStylePicker } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<FontWeightPicker
					onSelect={(weight) =>
						updateConfig({
							title: {
								...config.title,
								fontWeight: weight,
							},
						})
					}
				/>
			</Configuration.Section>
		</Configuration.Root>
	)
}

FontStylePicker

A selector component that allows users to select a font style from a predefined list.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { FontWeightPicker } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<FontStylePicker
					defaultValue={config?.title?.fontStyle || 'normal'}
					onSelect={(style) =>
						updateConfig({
							title: {
								...config.title,
								fontStyle: style,
							},
						})
					}
				/>
			</Configuration.Section>
		</Configuration.Root>
	)
}

GatingInspector

A pre-made component that allows Users to configure gating options.

You can have different gating options for one View, and different options for another View. To achieve this simply create different objects in your config that stores the gating options for each View. Afterwards make sure you pass the right object to the GatingInspector component and to its runGatingChecks counterpart.

Example

Inspector

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig, useFarcasterId } from '@/sdk/hooks'
import { GatingInspector } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	const fid = useFarcasterId()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<GatingInspector 
					config={config.gating} 
					fid={fid}
					// disabledOptions={[]} optional, if you want to disable some gating options
					onUpdate={(newConfig) => {
						updateConfig({ gating: newConfig }) // we are updating the **`gating`** property in the **`config`**
					}}
				/>
			</Configuration.Section>
		</Configuration.Root>
	)
}

Handler

import { runGatingChecks } from '@/lib/gating'

export default function myHandler({
	body,
    config,
    storage,
}: {
    body: FramePayloadValidated
    config: Config
    storage: Storage
}): Promise<BuildFrameData> {
	runGatingChecks(body, config.gating)
	
	...
}

To learn more about the gating functionality in FrameTrain, see the Gating section.

BasicViewInspector

This component is the Inspector counterpart of the BasicView. You use the BasicViewInspector to let the user configure the BasicView customization settings. Both the View, as well as the Inspector for it, have been created in order for you to not have to write the same code over and over again.

There is no limit to the number of BasicViewInspector s you can have in your Inspector, it depends on how many BasicViews you have in your template for the User to configure.

The BasicViewInspector component takes in a config parameter and an onUpdate function. The onUpdate function is called when the User changes the configuration. The config parameter is the configuration of this specific BasicView.

Do not confuse the config parameter of the BasicViewInspector with the root config of your template (from the useFrameConfig hook).

As a general rule, even if you don't use the BasicViewInspector and BasicView components, the way you structure your config is the same. Each View that has customization options needs to have its own key/object in the root config object.

For example, if you have 2 Views (say Cover and Page) and both of them are customizable, you structure your config as follows:

config: {
	cover: {
		...
	},
	page: {
		...
	}
	...rest of config
}

The power of the BasicViewInspector and its View counterpart is that you only have to feed it the correct config object, and then do the same in your Handler to the BasicView component. It will take care of the rest.

Example

Inspector

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { BasicViewInspector } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<BasicViewInspector 
					config={config.cover}
					initialConfig={initialConfig.cover}
					onUpdate={(newConfig) => {
						updateConfig({ cover: newConfig }) // we are updating the **`cover`** property in the **`config`**
					}} 
				/>
			</Configuration.Section>
		</Configuration.Root>
	)
}

Handler

import { BasicView } from '@/sdk/views'

export default function myHandler({
	
}) {
	...
	
	return {
		...
		component: BasicView(config.cover), // the same object as the one passed to the BasicViewInspector
	}
}

Select

 

A native html Select component, that is displayed as a native element on mobile devices such as iOS and Android.

It is used the same way as select in HTML, specifying options using the option element.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Select } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<Select
					defaultValue={config.selectedMeme}
					placeholder="Select meme template"
					onChange={(newSelection) => {
						updateConfig({
							selectedMeme: newSelection,
						})
					}}
				>
					{memeTemplates.map((meme) => (
						<option key={meme.id} value={meme.id}>
							{meme.name}
						</option>
					))}
				</Select>
			</Configuration.Section>
		</Configuration.Root>
	)
}

Button

A button component with 3 variants (primary, secondary and destructive).

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Button } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<Button
					variant="destructive"
					onClick={() => {
						updateConfig({
							myObject: { ...config.myObject, image: null },
						})
					}}
				>
					Remove
				</Button>
			</Configuration.Section>
		</Configuration.Root>
	)
}

Checkbox

A checkbox component.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Checkbox } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				 <Checkbox
                    defaultChecked={config.myValue || defaultConfig.myValue}
                    onCheckedChange={(newValue) => {
                        onChange({ myValue: newValue })
                    }}
                />
			</Configuration.Section>
		</Configuration.Root>
	)
}

Label

A label component, used in combination with other input-like components.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Label } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				 <div className="flex items-center space-x-2">
					<RadioGroup.Item value="image" id="image" />
					<Label htmlFor="image">Image</Label>
				</div>
			</Configuration.Section>
		</Configuration.Root>
	)
}

Switch

An iOS style toggle component.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Switch } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<Switch
					checked={config.myValue}
					onCheckedChange={(newValue) => {
						updateConfig({
							myValue: newValue,
						})
					}}
				/>
			</Configuration.Section>
		</Configuration.Root>
	)
}

Slider

A slider component.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Slider } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				 <Slider
					defaultValue={config.myValue || [1]} //! WTF
					max={140}
					step={2}
					onValueChange={(newRange) => {
						const value = newRange[0]
						setPairNameFontSize(fontSize)
						updateConfig({
							myValue: value,
						})
					}}
				/>
			</Configuration.Section>
		</Configuration.Root>
	)
}

Textarea

A textarea component.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Textarea } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<Textarea
					defaultValue={config.myValue}
					placeholder={'Placeholder text'}
					onChange={(e) => {
						updateConfig({
								myValue: e.target.value,
						})
					}}
				/>
			</Configuration.Section>
		</Configuration.Root>
	)
}

Input

An input component.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Input } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<Input
                    defaultValue={1}
                    type="number"
                    required={true}
                    placeholder="4"
                    max={4}
                    min={2}
                    onChange={(e) => {}}
                />
			</Configuration.Section>
		</Configuration.Root>
	)
}

Toggle

A toggle component.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Toggle } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
			
			</Configuration.Section>
		</Configuration.Root>
	)
}

Badge

A badge/chip component.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Badge } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				 <Badge
					variant={'outline'}
					onClick={() => {}}
				>
					My Badge
				</Badge>
			</Configuration.Section>
		</Configuration.Root>
	)
}

Separator

A separator component.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Separator } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<Separator />
			</Configuration.Section>
		</Configuration.Root>
	)
}

Avatar

An avatar component. Can be used both with text and images.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Separator } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<Avatar.Root style={{ width: 36, height: 36 }}>
                    <Avatar.Image src={'https://your-image-url'} />
					<AvatarFallback>FB</AvatarFallback>
                </Avatar.Root>
			</Configuration.Section>
		</Configuration.Root>
	)
}

Modal

A modal component.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Separator } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<Modal.Root>
					<Modal.Trigger asChild={true}>
						<Button>Show Submissions</Button>
					</Modal.Trigger>
					<Modal.Content>
						<Modal.Header>
							<Modal.Title>My Modal Title</Modal.Title>
							<Modal.Description>My Modal Content</Modal.Description>
						</Modal.Header>
					</Modal.Content>
				</Modal.Root>
			</Configuration.Section>
		</Configuration.Root>
	)
}

Table

A table component.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Separator } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
			
			</Configuration.Section>
		</Configuration.Root>
	)
}

Tabs

A tabs component.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Separator } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<Tabs.Root defaultValue="tabOne" className="w-full">
					<Tabs.List className="grid w-full grid-cols-3">
						<Tabs.Trigger value="tabOne">Tab One</Tabs.Trigger>
						<Tabs.Trigger value="tabTwo">Tab Two</Tabs.Trigger>
					</Tabs.List>
					<Tabs.Content value="tabOne">
					</Tabs.Content>
					<Tabs.Content value="tabTwo">
					</Tabs.Content>
				</Tabs.Root>
			</Configuration.Section>
		</Configuration.Root>
	)
}

AlertDialog

An alert dialog component.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Separator } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<AlertDialog.Root>
					<AlertDialog.Trigger>
						<Button>Press to open Alert</Button>
					</AlertDialog.Trigger>
					<AlertDialog.Content>
						<AlertDialog.Header>
							<AlertDialog.Title>Resolution Notice</AlertDialog.Title>
							<AlertDialog.Description className="flex flex-col gap-4">
								Your description
								<div className="flex flex-row items-center gap-2">
									<Checkbox
										id="understood"
										onCheckedChange={(e) => {}}
									/>
									<Label htmlFor="understood">
										Don't show again.
									</Label>
								</div>
							</AlertDialog.Description>
						</AlertDialog.Header>
						<AlertDialog.Footer>
							<AlertDialog.Cancel>Back</AlertDialog.Cancel>
							<AlertDialog.Action
								onClick={() => {}}
							>
								Understood
							</AlertDialog.Action>
						</AlertDialog.Footer>
					</AlertDialog.Content>
				</AlertDialog.Root>
			</Configuration.Section>
		</Configuration.Root>
	)
}

RadioGroup

A radio group component.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { RadioGroup } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
			    <RadioGroup.Root
					defaultValue={coverType}
					className="flex flex-row"
					onValueChange={(val) => {
						const value = val as 'image' | 'text'
						setCoverType(value)
						if (val === 'text' && config.cover.image) {
							updateConfig({
								cover: {
									...config.cover,
									image: null,
								},
							})
						}
					}}
				>
					<div className="flex items-center">
						<RadioGroup.Item value="text" id="text" />
						<Label htmlFor="text">Text</Label>
					</div>
					<div className="flex items-center">
						<RadioGroup.Item value="image" id="image" />
						<Label htmlFor="image">Image</Label>
					</div>
				</RadioGroup.Root>
			</Configuration.Section>
		</Configuration.Root>
	)
}

ToggleGroup

A toggle group component.

Example

import { Configuration } from '@/sdk/inspector'
import { useFrameConfig } from '@/sdk/hooks'
import { Separator } from '@/sdk/components'

export default function Inspector() {
	const [config, updateConfig] = useFrameConfig<Config>()
	
	return (
		<Configuration.Root>
			<Configuration.Section title="My Section">
				<ToggleGroup.Root
					type="single"
					defaultValue={config.pool.primary}
				>
					<ToggleGroup.Item
						value="token0"
						onClick={() => {
							updateConfig({
								pool: {
									...config.pool,
									primary: 'token0',
								},
							})
						}}
					>
						{config.pool.token0.symbol}
					</ToggleGroup.Item>
					<ToggleGroup.Item
						value="token1"
						onClick={() => {
							updateConfig({
								pool: {
									...config.pool,
									primary: 'token1',
								},
							})
						}}
					>
						{config.pool.token1.symbol}
					</ToggleGroup.Item>
				</ToggleGroup.Root>
			</Configuration.Section>
		</Configuration.Root>
	)
}