diff --git a/.env.example b/.env.example
index 9ec6526..2f44493 100644
--- a/.env.example
+++ b/.env.example
@@ -100,3 +100,6 @@ NEXT_PUBLIC_STARGAZE_WEBSITE_URL=https://testnet.publicawesome.dev
NEXT_PUBLIC_BADGES_URL=https://badges.publicawesome.dev
NEXT_PUBLIC_WEBSITE_URL=https://
NEXT_PUBLIC_SYNC_COLLECTIONS_API_URL="https://..."
+
+NEXT_PUBLIC_MEILISEARCH_HOST="https://search.publicawesome.dev"
+NEXT_PUBLIC_MEILISEARCH_API_KEY= "..."
diff --git a/components/Fieldset.tsx b/components/Fieldset.tsx
new file mode 100644
index 0000000..3afa4ac
--- /dev/null
+++ b/components/Fieldset.tsx
@@ -0,0 +1,79 @@
+/* eslint-disable eslint-comments/disable-enable-pair */
+/* eslint-disable no-implicit-coercion */
+/* eslint-disable import/no-default-export */
+/* eslint-disable tsdoc/syntax */
+import type { ReactNode } from 'react'
+
+export interface FieldsetBaseType {
+ /**
+ * The input's required id, used to link the label and input, as well as the error message.
+ */
+ id: string
+ /**
+ * Error message to show input validation.
+ */
+ error?: string
+ /**
+ * Success message to show input validation.
+ */
+ success?: string
+ /**
+ * Label to describe the input.
+ */
+ label?: string | ReactNode
+ /**
+ * Hint to show optional fields or a hint to the user of what to enter in the input.
+ */
+ hint?: string
+}
+
+type FieldsetType = FieldsetBaseType & {
+ children: ReactNode
+}
+
+/**
+ * @name Fieldset
+ * @description A fieldset component, used to share markup for labels, hints, and errors for Input components.
+ *
+ * @example
+ *
+ */
+export default function Fieldset({ label, hint, id, children, error, success }: FieldsetType) {
+ return (
+
+ {!!label && (
+
+
+
+ {typeof hint === 'string' && (
+
+ {hint}
+
+ )}
+
+ )}
+
+ {children}
+
+ {error && (
+
+ )}
+
+ {success && (
+
+ )}
+
+ )
+}
diff --git a/components/Input.tsx b/components/Input.tsx
new file mode 100644
index 0000000..34bf976
--- /dev/null
+++ b/components/Input.tsx
@@ -0,0 +1,173 @@
+/* eslint-disable eslint-comments/disable-enable-pair */
+/* eslint-disable import/no-default-export */
+/* eslint-disable @typescript-eslint/restrict-template-expressions */
+/* eslint-disable jsx-a11y/autocomplete-valid */
+/* eslint-disable tsdoc/syntax */
+import type { PropsOf } from '@headlessui/react/dist/types'
+import type { ReactNode } from 'react'
+import { forwardRef } from 'react'
+import { classNames } from 'utils/css'
+
+import type { FieldsetBaseType } from './Fieldset'
+import Fieldset from './Fieldset'
+import type { TrailingSelectProps } from './TrailingSelect'
+import TrailingSelect from './TrailingSelect'
+
+/**
+ * Shared styles for all input components.
+ */
+export const inputClassNames = {
+ base: [
+ 'block w-full rounded-lg bg-white shadow-sm dark:bg-zinc-900 sm:text-sm',
+ 'text-white placeholder:text-zinc-500 focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-primary-500 focus:ring-0 focus:ring-offset-0',
+ ],
+ valid: 'border-zinc-300 focus:border-zinc-300 dark:border-zinc-800 dark:focus:border-zinc-800',
+ invalid: '!text-red-500 !border-red-500 focus:!border-red-500',
+ success: 'text-green border-green focus:border-green',
+}
+
+type InputProps = Omit & FieldsetBaseType, 'className'> & {
+ directory?: 'true'
+ mozdirectory?: 'true'
+ webkitdirectory?: 'true'
+ leadingAddon?: string
+ trailingAddon?: string
+ trailingAddonIcon?: ReactNode
+ trailingSelectProps?: TrailingSelectProps
+ autoCompleteOff?: boolean
+ preventAutoCapitalizeFirstLetter?: boolean
+ className?: string
+ icon?: JSX.Element
+}
+
+/**
+ * @name Input
+ * @description A standard input component, defaults to the text type.
+ *
+ * @example
+ * // Standard input
+ *
+ *
+ * @example
+ * // Input component with label, placeholder and type email
+ *
+ *
+ * @example
+ * // Input component with label and leading and trailing addons
+ *
+ *
+ * @example
+ * // Input component with label and trailing select
+ * const [trailingSelectValue, trailingSelectValueSet] = useState('USD');
+ *
+ * trailingSelectValueSet(event.target.value),
+ * options: ['USD', 'CAD', 'EUR'],
+ * }}
+ * />
+ */
+const Input = forwardRef(
+ (
+ {
+ error,
+ success,
+ hint,
+ label,
+ leadingAddon,
+ trailingAddon,
+ trailingAddonIcon,
+ trailingSelectProps,
+ id,
+ className,
+ type = 'text',
+ autoCompleteOff = false,
+ preventAutoCapitalizeFirstLetter,
+ icon,
+ ...rest
+ },
+ ref,
+ ) => {
+ const cachedClassNames = classNames(
+ ...inputClassNames.base,
+ className,
+ error ? inputClassNames.invalid : inputClassNames.valid,
+ success ? inputClassNames.success : inputClassNames.valid,
+ leadingAddon && 'pl-7',
+ trailingAddon && 'pr-12',
+ trailingSelectProps && 'pr-16',
+ icon && 'pl-10',
+ )
+
+ const describedBy = [
+ ...(error ? [`${id}-error`] : []),
+ ...(success ? [`${id}-success`] : []),
+ ...(typeof hint === 'string' ? [`${id}-optional`] : []),
+ ...(typeof trailingAddon === 'string' ? [`${id}-addon`] : []),
+ ].join(' ')
+
+ return (
+
+ )
+ },
+)
+
+Input.displayName = 'Input'
+
+export default Input
diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx
index 4e3389e..0eccd53 100644
--- a/components/Sidebar.tsx
+++ b/components/Sidebar.tsx
@@ -108,6 +108,15 @@ export const Sidebar = () => {
>
Collection Actions
+
+ Snapshots
+