@fuf-stack/megapixels 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/Filter/Filter.tsx","../src/Filter/hooks/useFilterValidation.ts","../src/Filter/Subcomponents/ActiveFilters.tsx","../src/Filter/Subcomponents/FiltersContext.tsx","../src/Filter/Subcomponents/AddFilterMenu.tsx","../src/Filter/Subcomponents/FilterModal.tsx","../src/Filter/Subcomponents/SearchInput.tsx","../src/Filter/filters/createFilter.ts","../src/Filter/filters/boolean/Display.tsx","../src/Filter/filters/boolean/Form.tsx","../src/Filter/filters/boolean/schema.ts","../src/Filter/filters/boolean/boolean.ts","../src/Filter/filters/checkboxgroup/Display.tsx","../src/Filter/filters/checkboxgroup/Form.tsx","../src/Filter/filters/checkboxgroup/schema.ts","../src/Filter/filters/checkboxgroup/checkboxgroup.ts","../src/Filter/index.ts"],"sourcesContent":["import type { TVClassName } from '@fuf-stack/pixel-utils';\nimport type { VetoInput } from '@fuf-stack/veto';\nimport type { ReactNode } from 'react';\nimport type { FiltersConfiguration } from './filters/types';\nimport type { SearchConfiguration } from './Subcomponents/SearchInput';\n\nimport { tv, variantsToClassNames } from '@fuf-stack/pixel-utils';\nimport Form from '@fuf-stack/uniform/Form';\n\nimport { useFilterValidation } from './hooks/useFilterValidation';\nimport ActiveFilters from './Subcomponents/ActiveFilters';\nimport AddFilterMenu from './Subcomponents/AddFilterMenu';\nimport FilterModal from './Subcomponents/FilterModal';\nimport { FiltersContextProvider } from './Subcomponents/FiltersContext';\nimport SearchInput from './Subcomponents/SearchInput';\n\n// filter styling variants\nexport const filterVariants = tv({\n slots: {\n // outer wrapper\n base: '',\n // add filter menu trigger button\n addFilterMenuButton: '',\n // add filter menu item\n addFilterMenuItem: '',\n // active filter label\n activeFilterLabel: 'dark:text-foreground h-8 cursor-pointer rounded-md',\n // filter modal body\n filterModalBody: '',\n // filter modal header\n filterModalHeader: 'text-default-700 flex items-center gap-3',\n // filter modal footer\n filterModalFooter: 'justify-between',\n // form element\n form: 'mb-3 flex flex-wrap gap-3',\n // search input field\n searchInput: '',\n // search input wrapper (inner control)\n searchInputWrapper: '',\n // search motion container\n searchMotionDiv: 'flex w-72 gap-2',\n // search show button\n searchShowButton: '',\n // search submit button\n searchSubmitButton: '',\n // search wrapper\n searchWrapper: 'flex items-center',\n },\n});\n\ntype ClassName = TVClassName<typeof filterVariants>;\n\nexport interface FilterValues {\n search?: string;\n filter?: string | Record<string, unknown>;\n}\n\nexport type FilterChildRenderFn = (values: FilterValues) => ReactNode;\n\n/**\n * Filter\n *\n * Controlled, form-driven filter UI.\n *\n * Responsibilities\n * - Derives initial form values from the controlled `values` prop\n * - Builds a composite validation schema from the filter registry (and optional search)\n * - Exposes ergonomic UI: active filters list, add/remove actions, and per-filter modal\n * - Commits changes by invoking the controlled `onChange` callback on submit\n *\n * Structure\n * - Owns an ex-forms `Form` that wraps the entire filter experience\n * - Optionally renders a search input bound to the `search` field\n * - Renders ActiveFilters, AddFilterMenu, and FilterModal inside a shared context\n * - Optionally renders children as a render-prop with the resolved `values`\n */\nexport interface FilterProps {\n /** Optional render-prop that receives the resolved, controlled values */\n children?: FilterChildRenderFn;\n /** CSS class name */\n className?: ClassName;\n /** Configuration of the filter */\n config: {\n /**\n * Declarative filter configuration. Each entry ties a logical name to a\n * registry filter type and (optionally) per-usage config overrides.\n */\n filters: FiltersConfiguration;\n /** Optional configuration for search field */\n search?: SearchConfiguration;\n };\n /** ex-forms form instance name. Defaults to \"filterComponentForm\". */\n formName?: string;\n /** Controlled setter invoked on submit with the next canonical values */\n onChange: (nextValues: FilterValues) => void;\n /** Controlled committed state: the canonical `search` and `filter` values */\n values: FilterValues;\n}\n\n/**\n * Renders the filter UI bound to a single ex-forms `Form`.\n * The form is the source of truth during user interaction; the committed\n * state is controlled by the parent via `values`/`onChange`.\n */\nconst Filter = ({\n children = undefined,\n className = undefined,\n config,\n formName = 'filterComponentForm',\n onChange,\n values,\n}: FilterProps) => {\n // Submit handler: map form state back into the controlled `values` shape\n const handleSubmit = (nextValues: Record<string, unknown>) => {\n onChange(nextValues as FilterValues);\n };\n\n // Build validation schema for all configured filters (and optional search)\n const validation = useFilterValidation(\n config.filters,\n Boolean(config.search),\n );\n\n // validate controlled values are valid\n const { data: valuesValidated } = validation.validate(values as VetoInput);\n\n // classNames from slots\n const variants = filterVariants();\n const classNames = variantsToClassNames(variants, className, 'base');\n\n return (\n <div className={classNames.base}>\n {/*\n Uniform Form wrapper\n - initialValues derive from controlled props (with optional defaults)\n - validation is built from the registry for all configured filters\n - onSubmit maps form values back into values/onChange\n */}\n <Form\n className={classNames.form}\n // disable debug mode for now\n debug={{ disable: true }}\n initialValues={valuesValidated ?? {}}\n name={formName}\n onSubmit={handleSubmit}\n validation={validation}\n >\n {/* Render search if search config is provided */}\n {config.search ? (\n <SearchInput\n config={config.search}\n classNames={{\n searchInput: classNames.searchInput,\n searchInputWrapper: classNames.searchInputWrapper,\n searchMotionDiv: classNames.searchMotionDiv,\n searchShowButton: classNames.searchShowButton,\n searchSubmitButton: classNames.searchSubmitButton,\n searchWrapper: classNames.searchWrapper,\n }}\n />\n ) : null}\n {/*\n FiltersContextProvider exposes a minimal API for the UI layer:\n - activeFilters/unusedFilters by name\n - helpers to get merged config, value, components, and field names\n - methods to add/remove filters and show/close the modal\n */}\n <FiltersContextProvider config={config.filters}>\n <ActiveFilters className={classNames.activeFilterLabel} />\n <AddFilterMenu\n classNames={{\n addFilterMenuButton: classNames.addFilterMenuButton,\n addFilterMenuItem: classNames.addFilterMenuItem,\n }}\n />\n <FilterModal\n classNames={{\n body: classNames.filterModalBody,\n footer: classNames.filterModalFooter,\n header: classNames.filterModalHeader,\n }}\n />\n </FiltersContextProvider>\n </Form>\n {/* Children can consume derived search string and parsed filter object */}\n {children?.(valuesValidated ?? {})}\n </div>\n );\n};\n\nexport default Filter;\n","import type { VetoTypeAny } from '@fuf-stack/veto';\nimport type { FilterInstance } from '../filters/types';\n\nimport { useMemo } from 'react';\n\nimport { object, string, stringToJSON, veto } from '@fuf-stack/veto';\n\n/** Validation function return type alias. */\ntype ValidationSchema = ReturnType<typeof veto>;\n\n/**\n * useFilterValidation\n *\n * Builds a composite validation schema from all provided filter definitions\n * under \"filter\" and optionally includes a \"search\" string field.\n * Memoized by inputs.\n */\nexport const useFilterValidation = (\n filters: FilterInstance<unknown, unknown>[],\n withSearch?: boolean,\n) => {\n return useMemo<ValidationSchema>(() => {\n let validationObject: Record<string, VetoTypeAny> = {};\n let filterValidation: Record<string, VetoTypeAny> = {};\n\n filters.forEach((f) => {\n filterValidation = {\n ...filterValidation,\n [f.name]: f.validation(f.config),\n };\n });\n\n validationObject = {\n filter: stringToJSON()\n .pipe(object(filterValidation))\n .or(object(filterValidation))\n .optional(),\n ...(withSearch\n ? { search: string({ min: 0 }).nullable().optional() }\n : {}),\n };\n\n return veto(validationObject);\n }, [filters, withSearch]);\n};\n\nexport default useFilterValidation;\n","import Label from '@fuf-stack/pixels/Label';\n\nimport { useFilters } from './FiltersContext';\n\n/**\n * ActiveFilters\n *\n * Shows the list of currently applied filters as clickable chips that open\n * the edit modal. Each chip can be removed via its close action.\n */\ninterface ActiveFiltersProps {\n /** CSS class name to apply to each label */\n className?: string;\n}\n\nconst ActiveFilters = ({ className = undefined }: ActiveFiltersProps) => {\n const {\n activeFilters,\n getFilterValueByName,\n getFilterInstanceByName,\n hasError,\n removeFilter,\n showFilterModal,\n } = useFilters();\n return (\n <>\n {activeFilters.map((name) => {\n const instance = getFilterInstanceByName(name);\n const value = getFilterValueByName(name);\n\n // get the display component from the instance\n const DisplayComponent = instance.components.Display;\n\n return (\n <button\n key={name}\n aria-label={`Open ${name} filter`}\n type=\"button\"\n onClick={() => {\n showFilterModal(name);\n }}\n >\n <Label\n className={className}\n color={hasError(name) ? 'danger' : 'primary'}\n variant=\"flat\"\n onClose={() => {\n removeFilter(name);\n }}\n >\n {instance.icon}\n <DisplayComponent config={instance.config} value={value} />\n </Label>\n </button>\n );\n })}\n </>\n );\n};\n\nexport default ActiveFilters;\n","import type { ReactNode } from 'react';\nimport type { FilterInstance, FiltersConfiguration } from '../filters/types';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { useFormContext } from '@fuf-stack/uniform/hooks';\n\ninterface FiltersContextValue {\n /** Active filters (names only) */\n activeFilters: string[];\n /** Seed default value and open modal for a filter usage. */\n addFilter: (name: string) => void;\n /** Close the modal. */\n closeFilterModal: () => void;\n /** Build fully-qualified form field path for a filter name */\n getFilterFormFieldName: (name: string) => string;\n /** Get current form value for a given filter name */\n getFilterValueByName: (name: string) => unknown;\n /** Get filter instance by name */\n getFilterInstanceByName: (name: string) => FilterInstance<unknown, unknown>;\n /** Validation helper for a specific filter field. */\n hasError: (name: string) => boolean;\n /** Name of the current filter that has its modal open */\n modalFilterName: string | undefined;\n /** Remove a filter from the form. */\n removeFilter: (name: string) => void;\n /** Open the modal for a given filter name. */\n showFilterModal: (name: string) => void;\n /** Filters that are not active (names only) */\n unusedFilters: string[];\n}\n\n/**\n * FiltersContext\n *\n * Central state for the filter UI with a clear boundary:\n * - The parent component controls committed filter values (via value/onChange)\n * - The form acts as an edit buffer (used by the modal)\n *\n * Design:\n * - activeFilters/unusedFilters are names-only and derived from the controlled\n * form state\n * - getFilterInstanceByName gives access to the concrete registry entry to\n * retrieve the correct Form/Display components\n * - add seeds defaults in the form and opens the modal\n * - remove un-registers the form field; if the removed filter is currently\n * open in the modal, the modal is closed without rollback\n * - on a new successful form submit (Apply), the modal closes without rollback\n * by subscribing to ex-forms submit state\n */\nconst FiltersContext = createContext<FiltersContextValue | undefined>(\n undefined,\n);\n\nexport const FiltersContextProvider = ({\n children,\n config,\n}: {\n children: ReactNode;\n config: FiltersConfiguration;\n}) => {\n // ex-forms integration:\n // - setValue/unregister/getFieldState: core helpers to manipulate and validate fields\n // - formState: we subscribe to submit-success to auto-close the modal after Apply\n const {\n formState,\n getFieldState,\n setValue,\n triggerSubmit,\n unregister,\n watch,\n } = useFormContext();\n\n /**\n * currentModalFilter\n *\n * Single source of truth for the filter edit modal and its rollback snapshot.\n * - name: which filter's modal is currently open (null when closed)\n * - hadValue/previousValue: snapshot of the controlled value taken when the\n * modal is opened; used to restore state if the user cancels/closes without\n * applying.\n *\n * Lifecycle semantics:\n * - showFilterModal(name): capture snapshot (current controlled value) and open\n * the modal for that filter.\n * - closeFilterModal(): if a snapshot exists, roll back un-applied edits by\n * restoring the previous value (setValue) or removing the field (unregister)\n * when it did not exist before; then clear currentModalFilter.\n * - On successful submit (Apply): close and clear currentModalFilter WITHOUT rollback\n * so edits remain committed.\n * - removeFilter(name): unregisters the field; when removing the filter that is\n * currently open, close the modal WITHOUT rollback (since removal is explicit).\n */\n const [currentModalFilter, setCurrentModalFilter] = useState<{\n name: string;\n hadValue: boolean;\n previousValue: unknown;\n } | null>(null);\n\n // Read current filter values from the form as the live edit buffer\n const filterValue = watch('filter', {});\n\n /**\n * getFilterFormFieldName\n *\n * Returns the fully-qualified field path for a given filter name,\n * e.g., `${filterUrlParam}.status`.\n */\n const getFilterFormFieldName = useCallback((name: string) => {\n return `filter.${name}`;\n }, []);\n\n /**\n * getFilterValueByName\n *\n * Returns the committed value for a filter from the controlled state.\n */\n const getFilterValueByName = useCallback(\n (name: string) => {\n return (filterValue as Record<string, unknown>)[name];\n },\n [filterValue],\n );\n\n /** Open the filter edit modal for the given filter name. */\n const showFilterModal = useCallback(\n (name: string) => {\n const prev = getFilterValueByName(name);\n setCurrentModalFilter({\n name,\n hadValue: typeof prev !== 'undefined',\n previousValue: prev,\n });\n },\n [getFilterValueByName],\n );\n\n /** Close the filter edit modal. Rollback un-applied edits to controlled state. */\n const closeFilterModal = useCallback(() => {\n if (currentModalFilter?.name) {\n const fieldName = getFilterFormFieldName(currentModalFilter.name);\n // if the filter had a value, set it back to the previous value,\n // otherwise unregister the field\n if (currentModalFilter.hadValue) {\n setValue(fieldName, currentModalFilter.previousValue);\n } else {\n unregister(fieldName);\n }\n }\n setCurrentModalFilter(null);\n }, [getFilterFormFieldName, currentModalFilter, setValue, unregister]);\n\n /**\n * Auto-close on submit success\n *\n * Close the modal only on new successful submissions. We track the last\n * submitCount and only react when it changes AND the form reports a\n * successful submit. This prevents closing when `isSubmitSuccessful` remains\n * true without a new submit event.\n */\n const lastSubmitCountRef = useRef<number>(0);\n useEffect(() => {\n if (\n formState.submitCount !== lastSubmitCountRef.current &&\n formState.isSubmitSuccessful\n ) {\n // On successful submit, close without rollback\n setCurrentModalFilter(null);\n }\n lastSubmitCountRef.current = formState.submitCount;\n }, [\n formState.submitCount,\n formState.isSubmitSuccessful,\n setCurrentModalFilter,\n ]);\n\n /**\n * activeFilters\n *\n * Filter names derived from the controlled form state. A filter is considered\n * active when a field exists at `filter.<name>`. Newly added filters become\n * active immediately (seeded default), and will be rolled back on cancel.\n */\n const activeFilters = useMemo(() => {\n return config\n .filter((f) => {\n return Object.hasOwn(filterValue ?? {}, f.name);\n })\n .map((f) => {\n return f.name;\n });\n }, [config, filterValue]);\n\n /**\n * unusedFilters\n *\n * Complement of activeFilters (names without a corresponding `filter.<name>`\n * field in the controlled form state).\n */\n const unusedFilters = useMemo(() => {\n return config\n .filter((f) => {\n return !Object.hasOwn(filterValue ?? {}, f.name);\n })\n .map((f) => {\n return f.name;\n });\n }, [config, filterValue]);\n\n /**\n * getRegistryFilterByName\n *\n * Looks up the concrete registry entry for a filter by name, enabling access\n * to typed Form/Display components and other registry-level metadata.\n */\n const getFilterInstanceByName = useCallback(\n (name: string) => {\n return config.find((f) => {\n return f.name === name;\n }) as FilterInstance<unknown, unknown>;\n },\n [config],\n );\n\n /**\n * addFilter\n *\n * Seeds the filter with its registry default value inside the form and opens\n * the modal for immediate editing. No URL writes happen here.\n */\n const addFilter = useCallback(\n (name: string) => {\n const inst = getFilterInstanceByName(name);\n showFilterModal(name);\n setValue(getFilterFormFieldName(name), inst.defaultValue);\n },\n [\n getFilterFormFieldName,\n getFilterInstanceByName,\n setValue,\n showFilterModal,\n ],\n );\n\n /**\n * removeFilter\n *\n * Unregisters the filter field from the form. This immediately removes the\n * filter from the active list since derived state watches the form. It\n * closes the modal without rollback if the removed filter is currently open.\n */\n const removeFilter = useCallback(\n (name: string) => {\n // unregister form field\n unregister(getFilterFormFieldName(name));\n // close filter modal if open\n if (currentModalFilter?.name === name) {\n // Explicit removal: close without rollback\n setCurrentModalFilter(null);\n }\n // trigger form submit (to update filter state)\n triggerSubmit();\n },\n [\n getFilterFormFieldName,\n currentModalFilter,\n setCurrentModalFilter,\n triggerSubmit,\n unregister,\n ],\n );\n\n /**\n * hasError\n *\n * Helper that checks the ex-forms field state for a specific filter and\n * reports whether the field is currently invalid.\n */\n const hasError = useCallback(\n (name: string) => {\n return getFieldState(getFilterFormFieldName(name)).invalid;\n },\n [getFieldState, getFilterFormFieldName],\n );\n\n const contextValue: FiltersContextValue = useMemo(() => {\n return {\n activeFilters,\n addFilter,\n closeFilterModal,\n getFilterFormFieldName,\n getFilterValueByName,\n getFilterInstanceByName,\n hasError,\n modalFilterName: currentModalFilter?.name,\n removeFilter,\n showFilterModal,\n unusedFilters,\n };\n }, [\n activeFilters,\n addFilter,\n closeFilterModal,\n getFilterFormFieldName,\n getFilterValueByName,\n getFilterInstanceByName,\n hasError,\n currentModalFilter,\n removeFilter,\n showFilterModal,\n unusedFilters,\n ]);\n\n return (\n <FiltersContext.Provider value={contextValue}>\n {children}\n </FiltersContext.Provider>\n );\n};\n\n/**\n * useFilters\n *\n * Convenience hook to consume the FiltersContext. Throws a descriptive error\n * when used outside of a FiltersContextProvider to make integration mistakes\n * obvious during development.\n */\nexport const useFilters = (): FiltersContextValue => {\n const ctx = useContext(FiltersContext);\n if (!ctx) {\n throw new Error('useFilters must be used within FiltersContextProvider');\n }\n return ctx;\n};\n\nexport default FiltersContext;\n","import { FaSliders } from 'react-icons/fa6';\n\nimport Menu from '@fuf-stack/pixels/Menu';\n\nimport { useFilters } from './FiltersContext';\n\ninterface AddFilterMenuProps {\n /** CSS class name map for slots */\n classNames?: Partial<{\n addFilterMenuItem: string;\n addFilterMenuButton: string;\n }>;\n}\n\n/**\n * AddFilterMenu\n *\n * Renders a menu trigger that opens a list of addable filters. Selecting an\n * item triggers the parent to seed a default value and open the modal.\n */\nconst AddFilterMenu = ({ classNames = {} }: AddFilterMenuProps) => {\n const { unusedFilters, addFilter, getFilterInstanceByName } = useFilters();\n\n const menuItems = unusedFilters.map((name) => {\n const instance = getFilterInstanceByName(name);\n const config = instance.config as { text?: string };\n const label = config?.text ?? name;\n return {\n key: name,\n icon: instance.icon,\n label,\n onClick: () => {\n addFilter(name);\n },\n };\n });\n\n return (\n <Menu\n isDisabled={!menuItems.length}\n items={menuItems}\n placement=\"bottom-start\"\n className={{\n item: classNames.addFilterMenuItem,\n trigger: classNames.addFilterMenuButton,\n }}\n triggerButtonProps={{\n 'aria-label': 'Add Filter',\n disableRipple: true,\n size: 'sm',\n variant: 'bordered',\n }}\n >\n <FaSliders />\n Filter\n </Menu>\n );\n};\n\nexport default AddFilterMenu;\n","import { Suspense } from 'react';\nimport { PiSlidersHorizontalBold } from 'react-icons/pi';\n\nimport Button from '@fuf-stack/pixels/Button';\nimport Modal from '@fuf-stack/pixels/Modal';\nimport SubmitButton from '@fuf-stack/uniform/SubmitButton';\n\nimport { useFilters } from './FiltersContext';\n\ninterface FilterModalProps {\n classNames?: Partial<{\n header: string;\n footer: string;\n body: string;\n }>;\n}\n\nconst FilterModal = ({ classNames = {} }: FilterModalProps) => {\n const {\n closeFilterModal,\n getFilterFormFieldName,\n getFilterInstanceByName,\n modalFilterName,\n removeFilter,\n } = useFilters();\n\n // don't render if no filter is open\n if (!modalFilterName) {\n return null;\n }\n\n const instance = getFilterInstanceByName(modalFilterName);\n const config = instance.config as { text?: string };\n\n // get the form component from the instance\n const FormComponent = instance.components.Form;\n\n return (\n <Modal\n isOpen\n onClose={closeFilterModal}\n className={{\n body: classNames.body,\n footer: classNames.footer,\n header: classNames.header,\n }}\n footer={\n <>\n <Button\n color=\"danger\"\n variant=\"flat\"\n onClick={() => {\n removeFilter(modalFilterName);\n }}\n >\n Remove\n </Button>\n <SubmitButton>Apply Filter</SubmitButton>\n </>\n }\n header={\n <>\n {instance.icon ?? <PiSlidersHorizontalBold />}\n <div>{`${config?.text ?? modalFilterName} Filter`}</div>\n </>\n }\n >\n <Suspense>\n <FormComponent\n config={config}\n fieldName={getFilterFormFieldName(modalFilterName)}\n />\n </Suspense>\n </Modal>\n );\n};\n\nexport default FilterModal;\n","import { useState } from 'react';\nimport { FaSearch } from 'react-icons/fa';\n\nimport { motion } from '@fuf-stack/pixel-motion';\nimport Button from '@fuf-stack/pixels/Button';\nimport { useFormContext } from '@fuf-stack/uniform/hooks';\nimport Input from '@fuf-stack/uniform/Input';\nimport SubmitButton from '@fuf-stack/uniform/SubmitButton';\n\nexport type SearchConfiguration =\n | boolean\n | {\n /** Placeholder shown in the search input */\n placeholder?: string;\n };\n\ninterface SearchInputProps {\n /** Slots class names passed from parent variants */\n classNames?: Partial<{\n searchWrapper: string;\n searchShowButton: string;\n searchMotionDiv: string;\n searchInput: string;\n searchInputWrapper: string;\n searchSubmitButton: string;\n }>;\n /** Search configuration */\n config: SearchConfiguration;\n}\n\n/**\n * SearchInput\n *\n * By default renders only a search button. When clicked, the text input animates in\n * and a trailing submit button is shown.\n */\nconst SearchInput = ({ classNames = {}, config }: SearchInputProps) => {\n const { formState, setFocus, triggerSubmit } = useFormContext();\n\n // Auto-open if there is an initial or externally set search value\n const isInitiallyVisible = !!formState?.defaultValues?.search;\n const [isVisible, setIsVisible] = useState(isInitiallyVisible);\n\n const placeholder =\n typeof config === 'object' ? config.placeholder : undefined;\n\n return (\n <div className={classNames.searchWrapper}>\n {!isVisible && (\n <Button\n ariaLabel=\"Show search input\"\n className={classNames.searchShowButton}\n icon={<FaSearch />}\n size=\"sm\"\n variant=\"bordered\"\n onClick={() => {\n setIsVisible(true);\n }}\n />\n )}\n {isVisible ? (\n <motion.div\n key=\"search-input\"\n animate={{ opacity: 1 }}\n className={classNames.searchMotionDiv}\n initial={{ opacity: 0.5 }}\n onAnimationComplete={() => {\n // if the input was not initially visible, focus it\n if (!isInitiallyVisible) {\n setFocus('search');\n }\n }}\n transition={{\n // if the input was not initially visible, animate in\n duration: isInitiallyVisible ? 0 : 0.3,\n ease: 'circOut',\n }}\n >\n <Input\n clearable\n debounceDelay={0}\n name=\"search\"\n placeholder={placeholder}\n size=\"sm\"\n className={{\n input: classNames.searchInput,\n inputWrapper: classNames.searchInputWrapper,\n }}\n // submit on clear\n onClear={() => {\n triggerSubmit();\n }}\n />\n <SubmitButton\n ariaLabel=\"Trigger search\"\n // eslint-disable-next-line react/no-children-prop\n children={null}\n className={classNames.searchSubmitButton}\n color=\"primary\"\n icon={<FaSearch />}\n size=\"sm\"\n />\n </motion.div>\n ) : null}\n </div>\n );\n};\n\nexport default SearchInput;\n","import type { FilterDefinition, FilterFactory } from './types';\n\n/**\n * createFilter\n *\n * Builds a filter factory from a static FilterDefinition. The returned factory\n * accepts a usage descriptor (name/icon and optional partial config) and\n * produces a concrete FilterInstance with:\n * - merged config (shallow: definition.defaults.config overlaid by overrides)\n * - Form/Display components\n * - validate function (forwarded from the definition)\n * - defaultValue (forwarded from the definition)\n * - name and icon for UI integration\n *\n * @typeParam Config - Configuration object shape for the filter\n * @typeParam Value - Runtime value type for the filter\n * @param definition - Static description of the filter (components, defaults, validate)\n * @returns FilterFactory that creates FilterInstance<Config, Value>\n */\nconst createFilter = <Config, Value>(\n definition: FilterDefinition<Config, Value>,\n): FilterFactory<Config, Value> => {\n return ({ name, icon, config }) => {\n return {\n components: definition.components,\n config: { ...definition.defaults.config, ...(config ?? {}) } as Config,\n defaultValue: definition.defaults.value,\n icon,\n name,\n validation: definition.validation,\n };\n };\n};\n\nexport default createFilter;\n","import type { FilterDisplayProps } from '../types';\nimport type { Config, Value } from './schema';\n\nconst Display = ({\n value,\n config: { text, textPrefix, textNoWord },\n}: FilterDisplayProps<Config, Value>) => {\n if (typeof value === 'boolean') {\n return (\n <>\n {value ? textPrefix : `${textPrefix} ${textNoWord ?? 'no'}`} {text}\n </>\n );\n }\n return <>{`${text}...`}</>;\n};\n\nexport default Display;\n","import type { FilterFormProps } from '../types';\nimport type { Config } from './schema';\n\nimport Switch from '@fuf-stack/uniform/Switch';\n\nconst Form = ({\n fieldName,\n config: { text, textPrefix },\n}: FilterFormProps<Config>) => {\n return <Switch label={`${textPrefix} ${text}`} name={fieldName} />;\n};\n\nexport default Form;\n","import type { vInfer } from '@fuf-stack/veto';\n\nimport { boolean, object, string } from '@fuf-stack/veto';\n\n/** configuration of the filter */\nexport const config = object({\n /**\n * Human‑readable label used in the UI (e.g. in the chip and modal header).\n * Examples: \"Magical\", \"Haunted\"\n */\n text: string(),\n /**\n * Optional word shown before the label when building sentence‑like chips.\n * Examples: \"is\" → \"is Magical\"\n */\n textPrefix: string().optional(),\n /**\n * Optional negation word used when a boolean value is false.\n * Examples: \"not\" → \"is not Magical\"\n */\n textNoWord: string().optional(),\n});\n\n/** validate the filter value */\nexport const validate = (_config?: Config) => {\n return boolean().optional();\n};\n\nexport type Config = vInfer<typeof config>;\nexport type Value = vInfer<ReturnType<typeof validate>>;\n","/* eslint-disable import-x/prefer-default-export */\n\nimport type { Config, Value } from './schema';\n\nimport createFilter from '../createFilter';\nimport Display from './Display';\nimport Form from './Form';\nimport { validate } from './schema';\n\nexport const boolean = createFilter<Config, Value>({\n components: { Display, Form },\n defaults: {\n value: true,\n config: { text: 'Active', textPrefix: 'is', textNoWord: 'no' },\n },\n validation: validate,\n});\n","import type { FilterDisplayProps } from '../types';\nimport type { Config, Value } from './schema';\n\nconst Display = ({\n value,\n config: { text, options },\n}: FilterDisplayProps<Config, Value>) => {\n if (value && value.length > 0) {\n const labels = value\n .map((val) => {\n return (\n options.find((op) => {\n return op.value === val;\n })?.label ?? val\n );\n })\n .join(' ');\n return `${text} is ${labels}`;\n }\n return `${text} is ...`;\n};\n\nexport default Display;\n","import type { FilterFormProps } from '../types';\nimport type { Config } from './schema';\n\nimport CheckboxGroup from '@fuf-stack/uniform/CheckboxGroup';\n\nconst Form = ({ fieldName, config }: FilterFormProps<Config>) => {\n return <CheckboxGroup name={fieldName} options={config.options} />;\n};\n\nexport default Form;\n","import type { vInfer } from '@fuf-stack/veto';\n\nimport { array, object, refineArray, string } from '@fuf-stack/veto';\n\n/** configuration of the filter */\nexport const config = object({\n /**\n * Human‑readable label used in the UI (e.g. label and modal header).\n * Example: \"Snacks\", \"Mood\"\n */\n text: string(),\n /**\n * Options rendered as multiple checkboxes. Each option needs a `label`\n * (what the user sees) and a `value` (what is written into the form state).\n */\n options: array(object({ label: string(), value: string() })),\n});\n\n/** validate the filter value */\nexport const validate = (cfg?: Config) => {\n return refineArray(array(string()).optional())({\n unique: true,\n custom: (values, ctx) => {\n if (!cfg) {\n return;\n }\n values.forEach((value) => {\n if (\n !cfg.options.find((option) => {\n return option?.value === value;\n })\n ) {\n ctx.addIssue({\n code: 'custom',\n message: `Invalid value: ${value}`,\n });\n }\n });\n },\n });\n};\n\nexport type Config = vInfer<typeof config>;\nexport type Value = vInfer<ReturnType<typeof validate>>;\n","/* eslint-disable import-x/prefer-default-export */\n\nimport type { Config, Value } from './schema';\n\nimport createFilter from '../createFilter';\nimport Display from './Display';\nimport Form from './Form';\nimport { validate } from './schema';\n\nexport const checkboxgroup = createFilter<Config, Value>({\n components: { Display, Form },\n defaults: { value: [], config: { text: 'Options', options: [] } },\n validation: validate,\n});\n","import Filter from './Filter';\nimport { boolean } from './filters/boolean/boolean';\nimport { checkboxgroup } from './filters/checkboxgroup/checkboxgroup';\n\n// export types\nexport type * from './filters/types';\n\n// export helpers\nexport { default as createFilter } from './filters/createFilter';\n\n// export all filters\nexport const filters = {\n boolean,\n checkboxgroup,\n};\n\nexport default Filter;\n"],"mappings":"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","names":["filters","useMemo","config","jsx","jsx","jsxs","config","Fragment","jsx","jsxs","config","useState","Button","useFormContext","SubmitButton","jsx","jsxs","config","jsx","jsxs","config","config","Fragment","jsx","jsxs","jsx","Form","object","string","boolean","Display","Display_default","jsx","Form","config","Form_default","object","string","config","validate","Display_default","Form_default","validate","boolean","Filter_default"]}
@@ -117,7 +117,7 @@ var FiltersContextProvider = ({
117
117
  ]);
118
118
  const activeFilters = _react.useMemo.call(void 0, () => {
119
119
  return config3.filter((f) => {
120
- return Object.hasOwn(filterValue || {}, f.name);
120
+ return Object.hasOwn(filterValue != null ? filterValue : {}, f.name);
121
121
  }).map((f) => {
122
122
  return f.name;
123
123
  });
@@ -211,7 +211,7 @@ var useFilters = () => {
211
211
 
212
212
  // src/Filter/Subcomponents/ActiveFilters.tsx
213
213
 
214
- var ActiveFilters = () => {
214
+ var ActiveFilters = ({ className = void 0 }) => {
215
215
  const {
216
216
  activeFilters,
217
217
  getFilterValueByName,
@@ -230,12 +230,12 @@ var ActiveFilters = () => {
230
230
  "aria-label": `Open ${name} filter`,
231
231
  type: "button",
232
232
  onClick: () => {
233
- return void showFilterModal(name);
233
+ showFilterModal(name);
234
234
  },
235
235
  children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
236
236
  _Label2.default,
237
237
  {
238
- className: "dark:text-foreground h-8 cursor-pointer",
238
+ className,
239
239
  color: hasError(name) ? "danger" : "primary",
240
240
  variant: "flat",
241
241
  onClose: () => {
@@ -243,7 +243,7 @@ var ActiveFilters = () => {
243
243
  },
244
244
  children: [
245
245
  instance.icon,
246
- DisplayComponent ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, DisplayComponent, { config: instance.config, value }) : null
246
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, DisplayComponent, { config: instance.config, value })
247
247
  ]
248
248
  }
249
249
  )
@@ -256,10 +256,9 @@ var ActiveFilters_default = ActiveFilters;
256
256
 
257
257
  // src/Filter/Subcomponents/AddFilterMenu.tsx
258
258
  var _fa6 = require('react-icons/fa6');
259
-
260
259
  var _Menu = require('@fuf-stack/pixels/Menu'); var _Menu2 = _interopRequireDefault(_Menu);
261
260
 
262
- var AddFilterMenu = ({ className = void 0 }) => {
261
+ var AddFilterMenu = ({ classNames = {} }) => {
263
262
  const { unusedFilters, addFilter, getFilterInstanceByName } = useFilters();
264
263
  const menuItems = unusedFilters.map((name) => {
265
264
  var _a;
@@ -278,12 +277,16 @@ var AddFilterMenu = ({ className = void 0 }) => {
278
277
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
279
278
  _Menu2.default,
280
279
  {
281
- className: _pixelutils.cn.call(void 0, className),
282
280
  isDisabled: !menuItems.length,
283
281
  items: menuItems,
284
282
  placement: "bottom-start",
283
+ className: {
284
+ item: classNames.addFilterMenuItem,
285
+ trigger: classNames.addFilterMenuButton
286
+ },
285
287
  triggerButtonProps: {
286
288
  "aria-label": "Add Filter",
289
+ disableRipple: true,
287
290
  size: "sm",
288
291
  variant: "bordered"
289
292
  },
@@ -303,7 +306,7 @@ var _Button = require('@fuf-stack/pixels/Button'); var _Button2 = _interopRequir
303
306
  var _Modal = require('@fuf-stack/pixels/Modal'); var _Modal2 = _interopRequireDefault(_Modal);
304
307
  var _SubmitButton = require('@fuf-stack/uniform/SubmitButton'); var _SubmitButton2 = _interopRequireDefault(_SubmitButton);
305
308
 
306
- var FilterModal = () => {
309
+ var FilterModal = ({ classNames = {} }) => {
307
310
  var _a, _b;
308
311
  const {
309
312
  closeFilterModal,
@@ -324,8 +327,9 @@ var FilterModal = () => {
324
327
  isOpen: true,
325
328
  onClose: closeFilterModal,
326
329
  className: {
327
- footer: "justify-between",
328
- header: "text-default-700 flex items-center gap-3"
330
+ body: classNames.body,
331
+ footer: classNames.footer,
332
+ header: classNames.header
329
333
  },
330
334
  footer: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
331
335
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -363,21 +367,21 @@ var _fa = require('react-icons/fa');
363
367
  var _pixelmotion = require('@fuf-stack/pixel-motion');
364
368
 
365
369
 
366
-
367
370
  var _Input = require('@fuf-stack/uniform/Input'); var _Input2 = _interopRequireDefault(_Input);
368
371
 
369
372
 
370
- var SearchInput = ({ className = void 0, config: config3 }) => {
373
+ var SearchInput = ({ classNames = {}, config: config3 }) => {
371
374
  var _a;
372
375
  const { formState, setFocus, triggerSubmit } = _hooks.useFormContext.call(void 0, );
373
376
  const isInitiallyVisible = !!((_a = formState == null ? void 0 : formState.defaultValues) == null ? void 0 : _a.search);
374
377
  const [isVisible, setIsVisible] = _react.useState.call(void 0, isInitiallyVisible);
375
378
  const placeholder = typeof config3 === "object" ? config3.placeholder : void 0;
376
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _pixelutils.cn.call(void 0, "flex items-center", className), children: [
379
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: classNames.searchWrapper, children: [
377
380
  !isVisible && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
378
381
  _Button2.default,
379
382
  {
380
- "aria-label": "Show search input",
383
+ ariaLabel: "Show search input",
384
+ className: classNames.searchShowButton,
381
385
  icon: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _fa.FaSearch, {}),
382
386
  size: "sm",
383
387
  variant: "bordered",
@@ -390,7 +394,7 @@ var SearchInput = ({ className = void 0, config: config3 }) => {
390
394
  _pixelmotion.motion.div,
391
395
  {
392
396
  animate: { opacity: 1 },
393
- className: "flex w-72 gap-2",
397
+ className: classNames.searchMotionDiv,
394
398
  initial: { opacity: 0.5 },
395
399
  onAnimationComplete: () => {
396
400
  if (!isInitiallyVisible) {
@@ -411,6 +415,10 @@ var SearchInput = ({ className = void 0, config: config3 }) => {
411
415
  name: "search",
412
416
  placeholder,
413
417
  size: "sm",
418
+ className: {
419
+ input: classNames.searchInput,
420
+ inputWrapper: classNames.searchInputWrapper
421
+ },
414
422
  onClear: () => {
415
423
  triggerSubmit();
416
424
  }
@@ -419,7 +427,9 @@ var SearchInput = ({ className = void 0, config: config3 }) => {
419
427
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
420
428
  _SubmitButton2.default,
421
429
  {
430
+ ariaLabel: "Trigger search",
422
431
  children: null,
432
+ className: classNames.searchSubmitButton,
423
433
  color: "primary",
424
434
  icon: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _fa.FaSearch, {}),
425
435
  size: "sm"
@@ -435,6 +445,38 @@ var SearchInput_default = SearchInput;
435
445
 
436
446
  // src/Filter/Filter.tsx
437
447
 
448
+ var filterVariants = _pixelutils.tv.call(void 0, {
449
+ slots: {
450
+ // outer wrapper
451
+ base: "",
452
+ // add filter menu trigger button
453
+ addFilterMenuButton: "",
454
+ // add filter menu item
455
+ addFilterMenuItem: "",
456
+ // active filter label
457
+ activeFilterLabel: "dark:text-foreground h-8 cursor-pointer rounded-md",
458
+ // filter modal body
459
+ filterModalBody: "",
460
+ // filter modal header
461
+ filterModalHeader: "text-default-700 flex items-center gap-3",
462
+ // filter modal footer
463
+ filterModalFooter: "justify-between",
464
+ // form element
465
+ form: "mb-3 flex flex-wrap gap-3",
466
+ // search input field
467
+ searchInput: "",
468
+ // search input wrapper (inner control)
469
+ searchInputWrapper: "",
470
+ // search motion container
471
+ searchMotionDiv: "flex w-72 gap-2",
472
+ // search show button
473
+ searchShowButton: "",
474
+ // search submit button
475
+ searchSubmitButton: "",
476
+ // search wrapper
477
+ searchWrapper: "flex items-center"
478
+ }
479
+ });
438
480
  var Filter = ({
439
481
  children = void 0,
440
482
  className = void 0,
@@ -451,22 +493,54 @@ var Filter = ({
451
493
  Boolean(config3.search)
452
494
  );
453
495
  const { data: valuesValidated } = validation.validate(values);
454
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
496
+ const variants = filterVariants();
497
+ const classNames = _pixelutils.variantsToClassNames.call(void 0, variants, className, "base");
498
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: classNames.base, children: [
455
499
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
456
500
  _Form2.default,
457
501
  {
458
- className: _pixelutils.cn.call(void 0, "mb-3 flex flex-wrap gap-3", className),
502
+ className: classNames.form,
459
503
  debug: { disable: true },
460
504
  initialValues: valuesValidated != null ? valuesValidated : {},
461
505
  name: formName,
462
506
  onSubmit: handleSubmit,
463
507
  validation,
464
508
  children: [
465
- config3.search ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, SearchInput_default, { config: config3.search }) : null,
509
+ config3.search ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
510
+ SearchInput_default,
511
+ {
512
+ config: config3.search,
513
+ classNames: {
514
+ searchInput: classNames.searchInput,
515
+ searchInputWrapper: classNames.searchInputWrapper,
516
+ searchMotionDiv: classNames.searchMotionDiv,
517
+ searchShowButton: classNames.searchShowButton,
518
+ searchSubmitButton: classNames.searchSubmitButton,
519
+ searchWrapper: classNames.searchWrapper
520
+ }
521
+ }
522
+ ) : null,
466
523
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, FiltersContextProvider, { config: config3.filters, children: [
467
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ActiveFilters_default, {}),
468
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, AddFilterMenu_default, {}),
469
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, FilterModal_default, {})
524
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ActiveFilters_default, { className: classNames.activeFilterLabel }),
525
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
526
+ AddFilterMenu_default,
527
+ {
528
+ classNames: {
529
+ addFilterMenuButton: classNames.addFilterMenuButton,
530
+ addFilterMenuItem: classNames.addFilterMenuItem
531
+ }
532
+ }
533
+ ),
534
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
535
+ FilterModal_default,
536
+ {
537
+ classNames: {
538
+ body: classNames.filterModalBody,
539
+ footer: classNames.filterModalFooter,
540
+ header: classNames.filterModalHeader
541
+ }
542
+ }
543
+ )
470
544
  ] })
471
545
  ]
472
546
  }
@@ -522,11 +596,20 @@ var Form_default = Form2;
522
596
  // src/Filter/filters/boolean/schema.ts
523
597
 
524
598
  var config = _veto.object.call(void 0, {
525
- /** TODO... */
599
+ /**
600
+ * Human‑readable label used in the UI (e.g. in the chip and modal header).
601
+ * Examples: "Magical", "Haunted"
602
+ */
526
603
  text: _veto.string.call(void 0, ),
527
- /** TODO... */
604
+ /**
605
+ * Optional word shown before the label when building sentence‑like chips.
606
+ * Examples: "is" → "is Magical"
607
+ */
528
608
  textPrefix: _veto.string.call(void 0, ).optional(),
529
- /** TODO... */
609
+ /**
610
+ * Optional negation word used when a boolean value is false.
611
+ * Examples: "not" → "is not Magical"
612
+ */
530
613
  textNoWord: _veto.string.call(void 0, ).optional()
531
614
  });
532
615
  var validate = (_config) => {
@@ -554,7 +637,7 @@ var Display2 = ({
554
637
  return (_b = (_a = options.find((op) => {
555
638
  return op.value === val;
556
639
  })) == null ? void 0 : _a.label) != null ? _b : val;
557
- }).join(", ");
640
+ }).join(" ");
558
641
  return `${text} is ${labels}`;
559
642
  }
560
643
  return `${text} is ...`;
@@ -572,9 +655,15 @@ var Form_default2 = Form3;
572
655
  // src/Filter/filters/checkboxgroup/schema.ts
573
656
 
574
657
  var config2 = _veto.object.call(void 0, {
575
- /** TODO... */
658
+ /**
659
+ * Human‑readable label used in the UI (e.g. label and modal header).
660
+ * Example: "Snacks", "Mood"
661
+ */
576
662
  text: _veto.string.call(void 0, ),
577
- /** options... */
663
+ /**
664
+ * Options rendered as multiple checkboxes. Each option needs a `label`
665
+ * (what the user sees) and a `value` (what is written into the form state).
666
+ */
578
667
  options: _veto.array.call(void 0, _veto.object.call(void 0, { label: _veto.string.call(void 0, ), value: _veto.string.call(void 0, ) }))
579
668
  });
580
669
  var validate2 = (cfg) => {
@@ -617,4 +706,4 @@ var Filter_default2 = Filter_default;
617
706
 
618
707
 
619
708
  exports.createFilter_default = createFilter_default; exports.filters = filters; exports.Filter_default = Filter_default2;
620
- //# sourceMappingURL=chunk-XMMMIB2C.cjs.map
709
+ //# sourceMappingURL=chunk-DHHIGH3H.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/pixels/pixels/packages/megapixels/dist/chunk-DHHIGH3H.cjs","../src/Filter/Filter.tsx","../src/Filter/hooks/useFilterValidation.ts","../src/Filter/Subcomponents/ActiveFilters.tsx","../src/Filter/Subcomponents/FiltersContext.tsx","../src/Filter/Subcomponents/AddFilterMenu.tsx","../src/Filter/Subcomponents/FilterModal.tsx","../src/Filter/Subcomponents/SearchInput.tsx","../src/Filter/filters/createFilter.ts","../src/Filter/filters/boolean/Display.tsx","../src/Filter/filters/boolean/Form.tsx","../src/Filter/filters/boolean/schema.ts","../src/Filter/filters/boolean/boolean.ts","../src/Filter/filters/checkboxgroup/Display.tsx","../src/Filter/filters/checkboxgroup/Form.tsx","../src/Filter/filters/checkboxgroup/schema.ts","../src/Filter/filters/checkboxgroup/checkboxgroup.ts","../src/Filter/index.ts"],"names":["filters","config","useMemo","jsx","jsxs","Fragment","useState","Button","SubmitButton","Form","Display","object","Display_default","validate","boolean"],"mappings":"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","file":"/home/runner/work/pixels/pixels/packages/megapixels/dist/chunk-DHHIGH3H.cjs","sourcesContent":[null,"import type { TVClassName } from '@fuf-stack/pixel-utils';\nimport type { VetoInput } from '@fuf-stack/veto';\nimport type { ReactNode } from 'react';\nimport type { FiltersConfiguration } from './filters/types';\nimport type { SearchConfiguration } from './Subcomponents/SearchInput';\n\nimport { tv, variantsToClassNames } from '@fuf-stack/pixel-utils';\nimport Form from '@fuf-stack/uniform/Form';\n\nimport { useFilterValidation } from './hooks/useFilterValidation';\nimport ActiveFilters from './Subcomponents/ActiveFilters';\nimport AddFilterMenu from './Subcomponents/AddFilterMenu';\nimport FilterModal from './Subcomponents/FilterModal';\nimport { FiltersContextProvider } from './Subcomponents/FiltersContext';\nimport SearchInput from './Subcomponents/SearchInput';\n\n// filter styling variants\nexport const filterVariants = tv({\n slots: {\n // outer wrapper\n base: '',\n // add filter menu trigger button\n addFilterMenuButton: '',\n // add filter menu item\n addFilterMenuItem: '',\n // active filter label\n activeFilterLabel: 'dark:text-foreground h-8 cursor-pointer rounded-md',\n // filter modal body\n filterModalBody: '',\n // filter modal header\n filterModalHeader: 'text-default-700 flex items-center gap-3',\n // filter modal footer\n filterModalFooter: 'justify-between',\n // form element\n form: 'mb-3 flex flex-wrap gap-3',\n // search input field\n searchInput: '',\n // search input wrapper (inner control)\n searchInputWrapper: '',\n // search motion container\n searchMotionDiv: 'flex w-72 gap-2',\n // search show button\n searchShowButton: '',\n // search submit button\n searchSubmitButton: '',\n // search wrapper\n searchWrapper: 'flex items-center',\n },\n});\n\ntype ClassName = TVClassName<typeof filterVariants>;\n\nexport interface FilterValues {\n search?: string;\n filter?: string | Record<string, unknown>;\n}\n\nexport type FilterChildRenderFn = (values: FilterValues) => ReactNode;\n\n/**\n * Filter\n *\n * Controlled, form-driven filter UI.\n *\n * Responsibilities\n * - Derives initial form values from the controlled `values` prop\n * - Builds a composite validation schema from the filter registry (and optional search)\n * - Exposes ergonomic UI: active filters list, add/remove actions, and per-filter modal\n * - Commits changes by invoking the controlled `onChange` callback on submit\n *\n * Structure\n * - Owns an ex-forms `Form` that wraps the entire filter experience\n * - Optionally renders a search input bound to the `search` field\n * - Renders ActiveFilters, AddFilterMenu, and FilterModal inside a shared context\n * - Optionally renders children as a render-prop with the resolved `values`\n */\nexport interface FilterProps {\n /** Optional render-prop that receives the resolved, controlled values */\n children?: FilterChildRenderFn;\n /** CSS class name */\n className?: ClassName;\n /** Configuration of the filter */\n config: {\n /**\n * Declarative filter configuration. Each entry ties a logical name to a\n * registry filter type and (optionally) per-usage config overrides.\n */\n filters: FiltersConfiguration;\n /** Optional configuration for search field */\n search?: SearchConfiguration;\n };\n /** ex-forms form instance name. Defaults to \"filterComponentForm\". */\n formName?: string;\n /** Controlled setter invoked on submit with the next canonical values */\n onChange: (nextValues: FilterValues) => void;\n /** Controlled committed state: the canonical `search` and `filter` values */\n values: FilterValues;\n}\n\n/**\n * Renders the filter UI bound to a single ex-forms `Form`.\n * The form is the source of truth during user interaction; the committed\n * state is controlled by the parent via `values`/`onChange`.\n */\nconst Filter = ({\n children = undefined,\n className = undefined,\n config,\n formName = 'filterComponentForm',\n onChange,\n values,\n}: FilterProps) => {\n // Submit handler: map form state back into the controlled `values` shape\n const handleSubmit = (nextValues: Record<string, unknown>) => {\n onChange(nextValues as FilterValues);\n };\n\n // Build validation schema for all configured filters (and optional search)\n const validation = useFilterValidation(\n config.filters,\n Boolean(config.search),\n );\n\n // validate controlled values are valid\n const { data: valuesValidated } = validation.validate(values as VetoInput);\n\n // classNames from slots\n const variants = filterVariants();\n const classNames = variantsToClassNames(variants, className, 'base');\n\n return (\n <div className={classNames.base}>\n {/*\n Uniform Form wrapper\n - initialValues derive from controlled props (with optional defaults)\n - validation is built from the registry for all configured filters\n - onSubmit maps form values back into values/onChange\n */}\n <Form\n className={classNames.form}\n // disable debug mode for now\n debug={{ disable: true }}\n initialValues={valuesValidated ?? {}}\n name={formName}\n onSubmit={handleSubmit}\n validation={validation}\n >\n {/* Render search if search config is provided */}\n {config.search ? (\n <SearchInput\n config={config.search}\n classNames={{\n searchInput: classNames.searchInput,\n searchInputWrapper: classNames.searchInputWrapper,\n searchMotionDiv: classNames.searchMotionDiv,\n searchShowButton: classNames.searchShowButton,\n searchSubmitButton: classNames.searchSubmitButton,\n searchWrapper: classNames.searchWrapper,\n }}\n />\n ) : null}\n {/*\n FiltersContextProvider exposes a minimal API for the UI layer:\n - activeFilters/unusedFilters by name\n - helpers to get merged config, value, components, and field names\n - methods to add/remove filters and show/close the modal\n */}\n <FiltersContextProvider config={config.filters}>\n <ActiveFilters className={classNames.activeFilterLabel} />\n <AddFilterMenu\n classNames={{\n addFilterMenuButton: classNames.addFilterMenuButton,\n addFilterMenuItem: classNames.addFilterMenuItem,\n }}\n />\n <FilterModal\n classNames={{\n body: classNames.filterModalBody,\n footer: classNames.filterModalFooter,\n header: classNames.filterModalHeader,\n }}\n />\n </FiltersContextProvider>\n </Form>\n {/* Children can consume derived search string and parsed filter object */}\n {children?.(valuesValidated ?? {})}\n </div>\n );\n};\n\nexport default Filter;\n","import type { VetoTypeAny } from '@fuf-stack/veto';\nimport type { FilterInstance } from '../filters/types';\n\nimport { useMemo } from 'react';\n\nimport { object, string, stringToJSON, veto } from '@fuf-stack/veto';\n\n/** Validation function return type alias. */\ntype ValidationSchema = ReturnType<typeof veto>;\n\n/**\n * useFilterValidation\n *\n * Builds a composite validation schema from all provided filter definitions\n * under \"filter\" and optionally includes a \"search\" string field.\n * Memoized by inputs.\n */\nexport const useFilterValidation = (\n filters: FilterInstance<unknown, unknown>[],\n withSearch?: boolean,\n) => {\n return useMemo<ValidationSchema>(() => {\n let validationObject: Record<string, VetoTypeAny> = {};\n let filterValidation: Record<string, VetoTypeAny> = {};\n\n filters.forEach((f) => {\n filterValidation = {\n ...filterValidation,\n [f.name]: f.validation(f.config),\n };\n });\n\n validationObject = {\n filter: stringToJSON()\n .pipe(object(filterValidation))\n .or(object(filterValidation))\n .optional(),\n ...(withSearch\n ? { search: string({ min: 0 }).nullable().optional() }\n : {}),\n };\n\n return veto(validationObject);\n }, [filters, withSearch]);\n};\n\nexport default useFilterValidation;\n","import Label from '@fuf-stack/pixels/Label';\n\nimport { useFilters } from './FiltersContext';\n\n/**\n * ActiveFilters\n *\n * Shows the list of currently applied filters as clickable chips that open\n * the edit modal. Each chip can be removed via its close action.\n */\ninterface ActiveFiltersProps {\n /** CSS class name to apply to each label */\n className?: string;\n}\n\nconst ActiveFilters = ({ className = undefined }: ActiveFiltersProps) => {\n const {\n activeFilters,\n getFilterValueByName,\n getFilterInstanceByName,\n hasError,\n removeFilter,\n showFilterModal,\n } = useFilters();\n return (\n <>\n {activeFilters.map((name) => {\n const instance = getFilterInstanceByName(name);\n const value = getFilterValueByName(name);\n\n // get the display component from the instance\n const DisplayComponent = instance.components.Display;\n\n return (\n <button\n key={name}\n aria-label={`Open ${name} filter`}\n type=\"button\"\n onClick={() => {\n showFilterModal(name);\n }}\n >\n <Label\n className={className}\n color={hasError(name) ? 'danger' : 'primary'}\n variant=\"flat\"\n onClose={() => {\n removeFilter(name);\n }}\n >\n {instance.icon}\n <DisplayComponent config={instance.config} value={value} />\n </Label>\n </button>\n );\n })}\n </>\n );\n};\n\nexport default ActiveFilters;\n","import type { ReactNode } from 'react';\nimport type { FilterInstance, FiltersConfiguration } from '../filters/types';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { useFormContext } from '@fuf-stack/uniform/hooks';\n\ninterface FiltersContextValue {\n /** Active filters (names only) */\n activeFilters: string[];\n /** Seed default value and open modal for a filter usage. */\n addFilter: (name: string) => void;\n /** Close the modal. */\n closeFilterModal: () => void;\n /** Build fully-qualified form field path for a filter name */\n getFilterFormFieldName: (name: string) => string;\n /** Get current form value for a given filter name */\n getFilterValueByName: (name: string) => unknown;\n /** Get filter instance by name */\n getFilterInstanceByName: (name: string) => FilterInstance<unknown, unknown>;\n /** Validation helper for a specific filter field. */\n hasError: (name: string) => boolean;\n /** Name of the current filter that has its modal open */\n modalFilterName: string | undefined;\n /** Remove a filter from the form. */\n removeFilter: (name: string) => void;\n /** Open the modal for a given filter name. */\n showFilterModal: (name: string) => void;\n /** Filters that are not active (names only) */\n unusedFilters: string[];\n}\n\n/**\n * FiltersContext\n *\n * Central state for the filter UI with a clear boundary:\n * - The parent component controls committed filter values (via value/onChange)\n * - The form acts as an edit buffer (used by the modal)\n *\n * Design:\n * - activeFilters/unusedFilters are names-only and derived from the controlled\n * form state\n * - getFilterInstanceByName gives access to the concrete registry entry to\n * retrieve the correct Form/Display components\n * - add seeds defaults in the form and opens the modal\n * - remove un-registers the form field; if the removed filter is currently\n * open in the modal, the modal is closed without rollback\n * - on a new successful form submit (Apply), the modal closes without rollback\n * by subscribing to ex-forms submit state\n */\nconst FiltersContext = createContext<FiltersContextValue | undefined>(\n undefined,\n);\n\nexport const FiltersContextProvider = ({\n children,\n config,\n}: {\n children: ReactNode;\n config: FiltersConfiguration;\n}) => {\n // ex-forms integration:\n // - setValue/unregister/getFieldState: core helpers to manipulate and validate fields\n // - formState: we subscribe to submit-success to auto-close the modal after Apply\n const {\n formState,\n getFieldState,\n setValue,\n triggerSubmit,\n unregister,\n watch,\n } = useFormContext();\n\n /**\n * currentModalFilter\n *\n * Single source of truth for the filter edit modal and its rollback snapshot.\n * - name: which filter's modal is currently open (null when closed)\n * - hadValue/previousValue: snapshot of the controlled value taken when the\n * modal is opened; used to restore state if the user cancels/closes without\n * applying.\n *\n * Lifecycle semantics:\n * - showFilterModal(name): capture snapshot (current controlled value) and open\n * the modal for that filter.\n * - closeFilterModal(): if a snapshot exists, roll back un-applied edits by\n * restoring the previous value (setValue) or removing the field (unregister)\n * when it did not exist before; then clear currentModalFilter.\n * - On successful submit (Apply): close and clear currentModalFilter WITHOUT rollback\n * so edits remain committed.\n * - removeFilter(name): unregisters the field; when removing the filter that is\n * currently open, close the modal WITHOUT rollback (since removal is explicit).\n */\n const [currentModalFilter, setCurrentModalFilter] = useState<{\n name: string;\n hadValue: boolean;\n previousValue: unknown;\n } | null>(null);\n\n // Read current filter values from the form as the live edit buffer\n const filterValue = watch('filter', {});\n\n /**\n * getFilterFormFieldName\n *\n * Returns the fully-qualified field path for a given filter name,\n * e.g., `${filterUrlParam}.status`.\n */\n const getFilterFormFieldName = useCallback((name: string) => {\n return `filter.${name}`;\n }, []);\n\n /**\n * getFilterValueByName\n *\n * Returns the committed value for a filter from the controlled state.\n */\n const getFilterValueByName = useCallback(\n (name: string) => {\n return (filterValue as Record<string, unknown>)[name];\n },\n [filterValue],\n );\n\n /** Open the filter edit modal for the given filter name. */\n const showFilterModal = useCallback(\n (name: string) => {\n const prev = getFilterValueByName(name);\n setCurrentModalFilter({\n name,\n hadValue: typeof prev !== 'undefined',\n previousValue: prev,\n });\n },\n [getFilterValueByName],\n );\n\n /** Close the filter edit modal. Rollback un-applied edits to controlled state. */\n const closeFilterModal = useCallback(() => {\n if (currentModalFilter?.name) {\n const fieldName = getFilterFormFieldName(currentModalFilter.name);\n // if the filter had a value, set it back to the previous value,\n // otherwise unregister the field\n if (currentModalFilter.hadValue) {\n setValue(fieldName, currentModalFilter.previousValue);\n } else {\n unregister(fieldName);\n }\n }\n setCurrentModalFilter(null);\n }, [getFilterFormFieldName, currentModalFilter, setValue, unregister]);\n\n /**\n * Auto-close on submit success\n *\n * Close the modal only on new successful submissions. We track the last\n * submitCount and only react when it changes AND the form reports a\n * successful submit. This prevents closing when `isSubmitSuccessful` remains\n * true without a new submit event.\n */\n const lastSubmitCountRef = useRef<number>(0);\n useEffect(() => {\n if (\n formState.submitCount !== lastSubmitCountRef.current &&\n formState.isSubmitSuccessful\n ) {\n // On successful submit, close without rollback\n setCurrentModalFilter(null);\n }\n lastSubmitCountRef.current = formState.submitCount;\n }, [\n formState.submitCount,\n formState.isSubmitSuccessful,\n setCurrentModalFilter,\n ]);\n\n /**\n * activeFilters\n *\n * Filter names derived from the controlled form state. A filter is considered\n * active when a field exists at `filter.<name>`. Newly added filters become\n * active immediately (seeded default), and will be rolled back on cancel.\n */\n const activeFilters = useMemo(() => {\n return config\n .filter((f) => {\n return Object.hasOwn(filterValue ?? {}, f.name);\n })\n .map((f) => {\n return f.name;\n });\n }, [config, filterValue]);\n\n /**\n * unusedFilters\n *\n * Complement of activeFilters (names without a corresponding `filter.<name>`\n * field in the controlled form state).\n */\n const unusedFilters = useMemo(() => {\n return config\n .filter((f) => {\n return !Object.hasOwn(filterValue ?? {}, f.name);\n })\n .map((f) => {\n return f.name;\n });\n }, [config, filterValue]);\n\n /**\n * getRegistryFilterByName\n *\n * Looks up the concrete registry entry for a filter by name, enabling access\n * to typed Form/Display components and other registry-level metadata.\n */\n const getFilterInstanceByName = useCallback(\n (name: string) => {\n return config.find((f) => {\n return f.name === name;\n }) as FilterInstance<unknown, unknown>;\n },\n [config],\n );\n\n /**\n * addFilter\n *\n * Seeds the filter with its registry default value inside the form and opens\n * the modal for immediate editing. No URL writes happen here.\n */\n const addFilter = useCallback(\n (name: string) => {\n const inst = getFilterInstanceByName(name);\n showFilterModal(name);\n setValue(getFilterFormFieldName(name), inst.defaultValue);\n },\n [\n getFilterFormFieldName,\n getFilterInstanceByName,\n setValue,\n showFilterModal,\n ],\n );\n\n /**\n * removeFilter\n *\n * Unregisters the filter field from the form. This immediately removes the\n * filter from the active list since derived state watches the form. It\n * closes the modal without rollback if the removed filter is currently open.\n */\n const removeFilter = useCallback(\n (name: string) => {\n // unregister form field\n unregister(getFilterFormFieldName(name));\n // close filter modal if open\n if (currentModalFilter?.name === name) {\n // Explicit removal: close without rollback\n setCurrentModalFilter(null);\n }\n // trigger form submit (to update filter state)\n triggerSubmit();\n },\n [\n getFilterFormFieldName,\n currentModalFilter,\n setCurrentModalFilter,\n triggerSubmit,\n unregister,\n ],\n );\n\n /**\n * hasError\n *\n * Helper that checks the ex-forms field state for a specific filter and\n * reports whether the field is currently invalid.\n */\n const hasError = useCallback(\n (name: string) => {\n return getFieldState(getFilterFormFieldName(name)).invalid;\n },\n [getFieldState, getFilterFormFieldName],\n );\n\n const contextValue: FiltersContextValue = useMemo(() => {\n return {\n activeFilters,\n addFilter,\n closeFilterModal,\n getFilterFormFieldName,\n getFilterValueByName,\n getFilterInstanceByName,\n hasError,\n modalFilterName: currentModalFilter?.name,\n removeFilter,\n showFilterModal,\n unusedFilters,\n };\n }, [\n activeFilters,\n addFilter,\n closeFilterModal,\n getFilterFormFieldName,\n getFilterValueByName,\n getFilterInstanceByName,\n hasError,\n currentModalFilter,\n removeFilter,\n showFilterModal,\n unusedFilters,\n ]);\n\n return (\n <FiltersContext.Provider value={contextValue}>\n {children}\n </FiltersContext.Provider>\n );\n};\n\n/**\n * useFilters\n *\n * Convenience hook to consume the FiltersContext. Throws a descriptive error\n * when used outside of a FiltersContextProvider to make integration mistakes\n * obvious during development.\n */\nexport const useFilters = (): FiltersContextValue => {\n const ctx = useContext(FiltersContext);\n if (!ctx) {\n throw new Error('useFilters must be used within FiltersContextProvider');\n }\n return ctx;\n};\n\nexport default FiltersContext;\n","import { FaSliders } from 'react-icons/fa6';\n\nimport Menu from '@fuf-stack/pixels/Menu';\n\nimport { useFilters } from './FiltersContext';\n\ninterface AddFilterMenuProps {\n /** CSS class name map for slots */\n classNames?: Partial<{\n addFilterMenuItem: string;\n addFilterMenuButton: string;\n }>;\n}\n\n/**\n * AddFilterMenu\n *\n * Renders a menu trigger that opens a list of addable filters. Selecting an\n * item triggers the parent to seed a default value and open the modal.\n */\nconst AddFilterMenu = ({ classNames = {} }: AddFilterMenuProps) => {\n const { unusedFilters, addFilter, getFilterInstanceByName } = useFilters();\n\n const menuItems = unusedFilters.map((name) => {\n const instance = getFilterInstanceByName(name);\n const config = instance.config as { text?: string };\n const label = config?.text ?? name;\n return {\n key: name,\n icon: instance.icon,\n label,\n onClick: () => {\n addFilter(name);\n },\n };\n });\n\n return (\n <Menu\n isDisabled={!menuItems.length}\n items={menuItems}\n placement=\"bottom-start\"\n className={{\n item: classNames.addFilterMenuItem,\n trigger: classNames.addFilterMenuButton,\n }}\n triggerButtonProps={{\n 'aria-label': 'Add Filter',\n disableRipple: true,\n size: 'sm',\n variant: 'bordered',\n }}\n >\n <FaSliders />\n Filter\n </Menu>\n );\n};\n\nexport default AddFilterMenu;\n","import { Suspense } from 'react';\nimport { PiSlidersHorizontalBold } from 'react-icons/pi';\n\nimport Button from '@fuf-stack/pixels/Button';\nimport Modal from '@fuf-stack/pixels/Modal';\nimport SubmitButton from '@fuf-stack/uniform/SubmitButton';\n\nimport { useFilters } from './FiltersContext';\n\ninterface FilterModalProps {\n classNames?: Partial<{\n header: string;\n footer: string;\n body: string;\n }>;\n}\n\nconst FilterModal = ({ classNames = {} }: FilterModalProps) => {\n const {\n closeFilterModal,\n getFilterFormFieldName,\n getFilterInstanceByName,\n modalFilterName,\n removeFilter,\n } = useFilters();\n\n // don't render if no filter is open\n if (!modalFilterName) {\n return null;\n }\n\n const instance = getFilterInstanceByName(modalFilterName);\n const config = instance.config as { text?: string };\n\n // get the form component from the instance\n const FormComponent = instance.components.Form;\n\n return (\n <Modal\n isOpen\n onClose={closeFilterModal}\n className={{\n body: classNames.body,\n footer: classNames.footer,\n header: classNames.header,\n }}\n footer={\n <>\n <Button\n color=\"danger\"\n variant=\"flat\"\n onClick={() => {\n removeFilter(modalFilterName);\n }}\n >\n Remove\n </Button>\n <SubmitButton>Apply Filter</SubmitButton>\n </>\n }\n header={\n <>\n {instance.icon ?? <PiSlidersHorizontalBold />}\n <div>{`${config?.text ?? modalFilterName} Filter`}</div>\n </>\n }\n >\n <Suspense>\n <FormComponent\n config={config}\n fieldName={getFilterFormFieldName(modalFilterName)}\n />\n </Suspense>\n </Modal>\n );\n};\n\nexport default FilterModal;\n","import { useState } from 'react';\nimport { FaSearch } from 'react-icons/fa';\n\nimport { motion } from '@fuf-stack/pixel-motion';\nimport Button from '@fuf-stack/pixels/Button';\nimport { useFormContext } from '@fuf-stack/uniform/hooks';\nimport Input from '@fuf-stack/uniform/Input';\nimport SubmitButton from '@fuf-stack/uniform/SubmitButton';\n\nexport type SearchConfiguration =\n | boolean\n | {\n /** Placeholder shown in the search input */\n placeholder?: string;\n };\n\ninterface SearchInputProps {\n /** Slots class names passed from parent variants */\n classNames?: Partial<{\n searchWrapper: string;\n searchShowButton: string;\n searchMotionDiv: string;\n searchInput: string;\n searchInputWrapper: string;\n searchSubmitButton: string;\n }>;\n /** Search configuration */\n config: SearchConfiguration;\n}\n\n/**\n * SearchInput\n *\n * By default renders only a search button. When clicked, the text input animates in\n * and a trailing submit button is shown.\n */\nconst SearchInput = ({ classNames = {}, config }: SearchInputProps) => {\n const { formState, setFocus, triggerSubmit } = useFormContext();\n\n // Auto-open if there is an initial or externally set search value\n const isInitiallyVisible = !!formState?.defaultValues?.search;\n const [isVisible, setIsVisible] = useState(isInitiallyVisible);\n\n const placeholder =\n typeof config === 'object' ? config.placeholder : undefined;\n\n return (\n <div className={classNames.searchWrapper}>\n {!isVisible && (\n <Button\n ariaLabel=\"Show search input\"\n className={classNames.searchShowButton}\n icon={<FaSearch />}\n size=\"sm\"\n variant=\"bordered\"\n onClick={() => {\n setIsVisible(true);\n }}\n />\n )}\n {isVisible ? (\n <motion.div\n key=\"search-input\"\n animate={{ opacity: 1 }}\n className={classNames.searchMotionDiv}\n initial={{ opacity: 0.5 }}\n onAnimationComplete={() => {\n // if the input was not initially visible, focus it\n if (!isInitiallyVisible) {\n setFocus('search');\n }\n }}\n transition={{\n // if the input was not initially visible, animate in\n duration: isInitiallyVisible ? 0 : 0.3,\n ease: 'circOut',\n }}\n >\n <Input\n clearable\n debounceDelay={0}\n name=\"search\"\n placeholder={placeholder}\n size=\"sm\"\n className={{\n input: classNames.searchInput,\n inputWrapper: classNames.searchInputWrapper,\n }}\n // submit on clear\n onClear={() => {\n triggerSubmit();\n }}\n />\n <SubmitButton\n ariaLabel=\"Trigger search\"\n // eslint-disable-next-line react/no-children-prop\n children={null}\n className={classNames.searchSubmitButton}\n color=\"primary\"\n icon={<FaSearch />}\n size=\"sm\"\n />\n </motion.div>\n ) : null}\n </div>\n );\n};\n\nexport default SearchInput;\n","import type { FilterDefinition, FilterFactory } from './types';\n\n/**\n * createFilter\n *\n * Builds a filter factory from a static FilterDefinition. The returned factory\n * accepts a usage descriptor (name/icon and optional partial config) and\n * produces a concrete FilterInstance with:\n * - merged config (shallow: definition.defaults.config overlaid by overrides)\n * - Form/Display components\n * - validate function (forwarded from the definition)\n * - defaultValue (forwarded from the definition)\n * - name and icon for UI integration\n *\n * @typeParam Config - Configuration object shape for the filter\n * @typeParam Value - Runtime value type for the filter\n * @param definition - Static description of the filter (components, defaults, validate)\n * @returns FilterFactory that creates FilterInstance<Config, Value>\n */\nconst createFilter = <Config, Value>(\n definition: FilterDefinition<Config, Value>,\n): FilterFactory<Config, Value> => {\n return ({ name, icon, config }) => {\n return {\n components: definition.components,\n config: { ...definition.defaults.config, ...(config ?? {}) } as Config,\n defaultValue: definition.defaults.value,\n icon,\n name,\n validation: definition.validation,\n };\n };\n};\n\nexport default createFilter;\n","import type { FilterDisplayProps } from '../types';\nimport type { Config, Value } from './schema';\n\nconst Display = ({\n value,\n config: { text, textPrefix, textNoWord },\n}: FilterDisplayProps<Config, Value>) => {\n if (typeof value === 'boolean') {\n return (\n <>\n {value ? textPrefix : `${textPrefix} ${textNoWord ?? 'no'}`} {text}\n </>\n );\n }\n return <>{`${text}...`}</>;\n};\n\nexport default Display;\n","import type { FilterFormProps } from '../types';\nimport type { Config } from './schema';\n\nimport Switch from '@fuf-stack/uniform/Switch';\n\nconst Form = ({\n fieldName,\n config: { text, textPrefix },\n}: FilterFormProps<Config>) => {\n return <Switch label={`${textPrefix} ${text}`} name={fieldName} />;\n};\n\nexport default Form;\n","import type { vInfer } from '@fuf-stack/veto';\n\nimport { boolean, object, string } from '@fuf-stack/veto';\n\n/** configuration of the filter */\nexport const config = object({\n /**\n * Human‑readable label used in the UI (e.g. in the chip and modal header).\n * Examples: \"Magical\", \"Haunted\"\n */\n text: string(),\n /**\n * Optional word shown before the label when building sentence‑like chips.\n * Examples: \"is\" → \"is Magical\"\n */\n textPrefix: string().optional(),\n /**\n * Optional negation word used when a boolean value is false.\n * Examples: \"not\" → \"is not Magical\"\n */\n textNoWord: string().optional(),\n});\n\n/** validate the filter value */\nexport const validate = (_config?: Config) => {\n return boolean().optional();\n};\n\nexport type Config = vInfer<typeof config>;\nexport type Value = vInfer<ReturnType<typeof validate>>;\n","/* eslint-disable import-x/prefer-default-export */\n\nimport type { Config, Value } from './schema';\n\nimport createFilter from '../createFilter';\nimport Display from './Display';\nimport Form from './Form';\nimport { validate } from './schema';\n\nexport const boolean = createFilter<Config, Value>({\n components: { Display, Form },\n defaults: {\n value: true,\n config: { text: 'Active', textPrefix: 'is', textNoWord: 'no' },\n },\n validation: validate,\n});\n","import type { FilterDisplayProps } from '../types';\nimport type { Config, Value } from './schema';\n\nconst Display = ({\n value,\n config: { text, options },\n}: FilterDisplayProps<Config, Value>) => {\n if (value && value.length > 0) {\n const labels = value\n .map((val) => {\n return (\n options.find((op) => {\n return op.value === val;\n })?.label ?? val\n );\n })\n .join(' ');\n return `${text} is ${labels}`;\n }\n return `${text} is ...`;\n};\n\nexport default Display;\n","import type { FilterFormProps } from '../types';\nimport type { Config } from './schema';\n\nimport CheckboxGroup from '@fuf-stack/uniform/CheckboxGroup';\n\nconst Form = ({ fieldName, config }: FilterFormProps<Config>) => {\n return <CheckboxGroup name={fieldName} options={config.options} />;\n};\n\nexport default Form;\n","import type { vInfer } from '@fuf-stack/veto';\n\nimport { array, object, refineArray, string } from '@fuf-stack/veto';\n\n/** configuration of the filter */\nexport const config = object({\n /**\n * Human‑readable label used in the UI (e.g. label and modal header).\n * Example: \"Snacks\", \"Mood\"\n */\n text: string(),\n /**\n * Options rendered as multiple checkboxes. Each option needs a `label`\n * (what the user sees) and a `value` (what is written into the form state).\n */\n options: array(object({ label: string(), value: string() })),\n});\n\n/** validate the filter value */\nexport const validate = (cfg?: Config) => {\n return refineArray(array(string()).optional())({\n unique: true,\n custom: (values, ctx) => {\n if (!cfg) {\n return;\n }\n values.forEach((value) => {\n if (\n !cfg.options.find((option) => {\n return option?.value === value;\n })\n ) {\n ctx.addIssue({\n code: 'custom',\n message: `Invalid value: ${value}`,\n });\n }\n });\n },\n });\n};\n\nexport type Config = vInfer<typeof config>;\nexport type Value = vInfer<ReturnType<typeof validate>>;\n","/* eslint-disable import-x/prefer-default-export */\n\nimport type { Config, Value } from './schema';\n\nimport createFilter from '../createFilter';\nimport Display from './Display';\nimport Form from './Form';\nimport { validate } from './schema';\n\nexport const checkboxgroup = createFilter<Config, Value>({\n components: { Display, Form },\n defaults: { value: [], config: { text: 'Options', options: [] } },\n validation: validate,\n});\n","import Filter from './Filter';\nimport { boolean } from './filters/boolean/boolean';\nimport { checkboxgroup } from './filters/checkboxgroup/checkboxgroup';\n\n// export types\nexport type * from './filters/types';\n\n// export helpers\nexport { default as createFilter } from './filters/createFilter';\n\n// export all filters\nexport const filters = {\n boolean,\n checkboxgroup,\n};\n\nexport default Filter;\n"]}
package/dist/index.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
3
 
4
- var _chunkXMMMIB2Ccjs = require('./chunk-XMMMIB2C.cjs');
4
+ var _chunkDHHIGH3Hcjs = require('./chunk-DHHIGH3H.cjs');
5
5
 
6
6
 
7
7
 
8
- exports.createFilter = _chunkXMMMIB2Ccjs.createFilter_default; exports.filters = _chunkXMMMIB2Ccjs.filters;
8
+ exports.createFilter = _chunkDHHIGH3Hcjs.createFilter_default; exports.filters = _chunkDHHIGH3Hcjs.filters;
9
9
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.cts CHANGED
@@ -1,3 +1,5 @@
1
1
  export { FilterDefinition, FilterDisplayProps, FilterFactory, FilterFormProps, FilterInstance, FiltersConfiguration, createFilter, filters } from './Filter/index.cjs';
2
2
  import 'react/jsx-runtime';
3
+ import 'tailwind-variants';
4
+ import '@fuf-stack/pixel-utils';
3
5
  import 'react';
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export { FilterDefinition, FilterDisplayProps, FilterFactory, FilterFormProps, FilterInstance, FiltersConfiguration, createFilter, filters } from './Filter/index.js';
2
2
  import 'react/jsx-runtime';
3
+ import 'tailwind-variants';
4
+ import '@fuf-stack/pixel-utils';
3
5
  import 'react';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createFilter_default,
3
3
  filters
4
- } from "./chunk-X574WZ6N.js";
4
+ } from "./chunk-3ZL7ZLSU.js";
5
5
  export {
6
6
  createFilter_default as createFilter,
7
7
  filters
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuf-stack/megapixels",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "fuf react advanced components library",
5
5
  "author": "Fröhlich ∧ Frei",
6
6
  "homepage": "https://github.com/fuf-stack/megapixels#readme",
@@ -42,11 +42,11 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "react-icons": "5.5.0",
45
- "@fuf-stack/pixel-motion": "1.0.23",
45
+ "@fuf-stack/pixel-motion": "1.0.24",
46
+ "@fuf-stack/pixels": "1.2.5",
46
47
  "@fuf-stack/pixel-utils": "1.0.5",
47
- "@fuf-stack/pixels": "1.2.4",
48
- "@fuf-stack/uniform": "1.2.2",
49
- "@fuf-stack/veto": "0.12.6"
48
+ "@fuf-stack/veto": "0.12.6",
49
+ "@fuf-stack/uniform": "1.2.3"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/debug": "4.1.12",