1
0
Fork 0

wip: work on editor

This commit is contained in:
joostdecock 2025-01-19 11:01:34 +01:00
parent 30ae7b09da
commit c8b989afcb
16 changed files with 345 additions and 215 deletions

View file

@ -105,6 +105,7 @@ packageJson:
"./components/Table": "./components/Table/index.mjs"
"./components/Time": "./components/Time/index.mjs"
"./components/Uuid": "./components/Uuid/index.mjs"
"./components/Ux": "./components/Ux/index.mjs"
"./components/Yaml": "./components/Yaml/index.mjs"
"./components/Xray": "./components/Xray/index.mjs"
# Context

View file

@ -0,0 +1,131 @@
import React from 'react'
import mustache from 'mustache'
import { defaultConfig } from '../config/index.mjs'
import { flattenFlags } from '../lib/index.mjs'
import {
ChatIcon,
ErrorIcon,
ExpandIcon,
DocsIcon,
FixmeIcon,
FlagIcon,
OptionsIcon,
TipIcon,
WarningIcon,
WrenchIcon,
} from '@freesewing/react/components/Icon'
import { SubAccordion } from './Accordion.mjs'
/*
* Helper object to look up flag icons
*/
const flagIcons = {
error: ErrorIcon,
expand: ExpandIcon,
fixme: WrenchIcon,
info: DocsIcon,
note: ChatIcon,
otions: OptionsIcon,
tip: TipIcon,
warning: WarningIcon,
}
export const FlagTypeIcon = ({ type, className = 'tw-w-6 tw-h-6' }) => {
const Icon = flagIcons[type] || FixmeIcon
return <Icon className={className} />
}
export const Flag = ({ data, handleUpdate }) => {
const btnIcon = data.suggest?.icon ? (
<FlagTypeIcon type={data.suggest.icon} className="tw-w-5 tw-h-6 sm:tw-w-6 tw-h-6" />
) : null
const button =
data.suggest?.text && data.suggest?.update ? (
<button
className={`tw-btn tw-btn-secondary tw-btn-outline tw-flex tw-flex-row tw-items-center ${
btnIcon ? 'tw-gap-6' : ''
}`}
onClick={() => handleUpdate(data.suggest.update)}
>
{btnIcon}
{data.suggest.text}
</button>
) : null
const desc = data.replace ? mustache.render(data.desc, data.replace) : data.desc
const notes = data.notes
? Array.isArray(data.notes)
? '\n\n' +
data.notes
.map((note) => (data.replace ? mustache.render(note, data.replace) : note))
.join('\n\n')
: '\n\n' + (data.replace ? mustache.render(data.notes, data.replace) : data.notes)
: null
return (
<div className="tw-flex tw-flex-col tw-gap-2 tw-items-start">
<div className="first:tw-mt-0 tw-grow md flag">
<pre>{desc}</pre>
<pre>{notes}</pre>
</div>
{button ? (
<div className="tw-mt-2 tw-w-full tw-flex tw-flex-row tw-justify-end">{button}</div>
) : null}
</div>
)
}
export const FlagsAccordionTitle = ({ flags }) => {
const flagList = flattenFlags(flags)
if (Object.keys(flagList).length < 1) return null
return (
<>
<h5 className="tw-flex tw-flex-row tw-gap-2 tw-items-center tw-justify-between tw-w-full">
<span className="tw-text-left">Flags ({Object.keys(flagList).length})</span>
<FlagTypeIcon className="tw-w-8 tw-h-8" />
</h5>
<p className="tw-text-left">
{Object.keys(flagList).length > 1
? 'Some issues about your current pattern need your attention.'
: 'A specific issue about your current pattern needs your attention.'}
</p>
</>
)
}
export const FlagsAccordionEntries = ({ flags, update }) => {
const flagList = flattenFlags(flags)
if (Object.keys(flagList).length < 1) return null
const handleUpdate = (config) => {
if (config.settings) update.settings(...config.settings)
if (config.ui) update.ui(...config.settings)
}
return (
<SubAccordion
items={Object.entries(flagList).map(([key, flag], i) => {
const title = flag.replace ? mustache.render(flag.title, flag.replace) : flag.title
return [
<div className="tw-w-full tw-flex tw-flex-row tw-gap2 tw-justify-between" key={i}>
<div className="tw-flex tw-flex-row tw-items-center tw-gap-2">
<div className="tw-no-shrink">
<FlagIcon type={flag.type} />
</div>
<span className="tw-font-medium tw-text-left">{title}</span>
</div>
<span className="tw-uppercase tw-font-bold">{flag.type}</span>
</div>,
<Flag key={key} data={flag} handleUpdate={handleUpdate} />,
key,
]
})}
/>
)
}

View file

@ -1,5 +1,5 @@
// Dependencies
import { missingMeasurements } from '../lib/index.mjs'
import { missingMeasurements, flattenFlags } from '../lib/index.mjs'
// Hooks
import React, { useState } from 'react'
// Components
@ -7,9 +7,29 @@ import { Null } from './Null.mjs'
import { AsideViewMenuSpacer } from './AsideViewMenu.mjs'
import { ViewIcon, viewLabels } from './views/index.mjs'
import { Tooltip } from './Tooltip.mjs'
import { ErrorIcon } from '@freesewing/react/components/Icon'
import {
CircleIcon,
DetailIcon,
ErrorIcon,
ExpandIcon,
ExportIcon,
KioskIcon,
MenuIcon,
PaperlessIcon,
ResetAllIcon,
RocketIcon,
RotateIcon,
SaIcon,
SaveAsIcon,
SaveIcon,
TrashIcon,
UndoIcon,
UnitsIcon,
} from '@freesewing/react/components/Icon'
import { DesignOptionsMenu } from './menus/DesignOptionsMenu.mjs'
import { CoreSettingsMenu } from './menus/CoreSettingsMenu.mjs'
import { UiPreferencesMenu } from './menus/UiPreferencesMenu.mjs'
import { FlagsAccordionEntries } from './Flag.mjs'
export const HeaderMenuAllViews = ({ config, state, update, open, setOpen }) => (
<HeaderMenuViewMenu {...{ config, state, update, open, setOpen }} />

View file

@ -1,11 +1,12 @@
import React, { useState, useMemo, useCallback, forwardRef, useContext } from 'react'
import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'
import { Pattern } from '@freesewing/react/components/Pattern'
/*
* A pattern you can pan and zoom
*/
export const ZoomablePattern = forwardRef(function ZoomablePatternRef(props, ref) {
const { renderProps, Swizzled, rotate } = props
const { renderProps, rotate } = props
const { onTransformed, setZoomFunctions } = useContext(ZoomContext)
return (
@ -24,9 +25,8 @@ export const ZoomablePattern = forwardRef(function ZoomablePatternRef(props, ref
id="pan-zoom-pattern"
>
{props.children || (
<Swizzled.components.Pattern
<Pattern
{...{ renderProps }}
t={Swizzled.methods.t}
ref={ref}
className={`freesewing pattern w-full ${rotate ? '-rotate-90' : ''}`}
/>

View file

@ -5,6 +5,22 @@ import {
menuCoreSettingsSammHandler,
menuCoreSettingsStructure,
} from '../../lib/index.mjs'
import {
MenuBoolInput,
MenuListInput,
MenuMmInput,
MenuOnlySettingInput,
MenuSliderInput,
} from './Input.mjs'
import {
//MenuBoolValue,
MenuListValue,
MenuMmValue,
MenuOnlySettingValue,
MenuScaleSettingValue,
} from './Value.mjs'
import { MenuItemGroup } from './Container.mjs'
import { SettingsIcon } from '@freesewing/react/components/Icon'
/**
* The core settings menu

View file

@ -0,0 +1,60 @@
// Depdendencies
import React from 'react'
import { menuUiPreferencesStructure } from '../../lib/index.mjs'
// Components
import { MenuUxSettingInput, MenuListInput } from './Input.mjs'
import { MenuListValue } from './Value.mjs'
import { MenuItemGroup } from './Container.mjs'
import { Ux } from '@freesewing/react/components/Ux'
export const UiPreferencesMenu = ({ update, state, Design }) => {
console.log(state)
const structure = menuUiPreferencesStructure()
const drillProps = { Design, state, update }
const inputs = {
ux: (props) => <MenuUxSettingInput {...drillProps} {...props} />,
aside: (props) => <MenuListInput {...drillProps} {...props} />,
kiosk: (props) => <MenuListInput {...drillProps} {...props} />,
rotate: (props) => <MenuListInput {...drillProps} {...props} />,
renderer: (props) => <MenuListInput {...drillProps} {...props} />,
}
const values = {
ux: (props) => <Ux ux={state.ui.ux} {...props} />,
aside: MenuListValue,
kiosk: MenuListValue,
rotate: MenuListValue,
renderer: MenuListValue,
}
return (
<MenuItemGroup
{...{
structure,
ux: state.ui?.ux,
currentValues: state.ui || {},
Item: (props) => (
<UiPreference updateHandler={update} {...{ inputs, values, Design }} {...props} />
),
isFirst: true,
name: 'pe:uiPreferences',
language: state.locale,
passProps: {
ux: state.ui?.ux,
settings: state.settings,
patternConfig: Design.patternConfig,
},
updateHandler: update.ui,
isDesignOptionsGroup: false,
state,
Design,
inputs,
values,
}}
/>
)
}
export const UiPreference = ({ name, ux, ...rest }) => (
<MenuItem {...rest} name={name} allowToggle={!['ux', 'view'].includes(name) && ux > 3} ux={ux} />
)

View file

@ -1,34 +1,62 @@
import React from 'react'
import { mergeOptions } from '@freesewing/core'
import { formatMm } from '@freesewing/utils'
import { BoolYesIcon, BoolNoIcon } from '@freesewing/react/components/Icon'
/** Displays that constant values are not implemented in the front end */
/**
* this method is here to capture deprecated use of the translation method
*
* @param {string} key - The translation key
* @retunr {string} key - Returns the key as-is
*/
const t = (key) => {
console.log('FIXME: t method called in react/components/Editor/components/menus/Value.mjs')
return key
}
/**
* Displays that constant values are not implemented in the front end
*/
export const MenuConstantOptionValue = () => (
<span className="text-error">FIXME: No ConstantOptionvalue implemented</span>
)
/** Displays a count value*/
/**
* Displays a count value
*
* @param {object} config - The option config
* @param {number} current - The current (count) value
* @param {bool} changed - Whether or not the value is non-default
*/
export const MenuCountOptionValue = ({ config, current, changed }) => (
<MenuShowValue {...{ current, changed, dflt: config.count }} />
)
/** Displays a degree value */
/**
* Displays a degree value
*
* @param {object} config - The option config
* @param {number} current - The current (count) value
* @param {bool} changed - Whether or not the value is non-default
*/
export const MenuDegOptionValue = ({ config, current, changed }) => (
<MenuHighlightValue changed={changed}> {changed ? current : config.deg}&deg;</MenuHighlightValue>
)
/**
* A component to highlight a changed value
* @param {Boolean} changed - Whether the value is changed or not
*
* @param {Boolean} changed - Whether the value is non-default
* @param {Function} children - The React children
*/
export const MenuHighlightValue = ({ changed, children }) => (
<span className={changed ? 'text-accent' : ''}> {children} </span>
)
/** Displays a list option value */
export const MenuListOptionValue = (props) => (
<MenuListValue {...props} t={(input) => 'fixme handle option translation'} />
)
/**
* Displays a list option value
*/
export const MenuListOptionValue = (props) => <MenuListValue {...props} t={t} />
/**
* Displays the correct, translated value for a list
@ -51,20 +79,26 @@ export const MenuListValue = ({ current, config, changed }) => {
else if (val) key = <BoolYesIcon />
else key = <BoolNoIcon />
const translated = config.doNotTranslate || typeof key !== 'string' ? key : t(key)
const translated = config.doNotTranslate || key
return <MenuHighlightValue changed={changed}>{translated}</MenuHighlightValue>
}
/** Displays the corrent, translated value for a boolean */
/**
* Displays the corrent, translated value for a boolean
*/
export const MenuBoolValue = MenuListOptionValue
/** Displays the MmOptions are not supported */
/**
* Displays the MmOptions are not supported
*/
export const MenuMmOptionValue = () => (
<span className="text-error">FIXME: No Mm Options are not supported</span>
)
/** Displays a formated mm value based on the current units */
/**
* Displays a formated mm value based on the current units
*/
export const MenuMmValue = ({ current, config, units, changed }) => (
<MenuHighlightValue changed={changed}>
<span
@ -75,7 +109,8 @@ export const MenuMmValue = ({ current, config, units, changed }) => (
</MenuHighlightValue>
)
/** Displays the current percentage value, and the absolute value if configured
/**
* Displays the current percentage value, and the absolute value if configured
*
**************************************************************************
* SliderIcon Title THIS *
@ -83,7 +118,7 @@ export const MenuMmValue = ({ current, config, units, changed }) => (
* ----------------------0----------------------------------------------- *
* msg PencilIcon ResetIcon *
**************************************************************************
* */
*/
export const MenuPctOptionValue = ({ config, current, settings, changed, patternConfig }) => {
const val = changed ? current : config.pct / 100
@ -101,6 +136,7 @@ export const MenuPctOptionValue = ({ config, current, settings, changed, pattern
/**
* A component to display a value, highligting it if it changed
*
* @param {Number|String|Boolean} options.current - The current value, if it has been changed
* @param {Number|String|Boolean} options.dflt - The default value
* @param {Boolean} options.changed - Has the value been changed?
@ -109,10 +145,24 @@ export const MenuShowValue = ({ current, dflt, changed }) => {
return <MenuHighlightValue changed={changed}> {changed ? current : dflt} </MenuHighlightValue>
}
/**
* Displays the value for core's scale setting
*
* @param {object} config - The option config
* @param {number} current - The current (count) value
* @param {bool} changed - Whether or not the value is non-default
*/
export const MenuScaleSettingValue = ({ current, config, changed }) => (
<MenuHighlightValue current={current} dflt={config.dflt} changed={changed} />
)
/**
* Displays the value for core's only setting
*
* @param {object} config - The option config
* @param {number} current - The current (count) value
* @param {bool} changed - Whether or not the value is non-default
*/
export const MenuOnlySettingValue = ({ current, config }) => (
<MenuHighlightValue
current={current?.length}

View file

@ -1,60 +0,0 @@
export const UiPreferencesMenu = ({ Swizzled, update, state, Design }) => {
const structure = Swizzled.methods.menuUiPreferencesStructure()
const drillProps = { Design, state, update }
const inputs = {
ux: (props) => <Swizzled.components.MenuUxSettingInput {...drillProps} {...props} />,
aside: (props) => <Swizzled.components.MenuListInput {...drillProps} {...props} />,
kiosk: (props) => <Swizzled.components.MenuListInput {...drillProps} {...props} />,
rotate: (props) => <Swizzled.components.MenuListInput {...drillProps} {...props} />,
renderer: (props) => <Swizzled.components.MenuListInput {...drillProps} {...props} />,
}
const values = {
ux: (props) => <Swizzled.components.Ux ux={state.ui.ux} {...props} />,
aside: Swizzled.components.MenuListValue,
kiosk: Swizzled.components.MenuListValue,
rotate: Swizzled.components.MenuListValue,
renderer: Swizzled.components.MenuListValue,
}
return (
<Swizzled.components.MenuItemGroup
{...{
structure,
ux: state.ui?.ux,
currentValues: state.ui || {},
Item: (props) => (
<Swizzled.components.UiPreference
updateHandler={update}
{...{ inputs, values, Swizzled, Design }}
{...props}
/>
),
isFirst: true,
name: 'pe:uiPreferences',
language: state.locale,
passProps: {
ux: state.ui?.ux,
settings: state.settings,
patternConfig: Design.patternConfig,
},
updateHandler: update.ui,
isDesignOptionsGroup: false,
Swizzled,
state,
Design,
inputs,
values,
}}
/>
)
}
export const UiPreference = ({ Swizzled, name, ux, ...rest }) => (
<Swizzled.components.MenuItem
{...rest}
name={name}
allowToggle={!['ux', 'view'].includes(name) && ux > 3}
ux={ux}
/>
)

View file

@ -100,7 +100,7 @@ export const viewLabels = {
t: 'Choose a different view',
d: 'fixme',
},
undos: {
undo: {
t: 'Undo History',
d: 'Time-travel through your recent pattern changes',
},

View file

@ -1,3 +1,5 @@
// Dependencies
import { defaultConfig } from '../config/index.mjs'
// Components
import {
ErrorIcon,
@ -35,9 +37,10 @@ export function draft(Design, settings) {
return data
}
export function flattenFlags(flags, config) {
export function flattenFlags(flags) {
const all = {}
for (const type of config.flagTypes) {
for (const type of defaultConfig.flagTypes) {
let i = 0
if (flags[type]) {
for (const flag of Object.values(flags[type])) {

View file

@ -1,13 +1,22 @@
export function menuUiPreferencesStructure(Swizzled) {
const uiUx = Swizzled.config.uxLevels.ui
import { defaultConfig } from '../config/index.mjs'
import {
MenuIcon,
KioskIcon,
RotateIcon,
RocketIcon,
UxIcon,
} from '@freesewing/react/components/Icon'
export function menuUiPreferencesStructure() {
const uiUx = defaultConfig.uxLevels.ui
const uiPreferences = {
ux: {
ux: uiUx.ux,
emoji: '🖥️',
list: [1, 2, 3, 4, 5],
choiceTitles: {},
icon: Swizzled.components.UxIcon,
dflt: Swizzled.config.defaultUx,
icon: UxIcon,
dflt: defaultConfig.defaultUx,
},
aside: {
ux: uiUx.aside,
@ -17,7 +26,7 @@ export function menuUiPreferencesStructure(Swizzled) {
1: 'pe:withAside',
},
dflt: 1,
icon: Swizzled.components.MenuIcon,
icon: MenuIcon,
},
kiosk: {
ux: uiUx.kiosk,
@ -27,7 +36,7 @@ export function menuUiPreferencesStructure(Swizzled) {
1: 'pe:kioskMode',
},
dflt: 0,
icon: Swizzled.components.KioskIcon,
icon: KioskIcon,
},
rotate: {
ux: uiUx.rotate,
@ -37,7 +46,7 @@ export function menuUiPreferencesStructure(Swizzled) {
1: 'pe:rotateYes',
},
dflt: 0,
icon: Swizzled.components.RotateIcon,
icon: RotateIcon,
},
renderer: {
ux: uiUx.renderer,
@ -51,7 +60,7 @@ export function menuUiPreferencesStructure(Swizzled) {
svg: 'SVG',
},
dflt: 'react',
icon: Swizzled.components.RocketIcon,
icon: RocketIcon,
},
}

View file

@ -1,103 +0,0 @@
import mustache from 'mustache'
export const FlagTypeIcon = ({ Swizzled, type, className = 'w-6 h-6' }) => {
const Icon = Swizzled.components[`Flag${Swizzled.methods.capitalize(type)}Icon`]
return Icon ? <Icon className={className} /> : null
}
export const Flag = ({ Swizzled, data, handleUpdate }) => {
const btnIcon = data.suggest?.icon ? (
<Swizzled.components.FlagTypeIcon type={data.suggest.icon} className="w-5 h-6 sm:w-6 h-6" />
) : null
const { t } = Swizzled.methods
const button =
data.suggest?.text && data.suggest?.update ? (
<button
className={`btn btn-secondary btn-outline flex flex-row items-center ${
btnIcon ? 'gap-6' : ''
}`}
onClick={() => handleUpdate(data.suggest.update)}
>
{btnIcon}
{t(data.suggest.text)}
</button>
) : null
const desc = data.replace ? mustache.render(t(data.desc), data.replace) : t(data.desc)
const notes = data.notes
? Array.isArray(data.notes)
? '\n\n' +
data.notes
.map((note) => (data.replace ? mustache.render(t(note), data.replace) : t(note)))
.join('\n\n')
: '\n\n' + (data.replace ? mustache.render(t(data.notes), data.replace) : t(data.notes))
: null
return (
<div className="flex flex-col gap-2 items-start">
<div className="first:mt-0 grow md flag">
<pre>{desc}</pre>
<pre>{notes}</pre>
</div>
{button ? <div className="mt-2 w-full flex flex-row justify-end">{button}</div> : null}
</div>
)
}
//<Mdx md={notes ? desc + notes : desc} />
export const FlagsAccordionTitle = ({ flags, Swizzled }) => {
const { t } = Swizzled.methods
const flagList = Swizzled.methods.flattenFlags(flags)
if (Object.keys(flagList).length < 1) return null
return (
<>
<h5 className="flex flex-row gap-2 items-center justify-between w-full">
<span className="text-left">
{t('pe:flagMenu.t')} ({Object.keys(flagList).length})
</span>
<Swizzled.components.FlagTypeIcon className="w-8 h-8" />
</h5>
<p className="text-left">
{Object.keys(flagList).length > 1 ? t('pe:flagMenuMany.d') : t('pe:flagMenuOne.d')}
</p>
</>
)
}
export const FlagsAccordionEntries = ({ flags, update, Swizzled }) => {
const flagList = Swizzled.methods.flattenFlags(flags)
const { t } = Swizzled.methods
if (Object.keys(flagList).length < 1) return null
const handleUpdate = (config) => {
if (config.settings) update.settings(...config.settings)
if (config.ui) update.ui(...config.settings)
}
return (
<Swizzled.components.SubAccordion
items={Object.entries(flagList).map(([key, flag], i) => {
const title = flag.replace ? mustache.render(t(flag.title), flag.replace) : t(flag.title)
return [
<div className="w-full flex flex-row gap2 justify-between" key={i}>
<div className="flex flex-row items-center gap-2">
<div className="no-shrink">
<Swizzled.components.FlagIcon type={flag.type} />
</div>
<span className="font-medium text-left">{title}</span>
</div>
<span className="uppercase font-bold">{flag.type}</span>
</div>,
<Swizzled.components.Flag key={key} t={t} data={flag} handleUpdate={handleUpdate} />,
key,
]
})}
/>
)
}

View file

@ -1,12 +0,0 @@
export const Ux = ({ Swizzled, ux = 0 }) => (
<div className="flex flex-row">
{[0, 1, 2, 3, 4].map((i) => (
<Swizzled.components.CircleIcon
key={i}
fill={i < ux ? true : false}
className={`w-6 h-6 ${i < ux ? 'stroke-secondary fill-secondary' : 'stroke-current'}`}
fillOpacity={0.3}
/>
))}
</div>
)

View file

@ -242,6 +242,13 @@ export const FingerprintIcon = (props) => (
</IconWrapper>
)
// Looks lik an exclamation point inside a circle
export const FixmeIcon = (props) => (
<IconWrapper {...props}>
<path d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</IconWrapper>
)
// Looks lik a flag
export const FlagIcon = (props) => (
<IconWrapper {...props}>
@ -753,12 +760,3 @@ export const ViewDocsIcon = DocsIcon
export const ViewDesignsIcon = DesignIcon
export const ViewViewPickerIcon = UiIcon
export const ViewUndosIcon = BackIcon
// Flag icons
export const FlagNoteIcon = ChatIcon
export const FlagInfoIcon = DocsIcon
export const FlagTipIcon = TipIcon
export const FlagWarningIcon = WarningIcon
export const FlagErrorIcon = ErrorIcon
export const FlagFixmeIcon = WrenchIcon
export const FlagExpandIcon = ExpandIcon
export const FlagOtionsIcon = OptionsIcon

View file

@ -0,0 +1,15 @@
import React from 'react'
import { CircleIcon } from '@freesewing/react/components/Icon'
export const Ux = ({ ux = 0 }) => (
<div className="flex flex-row">
{[0, 1, 2, 3, 4].map((i) => (
<CircleIcon
key={i}
fill={i < ux ? true : false}
className={`tw-w-6 tw-h-6 ${i < ux ? 'tw-stroke-secondary tw-fill-secondary' : 'tw-stroke-current'}`}
fillOpacity={0.3}
/>
))}
</div>
)

View file

@ -58,6 +58,7 @@
"./components/Table": "./components/Table/index.mjs",
"./components/Time": "./components/Time/index.mjs",
"./components/Uuid": "./components/Uuid/index.mjs",
"./components/Ux": "./components/Ux/index.mjs",
"./components/Yaml": "./components/Yaml/index.mjs",
"./components/Xray": "./components/Xray/index.mjs",
"./context/LoadingStatus": "./context/LoadingStatus/index.mjs",
@ -81,12 +82,13 @@
"highlight.js": "^11.11.0",
"html-react-parser": "^5.0.7",
"luxon": "^3.5.0",
"nuqs": "^2.3.0",
"nuqs": "^1.17.6",
"react-markdown": "^9.0.1",
"tlds": "^1.255.0",
"use-local-storage-state": "19.1.0",
"use-session-storage-state": "^19.0.0"
},
"devDependencies": {},
"files": [
"components/**",
"hooks/**",