diff --git a/packages/date/package.json b/packages/date/package.json index 828407f0a2..5fda42f4ae 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -48,10 +48,12 @@ "dependencies": { "@vtex/shoreline-icons": "workspace", "@vtex/shoreline-utils": "workspace", + "@vtex/shoreline-store": "workspace", "@internationalized/date": "3.5.0", "@react-aria/calendar": "3.5.3", "@react-aria/datepicker": "3.9.0", "@react-stately/datepicker": "3.9.0", - "@react-stately/calendar": "3.4.2" + "@react-stately/calendar": "3.4.2", + "@react-aria/i18n": "3.9.0" } } diff --git a/packages/date/src/calendar/calendar-cell.css b/packages/date/src/calendar/calendar-cell.css index 7526a5eaa4..4bfe10b2ff 100644 --- a/packages/date/src/calendar/calendar-cell.css +++ b/packages/date/src/calendar/calendar-cell.css @@ -2,7 +2,9 @@ [data-sl-calendar-cell] { /* height: 32px; */ } +} +@layer sl-expended-components { [data-sl-calendar-cell-button] { width: 100%; height: 100%; diff --git a/packages/date/src/calendar/calendar-cell.tsx b/packages/date/src/calendar/calendar-cell.tsx index 001e947bec..d64432c229 100644 --- a/packages/date/src/calendar/calendar-cell.tsx +++ b/packages/date/src/calendar/calendar-cell.tsx @@ -3,9 +3,12 @@ import { useCalendarCell } from '@react-aria/calendar' import { IconButton } from '@vtex/shoreline-components' import './calendar-cell.css' +import { useCalendarContext } from './calendar-provider' -export function CalendarCell({ state, date }: any) { +export function CalendarCell({ date }: any) { const ref = useRef(null) + const store = useCalendarContext() + const { cellProps, buttonProps, @@ -15,7 +18,7 @@ export function CalendarCell({ state, date }: any) { isUnavailable, formattedDate, isFocused, - } = useCalendarCell({ date }, state, ref) + } = useCalendarCell({ date }, store.state, ref) return ( diff --git a/packages/date/src/calendar/calendar-grid.tsx b/packages/date/src/calendar/calendar-grid.tsx index d6e978bde1..bec9a61496 100644 --- a/packages/date/src/calendar/calendar-grid.tsx +++ b/packages/date/src/calendar/calendar-grid.tsx @@ -1,16 +1,23 @@ import React from 'react' import { useCalendarGrid } from '@react-aria/calendar' import { useLocale } from '@vtex/shoreline-components' -import { getWeeksInMonth } from '../utils' +import { getWeeksInMonth } from '../utils' import { CalendarCell } from './calendar-cell' +import { useCalendarContext } from './calendar-provider' import './calendar-grid.css' -export function CalendarGrid({ state, ...props }: any) { +export function CalendarGrid(props: any) { const locale = useLocale() - const { gridProps, headerProps, weekDays } = useCalendarGrid(props, state) - const weeksInMonth = getWeeksInMonth(state.visibleRange.start, locale) + const store = useCalendarContext() + + const { gridProps, headerProps, weekDays } = useCalendarGrid( + props, + store.state + ) + + const weeksInMonth = getWeeksInMonth(store.state.visibleRange.start, locale) return ( @@ -24,11 +31,11 @@ export function CalendarGrid({ state, ...props }: any) { {[...new Array(weeksInMonth).keys()].map((weekIndex) => ( - {state + {store.state .getDatesInWeek(weekIndex) .map((date: any, i: number) => date ? ( - + ) : (
) diff --git a/packages/date/src/calendar/calendar-provider.tsx b/packages/date/src/calendar/calendar-provider.tsx new file mode 100644 index 0000000000..d47fce3096 --- /dev/null +++ b/packages/date/src/calendar/calendar-provider.tsx @@ -0,0 +1,23 @@ +import type { CalendarState } from '@react-stately/calendar' +import React, { createContext, useContext } from 'react' +import type { Store } from '@vtex/shoreline-store' + +export const CalendarContext = createContext | null>(null) + +export function CalendarProvider({ store, children }: any) { + return ( + + {children} + + ) +} + +export function useCalendarContext() { + const context = useContext(CalendarContext) + + if (!context) { + throw new Error('Calendar components must be wrapped by CalendarProvider') + } + + return context +} diff --git a/packages/date/src/calendar/calendar-store.ts b/packages/date/src/calendar/calendar-store.ts new file mode 100644 index 0000000000..461722876a --- /dev/null +++ b/packages/date/src/calendar/calendar-store.ts @@ -0,0 +1,15 @@ +import { createCalendar } from '@internationalized/date' +import { useCalendarState } from '@react-stately/calendar' +import { Store } from '@vtex/shoreline-store' +import { useMemo } from 'react' + +export function useCalendarStore(props: any) { + const state = useCalendarState({ + ...props, + createCalendar, + }) + + const store = useMemo(() => new Store(state), [state]) + + return store +} diff --git a/packages/date/src/calendar/calendar.tsx b/packages/date/src/calendar/calendar.tsx index 285f9e5136..461f7fdffc 100644 --- a/packages/date/src/calendar/calendar.tsx +++ b/packages/date/src/calendar/calendar.tsx @@ -1,52 +1,57 @@ import React from 'react' import { useCalendar } from '@react-aria/calendar' -import { useCalendarState } from '@react-stately/calendar' import { createCalendar } from '@internationalized/date' import { useLocale, IconButton } from '@vtex/shoreline-components' import { IconCaretLeft, IconCaretRight } from '@vtex/shoreline-icons' +import { I18nProvider } from '@react-aria/i18n' + import { CalendarGrid } from './calendar-grid' +import { CalendarProvider } from './calendar-provider' +import { useCalendarStore } from './calendar-store' import './calendar.css' export function Calendar(props: any) { const locale = useLocale() - const state = useCalendarState({ + const store = useCalendarStore({ ...props, locale, createCalendar, }) const { calendarProps, prevButtonProps, nextButtonProps, title } = - useCalendar(props, state) - - console.log(prevButtonProps) + useCalendar(props, store.state) return ( -
-
- - - -

{title}

- - - -
- -
+ + +
+
+ + + +

{title}

+ + + +
+ +
+
+
) } diff --git a/packages/date/src/calendar/stories/calendar.stories.tsx b/packages/date/src/calendar/stories/calendar.stories.tsx index 7011539afd..ebc3f40e88 100644 --- a/packages/date/src/calendar/stories/calendar.stories.tsx +++ b/packages/date/src/calendar/stories/calendar.stories.tsx @@ -2,6 +2,8 @@ import React, { useState } from 'react' // import { Stack } from '@vtex/shoreline-components' import { Calendar } from '../index' +import { LocaleProvider } from '@vtex/shoreline-components' + // import { parseDate } from '../../utils' export default { @@ -18,9 +20,13 @@ export function Default() { // return // } -// export function Locale() { -// return -// } +export function Locale() { + return ( + + + + ) +} // export function Granularity() { // return ( diff --git a/packages/store/CHANGELOG.md b/packages/store/CHANGELOG.md new file mode 100644 index 0000000000..e4d87c4d45 --- /dev/null +++ b/packages/store/CHANGELOG.md @@ -0,0 +1,4 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. diff --git a/packages/store/package.json b/packages/store/package.json new file mode 100644 index 0000000000..f5eea192a5 --- /dev/null +++ b/packages/store/package.json @@ -0,0 +1,48 @@ +{ + "name": "@vtex/shoreline-store", + "version": "0.0.0", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "files": [ + "dist" + ], + "exports": { + ".": { + "require": "./dist/index.js", + "import": "./dist/index.mjs", + "types": "./dist/index.d.ts" + }, + "./styles": "./dist/index.css" + }, + "engines": { + "node": ">=16" + }, + "scripts": { + "prebuild": "rm -rf dist", + "dev": "tsup --watch", + "build": "tsup" + }, + "repository": { + "directory": "packages/shoreline", + "type": "git", + "url": "git+https://github.com/vtex/shoreline.git" + }, + "bugs": { + "url": "https://github.com/vtex/shoreline/issues" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "devDependencies": { + "@types/use-sync-external-store": "0.0.6" + }, + "dependencies": { + "use-sync-external-store": "1.2.0" + } +} diff --git a/packages/store/src/index.ts b/packages/store/src/index.ts new file mode 100644 index 0000000000..505c32cbbf --- /dev/null +++ b/packages/store/src/index.ts @@ -0,0 +1,2 @@ +export * from './store' +export * from './use-store' diff --git a/packages/store/src/shallow.ts b/packages/store/src/shallow.ts new file mode 100644 index 0000000000..52b95f5e46 --- /dev/null +++ b/packages/store/src/shallow.ts @@ -0,0 +1,31 @@ +export function shallow(objA: T, objB: T) { + if (Object.is(objA, objB)) { + return true + } + + if ( + typeof objA !== 'object' || + objA === null || + typeof objB !== 'object' || + objB === null + ) { + return false + } + + const keysA = Object.keys(objA) + + if (keysA.length !== Object.keys(objB).length) { + return false + } + + for (let i = 0; i < keysA.length; i++) { + if ( + !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || + !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) + ) { + return false + } + } + + return true +} diff --git a/packages/store/src/store.ts b/packages/store/src/store.ts new file mode 100644 index 0000000000..ebf6192018 --- /dev/null +++ b/packages/store/src/store.ts @@ -0,0 +1,77 @@ +export type AnyUpdater = (...args: any[]) => any + +export type Listener = () => void + +interface StoreOptions< + TState, + TUpdater extends AnyUpdater = (cb: TState) => TState +> { + updateFn?: (previous: TState) => (updater: TUpdater) => TState + onSubscribe?: ( + listener: Listener, + store: Store + ) => () => void + onUpdate?: () => void +} + +export class Store< + TState, + TUpdater extends AnyUpdater = (cb: TState) => TState +> { + private listeners = new Set() + private _state: TState + private _options?: StoreOptions + private _batching = false + private _flushing = 0 + + constructor(initialState: TState, options?: StoreOptions) { + this._state = initialState + this._options = options + } + + public get state() { + return this._state + } + + public subscribe = (listener: Listener) => { + this.listeners.add(listener) + const unsub = this._options?.onSubscribe?.(listener, this) + + return () => { + this.listeners.delete(listener) + unsub?.() + } + } + + public setState = (updater: TUpdater) => { + const previous = this._state + + this._state = this._options?.updateFn + ? this._options.updateFn(previous)(updater) + : (updater as any)(previous) + + // Always run onUpdate, regardless of batching + this._options?.onUpdate?.() + + // Attempt to flush + this._flush() + } + + public _flush = () => { + if (this._batching) return + const flushId = ++this._flushing + + this.listeners.forEach((listener) => { + if (this._flushing !== flushId) return + listener() + }) + } + + public batch = (cb: () => void) => { + if (this._batching) return cb() + this._batching = true + cb() + this._batching = false + this._flush() + } +} diff --git a/packages/store/src/tests/shallow.test.ts b/packages/store/src/tests/shallow.test.ts new file mode 100644 index 0000000000..20a2165b37 --- /dev/null +++ b/packages/store/src/tests/shallow.test.ts @@ -0,0 +1,60 @@ +import { describe, it, expect } from '@vtex/shoreline-test-utils' +import { shallow } from '../shallow' + +describe('shallow', () => { + it('should return true for shallowly equal objects', () => { + const objA = { a: 1, b: 'hello' } + const objB = { a: 1, b: 'hello' } + + expect(shallow(objA, objB)).toBe(true) + }) + + it('should return false for objects with different values', () => { + const objA = { a: 1, b: 'hello' } + const objB = { a: 2, b: 'world' } + + expect(shallow(objA, objB)).toBe(false) + }) + + it('should return false for objects with different keys', () => { + const objA = { a: 1, b: 'hello' } + const objB = { a: 1, c: 'world' } + + expect(shallow(objA, objB as any)).toBe(false) + }) + + it('should return false for objects with different structures', () => { + const objA = { a: 1, b: 'hello' } + const objB = [1, 'hello'] + + expect(shallow(objA, objB as any)).toBe(false) + }) + + it('should return false for one object being null', () => { + const objA = { a: 1, b: 'hello' } + const objB = null + + expect(shallow(objA, objB)).toBe(false) + }) + + it('should return false for one object being undefined', () => { + const objA = { a: 1, b: 'hello' } + const objB = undefined + + expect(shallow(objA, objB)).toBe(false) + }) + + it('should return true for two null objects', () => { + const objA = null + const objB = null + + expect(shallow(objA, objB)).toBe(true) + }) + + it('should return false for objects with different types', () => { + const objA = { a: 1, b: 'hello' } + const objB = { a: '1', b: 'hello' } + + expect(shallow(objA, objB as any)).toBe(false) + }) +}) diff --git a/packages/store/src/tests/store.test.ts b/packages/store/src/tests/store.test.ts new file mode 100644 index 0000000000..d894eb5706 --- /dev/null +++ b/packages/store/src/tests/store.test.ts @@ -0,0 +1,83 @@ +import { describe, test, expect, vi } from '@vtex/shoreline-test-utils' +import { Store } from '../store' + +describe('store', () => { + test(`should set the initial value`, () => { + const store = new Store(0) + + expect(store.state).toEqual(0) + }) + + test(`basic subscriptions should work`, () => { + const store = new Store(0) + + const subscription = vi.fn() + + const unsub = store.subscribe(subscription) + + store.setState(() => 1) + + expect(store.state).toEqual(1) + expect(subscription).toHaveBeenCalled() + + unsub() + + store.setState(() => 2) + + expect(store.state).toEqual(2) + + expect(subscription).toHaveBeenCalledTimes(1) + }) + + test(`setState passes previous state`, () => { + const store = new Store(3) + + store.setState((v) => v + 1) + + expect(store.state).toEqual(4) + }) + + test(`updateFn acts as state transformer`, () => { + const store = new Store(1, { + updateFn: (v) => (updater) => Number(updater(v)), + }) + + store.setState((v) => `${v + 1}` as never) + + expect(store.state).toEqual(2) + + store.setState((v) => `${v + 2}` as never) + + expect(store.state).toEqual(4) + + expect(typeof store.state).toEqual('number') + }) + + test('Batch prevents listeners from being called during repeated setStates', () => { + const store = new Store(0) + + const listener = vi.fn() + + store.subscribe(listener) + + store.batch(() => { + store.setState(() => 1) + store.setState(() => 2) + store.setState(() => 3) + store.setState(() => 4) + }) + + expect(store.state).toEqual(4) + // Listener is only called once because of batching + expect(listener).toHaveBeenCalledTimes(1) + + store.setState(() => 1) + store.setState(() => 2) + store.setState(() => 3) + store.setState(() => 4) + + expect(store.state).toEqual(4) + // Listener is called 4 times because of a lack of batching + expect(listener).toHaveBeenCalledTimes(5) + }) +}) diff --git a/packages/store/src/tests/use-store.test.tsx b/packages/store/src/tests/use-store.test.tsx new file mode 100644 index 0000000000..41de8a5649 --- /dev/null +++ b/packages/store/src/tests/use-store.test.tsx @@ -0,0 +1,88 @@ +import { + render, + waitFor, + expect, + describe, + it, + userEvent, + vi, +} from '@vtex/shoreline-test-utils' +import * as React from 'react' +import { Store } from '../store' +import { useStore } from '../use-store' +import { useState } from 'react' + +const user = userEvent.setup() + +describe('useStore', () => { + it('allows us to select state using a selector', async () => { + const store = new Store({ + select: 0, + ignored: 1, + }) + + function Comp() { + const storeVal = useStore(store, (state) => state.select) + + return

Store: {storeVal}

+ } + + const { getByText } = render() + + expect(getByText('Store: 0')).toBeInTheDocument() + }) + + it('only triggers a re-render when selector state is updated', async () => { + const store = new Store({ + select: 0, + ignored: 1, + }) + + function Comp() { + const storeVal = useStore(store, (state) => state.select) + const [fn] = useState(vi.fn) + + fn() + + return ( +
+

Number rendered: {fn.mock.calls.length}

+

Store: {storeVal}

+ + +
+ ) + } + + const { getByText } = render() + + expect(getByText('Store: 0')).toBeInTheDocument() + expect(getByText('Number rendered: 1')).toBeInTheDocument() + + await user.click(getByText('Update select')) + + await waitFor(() => expect(getByText('Store: 10')).toBeInTheDocument()) + expect(getByText('Number rendered: 2')).toBeInTheDocument() + + await user.click(getByText('Update ignored')) + expect(getByText('Number rendered: 2')).toBeInTheDocument() + }) +}) diff --git a/packages/store/src/use-store.ts b/packages/store/src/use-store.ts new file mode 100644 index 0000000000..1e9ec969ee --- /dev/null +++ b/packages/store/src/use-store.ts @@ -0,0 +1,24 @@ +import type { AnyUpdater, Store } from './store' +import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector.js' +import { shallow } from './shallow' + +export type NoInfer = [T][T extends any ? 0 : never] + +export function useStore< + TState, + TSelected = NoInfer, + TUpdater extends AnyUpdater = AnyUpdater +>( + store: Store, + selector: (state: NoInfer) => TSelected = (d) => d as any +) { + const slice = useSyncExternalStoreWithSelector( + store.subscribe, + () => store.state, + () => store.state, + selector, + shallow + ) + + return slice +} diff --git a/packages/store/tsconfig.json b/packages/store/tsconfig.json new file mode 100644 index 0000000000..9907687682 --- /dev/null +++ b/packages/store/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declarationDir": "declarations" + }, + "include": ["./src"], + "exclude": [ + "node_modules", + "dist", + "**/*.test.*", + "**/*.stories.*", + "**/*test-utils*" + ] +} diff --git a/packages/store/tsup.config.ts b/packages/store/tsup.config.ts new file mode 100644 index 0000000000..752fe81784 --- /dev/null +++ b/packages/store/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'tsup' + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['cjs', 'esm'], + external: ['react'], + splitting: false, + sourcemap: true, + clean: true, + dts: true, +}) diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 4c7be86c31..39bec48f2b 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -40,6 +40,7 @@ "dependencies": { "@testing-library/dom": "9.3.3", "@testing-library/jest-dom": "6.1.4", - "@testing-library/react": "14.1.2" + "@testing-library/react": "14.1.2", + "@testing-library/user-event": "14.5.1" } } diff --git a/packages/test-utils/src/react.ts b/packages/test-utils/src/react.ts index 4c421ecc23..57ae296037 100644 --- a/packages/test-utils/src/react.ts +++ b/packages/test-utils/src/react.ts @@ -4,6 +4,8 @@ import { act, screen, fireEvent, + waitFor, } from '@testing-library/react' +import userEvent from '@testing-library/user-event' -export { render, renderHook, act, screen, fireEvent } +export { render, renderHook, act, screen, fireEvent, waitFor, userEvent } diff --git a/packages/theme/src/get-preflight.ts b/packages/theme/src/get-preflight.ts index 6943bdfb3b..17c85b0514 100644 --- a/packages/theme/src/get-preflight.ts +++ b/packages/theme/src/get-preflight.ts @@ -1,7 +1,7 @@ import { constants } from '@vtex/shoreline-utils' export const getPreflight = (prefix = constants.dsPrefix) => ` -@layer sl-reset, sl-base, sl-tokens, sl-components; +@layer sl-reset, sl-base, sl-tokens, sl-components, sl-expended-components; @layer sl-reset { *, *::before, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46984aa61a..bc9967f71f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -339,6 +339,9 @@ importers: '@react-aria/datepicker': specifier: 3.9.0 version: 3.9.0(react-dom@18.2.0)(react@18.2.0) + '@react-aria/i18n': + specifier: 3.9.0 + version: 3.9.0(react@18.2.0) '@react-stately/calendar': specifier: 3.4.2 version: 3.4.2(react@18.2.0) @@ -348,6 +351,9 @@ importers: '@vtex/shoreline-icons': specifier: workspace version: link:../icons + '@vtex/shoreline-store': + specifier: workspace + version: link:../store '@vtex/shoreline-utils': specifier: workspace version: link:../utils @@ -435,6 +441,22 @@ importers: specifier: 20.10.0 version: 20.10.0 + packages/store: + dependencies: + react: + specifier: '>=18' + version: 18.2.0 + react-dom: + specifier: '>=18' + version: 18.2.0(react@18.2.0) + use-sync-external-store: + specifier: 1.2.0 + version: 1.2.0(react@18.2.0) + devDependencies: + '@types/use-sync-external-store': + specifier: 0.0.6 + version: 0.0.6 + packages/stylelint: dependencies: stylelint: @@ -452,6 +474,9 @@ importers: '@testing-library/react': specifier: 14.1.2 version: 14.1.2(react-dom@18.2.0)(react@18.2.0) + '@testing-library/user-event': + specifier: 14.5.1 + version: 14.5.1(@testing-library/dom@9.3.3) devDependencies: vitest: specifier: 0.34.6 @@ -5950,7 +5975,7 @@ packages: dependencies: '@babel/runtime': 7.22.6 '@react-aria/focus': 3.14.3(react@18.2.0) - '@react-aria/i18n': 3.5.1(react@18.2.0) + '@react-aria/i18n': 3.9.0(react@18.2.0) '@react-aria/interactions': 3.19.1(react@18.2.0) '@react-aria/utils': 3.21.1(react@18.2.0) '@react-stately/collections': 3.4.2(react@18.2.0) @@ -5966,7 +5991,7 @@ packages: react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 dependencies: '@babel/runtime': 7.22.6 - '@react-aria/i18n': 3.5.1(react@18.2.0) + '@react-aria/i18n': 3.9.0(react@18.2.0) '@react-aria/live-announcer': 3.1.1 '@react-aria/utils': 3.21.1(react@18.2.0) '@react-types/button': 3.6.0(react@18.2.0) @@ -7776,6 +7801,15 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@testing-library/user-event@14.5.1(@testing-library/dom@9.3.3): + resolution: {integrity: sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + dependencies: + '@testing-library/dom': 9.3.3 + dev: false + /@theguild/remark-mermaid@0.0.4(react@18.2.0): resolution: {integrity: sha512-C1gssw07eURtCwzXqZZdvyV/eawQ/cXfARaXIgBU9orffox+/YQ+exxmNu9v16NSGzAVsGF4qEVHvCOcCR/FpQ==} peerDependencies: @@ -8217,6 +8251,10 @@ packages: /@types/unist@3.0.0: resolution: {integrity: sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==} + /@types/use-sync-external-store@0.0.6: + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + dev: true + /@types/websocket@1.0.2: resolution: {integrity: sha512-B5m9aq7cbbD/5/jThEr33nUY8WEfVi6A2YKCTOvw5Ldy7mtsOkqRvGjnzy6g7iMMDsgu7xREuCzqATLDLQVKcQ==} dependencies: @@ -8757,7 +8795,7 @@ packages: react-dom: ^16.9 || ^17.0 dependencies: '@babel/core': 7.23.5 - '@react-aria/i18n': 3.5.1(react@18.2.0) + '@react-aria/i18n': 3.9.0(react@18.2.0) '@react-aria/listbox': 3.6.0(react@18.2.0) '@react-aria/spinbutton': 3.1.2(react-dom@18.2.0)(react@18.2.0) '@react-aria/utils': 3.21.1(react@18.2.0)