@intlayer/design-system 9.3.3 → 9.4.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.
- package/README.md +3 -3
- package/dist/esm/api/hooks/analytics.mjs +6 -5
- package/dist/esm/api/hooks/analytics.mjs.map +1 -1
- package/dist/esm/api/index.mjs +2 -2
- package/dist/esm/api/useAuth/useOAuth2.mjs.map +1 -1
- package/dist/esm/api/useIntlayerAPI.mjs +1 -3
- package/dist/esm/api/useIntlayerAPI.mjs.map +1 -1
- package/dist/esm/api/useUser/index.mjs +1 -1
- package/dist/esm/components/Accordion/Accordion.mjs +2 -2
- package/dist/esm/components/Accordion/Accordion.mjs.map +1 -1
- package/dist/esm/components/Carousel/index.mjs +48 -30
- package/dist/esm/components/Carousel/index.mjs.map +1 -1
- package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs +1 -1
- package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs.map +1 -1
- package/dist/esm/components/Form/elements/OTPElement.mjs +4 -4
- package/dist/esm/components/Form/elements/OTPElement.mjs.map +1 -1
- package/dist/esm/components/Link/Link.mjs +10 -5
- package/dist/esm/components/Link/Link.mjs.map +1 -1
- package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs +1 -1
- package/dist/esm/components/Modal/Modal.mjs +2 -2
- package/dist/esm/components/Navbar/MobileNavbar.mjs +25 -3
- package/dist/esm/components/Navbar/MobileNavbar.mjs.map +1 -1
- package/dist/esm/components/Pagination/Pagination.mjs +4 -4
- package/dist/esm/components/Pagination/Pagination.mjs.map +1 -1
- package/dist/esm/components/Pattern/GridDistortionPattern.mjs +3 -2
- package/dist/esm/components/Pattern/GridDistortionPattern.mjs.map +1 -1
- package/dist/esm/components/Popover/dynamic.mjs +68 -24
- package/dist/esm/components/Popover/dynamic.mjs.map +1 -1
- package/dist/esm/components/RightDrawer/RightDrawer.mjs +34 -10
- package/dist/esm/components/RightDrawer/RightDrawer.mjs.map +1 -1
- package/dist/esm/components/RightDrawer/index.mjs +2 -2
- package/dist/esm/components/RightDrawer/useRightDrawer.mjs +37 -5
- package/dist/esm/components/RightDrawer/useRightDrawer.mjs.map +1 -1
- package/dist/esm/components/SwitchSelector/useSwitchSelector.mjs +2 -2
- package/dist/esm/components/SwitchSelector/useSwitchSelector.mjs.map +1 -1
- package/dist/esm/components/Tab/Tab.mjs +21 -4
- package/dist/esm/components/Tab/Tab.mjs.map +1 -1
- package/dist/esm/components/TabSelector/TabSelector.mjs +2 -2
- package/dist/esm/components/TabSelector/TabSelector.mjs.map +1 -1
- package/dist/esm/components/TechLogo/TechLogo.mjs +1 -0
- package/dist/esm/components/TechLogo/TechLogo.mjs.map +1 -1
- package/dist/esm/components/TechLogo/logos/Elysia.mjs +62 -0
- package/dist/esm/components/TechLogo/logos/Elysia.mjs.map +1 -0
- package/dist/esm/components/TechLogo/techLogos.mjs +3 -1
- package/dist/esm/components/TechLogo/techLogos.mjs.map +1 -1
- package/dist/esm/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs +10 -18
- package/dist/esm/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs.map +1 -1
- package/dist/esm/components/ThemeSwitcherDropDown/MobileThemeSwitcher.mjs +14 -13
- package/dist/esm/components/ThemeSwitcherDropDown/MobileThemeSwitcher.mjs.map +1 -1
- package/dist/esm/components/index.mjs +2 -2
- package/dist/esm/hooks/index.mjs +9 -9
- package/dist/esm/hooks/useItemSelector.mjs +12 -9
- package/dist/esm/hooks/useItemSelector.mjs.map +1 -1
- package/dist/esm/hooks/useScrollBlockage/index.mjs +9 -10
- package/dist/esm/hooks/useScrollBlockage/index.mjs.map +1 -1
- package/dist/esm/hooks/useScrollDetection.mjs +15 -2
- package/dist/esm/hooks/useScrollDetection.mjs.map +1 -1
- package/dist/esm/hooks/useScrollY.mjs +64 -45
- package/dist/esm/hooks/useScrollY.mjs.map +1 -1
- package/dist/esm/hooks/useStickToBottom.mjs +69 -0
- package/dist/esm/hooks/useStickToBottom.mjs.map +1 -0
- package/dist/esm/providers/ThemeProvider.mjs +143 -0
- package/dist/esm/providers/ThemeProvider.mjs.map +1 -0
- package/dist/esm/providers/index.mjs +2 -1
- package/dist/esm/routes.mjs +4 -1
- package/dist/esm/routes.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +3 -1
- package/dist/esm/utils/observeScrollExtent.mjs +39 -0
- package/dist/esm/utils/observeScrollExtent.mjs.map +1 -0
- package/dist/esm/utils/scheduleFrameTask.mjs +44 -0
- package/dist/esm/utils/scheduleFrameTask.mjs.map +1 -0
- package/dist/types/api/hooks/analytics.d.ts +5 -3
- package/dist/types/api/hooks/analytics.d.ts.map +1 -1
- package/dist/types/api/useIntlayerAPI.d.ts +12 -0
- package/dist/types/api/useIntlayerAPI.d.ts.map +1 -1
- package/dist/types/components/Carousel/index.d.ts.map +1 -1
- package/dist/types/components/Navbar/MobileNavbar.d.ts.map +1 -1
- package/dist/types/components/Popover/dynamic.d.ts.map +1 -1
- package/dist/types/components/RightDrawer/RightDrawer.d.ts.map +1 -1
- package/dist/types/components/RightDrawer/index.d.ts +2 -2
- package/dist/types/components/RightDrawer/useRightDrawer.d.ts +34 -1
- package/dist/types/components/RightDrawer/useRightDrawer.d.ts.map +1 -1
- package/dist/types/components/Tab/Tab.d.ts.map +1 -1
- package/dist/types/components/TechLogo/TechLogo.d.ts.map +1 -1
- package/dist/types/components/TechLogo/logos/Elysia.d.ts +6 -0
- package/dist/types/components/TechLogo/logos/Elysia.d.ts.map +1 -0
- package/dist/types/components/TechLogo/techLogos.d.ts +3 -2
- package/dist/types/components/TechLogo/types.d.ts +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.d.ts +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.d.ts.map +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/MobileThemeSwitcher.d.ts +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/MobileThemeSwitcher.d.ts.map +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/types.d.ts +3 -1
- package/dist/types/components/ThemeSwitcherDropDown/types.d.ts.map +1 -1
- package/dist/types/components/index.d.ts +2 -2
- package/dist/types/hooks/index.d.ts +2 -2
- package/dist/types/hooks/useItemSelector.d.ts +2 -0
- package/dist/types/hooks/useItemSelector.d.ts.map +1 -1
- package/dist/types/hooks/useScrollBlockage/index.d.ts.map +1 -1
- package/dist/types/hooks/useScrollDetection.d.ts.map +1 -1
- package/dist/types/hooks/useScrollY.d.ts +10 -0
- package/dist/types/hooks/useScrollY.d.ts.map +1 -1
- package/dist/types/hooks/useStickToBottom.d.ts +41 -0
- package/dist/types/hooks/useStickToBottom.d.ts.map +1 -0
- package/dist/types/providers/ThemeProvider.d.ts +41 -0
- package/dist/types/providers/ThemeProvider.d.ts.map +1 -0
- package/dist/types/providers/index.d.ts +2 -1
- package/dist/types/routes.d.ts +4 -1
- package/dist/types/routes.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +3 -1
- package/dist/types/utils/observeScrollExtent.d.ts +22 -0
- package/dist/types/utils/observeScrollExtent.d.ts.map +1 -0
- package/dist/types/utils/scheduleFrameTask.d.ts +25 -0
- package/dist/types/utils/scheduleFrameTask.d.ts.map +1 -0
- package/package.json +24 -24
- package/dist/esm/hooks/useIsDarkMode.mjs +0 -17
- package/dist/esm/hooks/useIsDarkMode.mjs.map +0 -1
- package/dist/types/hooks/useIsDarkMode.d.ts +0 -5
- package/dist/types/hooks/useIsDarkMode.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -194,8 +194,8 @@ Explore our comprehensive documentation to get started with Intlayer and learn h
|
|
|
194
194
|
<li><a href="https://intlayer.org/doc/environment/vite-and-vue" rel=''>Vite + Vue</a></li>
|
|
195
195
|
<li><a href="https://intlayer.org/doc/environment/nuxt-and-vue" rel=''>Vite + Nuxt</a></li>
|
|
196
196
|
<li><a href="https://intlayer.org/doc/environment/vite-and-solid" rel=''>Vite + Solid</a></li>
|
|
197
|
-
<li><a href="https://intlayer.org/doc/environment/
|
|
198
|
-
<li><a href="https://intlayer.org/doc/environment/express" rel=''>Express</a></li>
|
|
197
|
+
<li><a href="https://intlayer.org/doc/environment/solid-start" rel=''>Solid Start</a></li>
|
|
198
|
+
<li><a href="https://intlayer.org/doc/environment/angular" rel=''>Angular</a></li> <li><a href="https://intlayer.org/doc/environment/express" rel=''>Express</a></li>
|
|
199
199
|
<li><a href="https://intlayer.org/doc/environment/nest" rel=''>NestJS</a></li>
|
|
200
200
|
</ul>
|
|
201
201
|
</details>
|
|
@@ -278,4 +278,4 @@ Contribute on [GitHub](https://github.com/aymericzip/intlayer), [GitLab](https:/
|
|
|
278
278
|
|
|
279
279
|
If you like Intlayer, give us a ⭐ on GitHub. It helps others discover the project! [See why GitHub Stars matter](https://github.com/aymericzip/intlayer/blob/main/CONTRIBUTING.md#why-github-stars-matter-).
|
|
280
280
|
|
|
281
|
-
[](https://star-history.dera.page/#aymericzip/intlayer&Date)
|
|
@@ -6,21 +6,22 @@ import { useAppQuery } from "./utils.mjs";
|
|
|
6
6
|
//#region src/api/hooks/analytics.ts
|
|
7
7
|
/**
|
|
8
8
|
* Fetches the audience report for the active project: distinct visitors
|
|
9
|
-
* (today / 7d / window), page views, the
|
|
9
|
+
* (today / 7d / window), page views, the evolution series, and locale +
|
|
10
10
|
* country breakdowns.
|
|
11
11
|
*
|
|
12
|
-
* @param
|
|
12
|
+
* @param range - Rolling window, either a named range (`1h`, `24h`, `7d`,
|
|
13
|
+
* `30d`, `90d`, `6mo`, `1y`, `3y`) or a number of days (default 30).
|
|
13
14
|
* @param options - Extra react-query options.
|
|
14
15
|
*/
|
|
15
|
-
const useGetAnalyticsAudience = (
|
|
16
|
+
const useGetAnalyticsAudience = (range = 30, options) => {
|
|
16
17
|
const analyticsAPI = useAnalyticsAPI();
|
|
17
18
|
return useAppQuery({
|
|
18
19
|
queryKey: [
|
|
19
20
|
"analytics",
|
|
20
21
|
"audience",
|
|
21
|
-
|
|
22
|
+
range
|
|
22
23
|
],
|
|
23
|
-
queryFn: ({ signal }) => analyticsAPI.getAudience(
|
|
24
|
+
queryFn: ({ signal }) => analyticsAPI.getAudience(range, { signal }),
|
|
24
25
|
requireUser: true,
|
|
25
26
|
requireOrganization: true,
|
|
26
27
|
requireProject: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.mjs","names":[],"sources":["../../../../src/api/hooks/analytics.ts"],"sourcesContent":["'use client';\n\nimport type { UseQueryOptions } from '@tanstack/react-query';\nimport { useAnalyticsAPI } from '../useIntlayerAPI';\nimport { useAppQuery } from './utils';\n\n/**\n * Fetches the audience report for the active project: distinct visitors\n * (today / 7d / window), page views, the
|
|
1
|
+
{"version":3,"file":"analytics.mjs","names":[],"sources":["../../../../src/api/hooks/analytics.ts"],"sourcesContent":["'use client';\n\nimport type { AudienceRange } from '@intlayer/backend';\nimport type { UseQueryOptions } from '@tanstack/react-query';\nimport { useAnalyticsAPI } from '../useIntlayerAPI';\nimport { useAppQuery } from './utils';\n\n/**\n * Fetches the audience report for the active project: distinct visitors\n * (today / 7d / window), page views, the evolution series, and locale +\n * country breakdowns.\n *\n * @param range - Rolling window, either a named range (`1h`, `24h`, `7d`,\n * `30d`, `90d`, `6mo`, `1y`, `3y`) or a number of days (default 30).\n * @param options - Extra react-query options.\n */\nexport const useGetAnalyticsAudience = (\n range: AudienceRange | number = 30,\n options?: Partial<UseQueryOptions>\n) => {\n const analyticsAPI = useAnalyticsAPI();\n\n return useAppQuery({\n queryKey: ['analytics', 'audience', range],\n queryFn: ({ signal }) => analyticsAPI.getAudience(range, { signal }),\n requireUser: true,\n requireOrganization: true,\n requireProject: true,\n ...options,\n });\n};\n\n/**\n * Fetches per-content exposure totals — \"which content is actually shown\".\n *\n * @param options - Extra react-query options.\n */\nexport const useGetContentStats = (options?: Partial<UseQueryOptions>) => {\n const analyticsAPI = useAnalyticsAPI();\n\n return useAppQuery({\n queryKey: ['analytics', 'content-stats'],\n queryFn: ({ signal }) => analyticsAPI.getContentStats({ signal }),\n requireUser: true,\n requireOrganization: true,\n requireProject: true,\n ...options,\n });\n};\n\n/**\n * Fetches A/B experiment results for the active project.\n *\n * @param experimentKey - The experiment to evaluate.\n * @param options - Extra react-query options.\n */\nexport const useGetExperimentResults = (\n experimentKey: string,\n options?: Partial<UseQueryOptions>\n) => {\n const analyticsAPI = useAnalyticsAPI();\n\n return useAppQuery({\n queryKey: ['analytics', 'experiment', experimentKey],\n queryFn: ({ signal }) =>\n analyticsAPI.getExperimentResults(experimentKey, { signal }),\n requireUser: true,\n requireOrganization: true,\n requireProject: true,\n enabled: Boolean(experimentKey),\n ...options,\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,MAAa,2BACX,QAAgC,IAChC,YACG;CACH,MAAM,eAAe,gBAAgB;CAErC,OAAO,YAAY;EACjB,UAAU;GAAC;GAAa;GAAY;EAAK;EACzC,UAAU,EAAE,aAAa,aAAa,YAAY,OAAO,EAAE,OAAO,CAAC;EACnE,aAAa;EACb,qBAAqB;EACrB,gBAAgB;EAChB,GAAG;CACL,CAAC;AACH;;;;;;AAOA,MAAa,sBAAsB,YAAuC;CACxE,MAAM,eAAe,gBAAgB;CAErC,OAAO,YAAY;EACjB,UAAU,CAAC,aAAa,eAAe;EACvC,UAAU,EAAE,aAAa,aAAa,gBAAgB,EAAE,OAAO,CAAC;EAChE,aAAa;EACb,qBAAqB;EACrB,gBAAgB;EAChB,GAAG;CACL,CAAC;AACH;;;;;;;AAQA,MAAa,2BACX,eACA,YACG;CACH,MAAM,eAAe,gBAAgB;CAErC,OAAO,YAAY;EACjB,UAAU;GAAC;GAAa;GAAc;EAAa;EACnD,UAAU,EAAE,aACV,aAAa,qBAAqB,eAAe,EAAE,OAAO,CAAC;EAC7D,aAAa;EACb,qBAAqB;EACrB,gBAAgB;EAChB,SAAS,QAAQ,aAAa;EAC9B,GAAG;CACL,CAAC;AACH"}
|
package/dist/esm/api/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { useAiAPI, useAnalyticsAPI, useAssetAPI, useAuditAPI, useBitbucketAPI, useDictionaryAPI, useEditorAPI, useEnvironmentAPI, useGithubAPI, useGitlabAPI, useIntlayerAuth, useIntlayerOAuth, useIntlayerOAuthOptions, useNewsletterAPI, useOAuthAPI, useOrganizationAPI, useProjectAPI, useReviewerAPI, useSearchAPI, useShowcaseProjectAPI, useStripeAPI, useTagAPI, useTranslateAPI, useUserAPI } from "./useIntlayerAPI.mjs";
|
|
2
|
+
import { useAddPasskey, useAskResetPassword, useChangePassword, useDeletePasskey, useDeleteSSOProvider, useDisableTwoFactor, useEnableTwoFactor, useGetUserByAccount, useGetVerifyEmailStatus, useLinkSocial, useListAccounts, useListPasskeys, useListSSOProviders, useLogin, useLogout, useRegister, useRegisterSSO, useResetPassword, useSignInMagicLink, useSignInPasskey, useSignInSSO, useUnlinkAccount, useVerifyBackupCode, useVerifyEmail, useVerifyTotp } from "./hooks/auth.mjs";
|
|
1
3
|
import { useOAuth2 } from "./useAuth/useOAuth2.mjs";
|
|
2
4
|
import { useSession } from "./useAuth/useSession.mjs";
|
|
3
5
|
import { useAuth } from "./useAuth/useAuth.mjs";
|
|
4
|
-
import { useAiAPI, useAnalyticsAPI, useAssetAPI, useAuditAPI, useBitbucketAPI, useDictionaryAPI, useEditorAPI, useEnvironmentAPI, useGithubAPI, useGitlabAPI, useIntlayerAuth, useIntlayerOAuth, useIntlayerOAuthOptions, useNewsletterAPI, useOAuthAPI, useOrganizationAPI, useProjectAPI, useReviewerAPI, useSearchAPI, useShowcaseProjectAPI, useStripeAPI, useTagAPI, useTranslateAPI, useUserAPI } from "./useIntlayerAPI.mjs";
|
|
5
|
-
import { useAddPasskey, useAskResetPassword, useChangePassword, useDeletePasskey, useDeleteSSOProvider, useDisableTwoFactor, useEnableTwoFactor, useGetUserByAccount, useGetVerifyEmailStatus, useLinkSocial, useListAccounts, useListPasskeys, useListSSOProviders, useLogin, useLogout, useRegister, useRegisterSSO, useResetPassword, useSignInMagicLink, useSignInPasskey, useSignInSSO, useUnlinkAccount, useVerifyBackupCode, useVerifyEmail, useVerifyTotp } from "./hooks/auth.mjs";
|
|
6
6
|
import { useUser } from "./useUser/index.mjs";
|
|
7
7
|
import { useAskDocQuestion, useAuditContentDeclaration, useAuditContentDeclarationField, useAuditContentDeclarationMetadata, useAuditTag, useAutocomplete, useChat, useCustomQuery, useGetAIStats, useTranslateJSONDeclaration } from "./hooks/ai.mjs";
|
|
8
8
|
import { useGetAnalyticsAudience, useGetContentStats, useGetExperimentResults } from "./hooks/analytics.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOAuth2.mjs","names":[],"sources":["../../../../src/api/useAuth/useOAuth2.ts"],"sourcesContent":["'use client';\n\nimport { getOAuthAPI } from '@intlayer/api';\nimport type { OAuth2Token } from '@intlayer/backend';\nimport { editor } from '@intlayer/config/built';\nimport { useConfiguration } from '@intlayer/editor-react';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { useQuery } from '@tanstack/react-query';\nimport { defu } from 'defu';\nimport { useIsAuthFetchEnabled } from '../../providers/DeferredAuthProvider';\n\nexport type UseOAuth2Result = {\n /** The OAuth2 token: `undefined` while unfetched, `null` when none was issued. */\n oAuth2AccessToken: OAuth2Token | null | undefined;\n};\n\n/**\n * Fetches the OAuth2 client-credentials token for the configured editor client.\n *\n * The query stays disabled when no client credentials are configured, and — under\n * a `DeferredAuthProvider` — until the page has finished loading.\n */\nexport const useOAuth2 = (\n intlayerConfiguration?: Pick<IntlayerConfig, 'editor'>\n): UseOAuth2Result => {\n const configuration = useConfiguration();\n const config = defu(intlayerConfiguration, configuration, {\n editor,\n }) as IntlayerConfig;\n\n const intlayerAPI = getOAuthAPI(undefined, config);\n const isAuthFetchEnabled = useIsAuthFetchEnabled();\n\n const { data } = useQuery({\n queryKey: ['oAuth2AccessToken'],\n queryFn: intlayerAPI.getOAuth2AccessToken,\n // Held back until after page load when a `DeferredAuthProvider` is mounted\n // (content sites); unconditional everywhere else (the CMS dashboard).\n enabled:\n !!(config.editor.clientId && config.editor.clientSecret) &&\n isAuthFetchEnabled,\n staleTime: 0,\n gcTime: 0,\n refetchOnWindowFocus: false,\n refetchOnMount: false,\n refetchOnReconnect: false,\n refetchInterval: false,\n refetchIntervalInBackground: false,\n });\n\n const oAuth2AccessToken = data?.data;\n\n return {\n oAuth2AccessToken,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAsBA,MAAa,aACX,0BACoB;CACpB,MAAM,gBAAgB,iBAAiB;CACvC,MAAM,SAAS,KAAK,uBAAuB,eAAe,EACxD,OACF,CAAC;CAED,MAAM,cAAc,YAAY,QAAW,MAAM;CACjD,MAAM,qBAAqB,sBAAsB;CAEjD,MAAM,EAAE,SAAS,SAAS;EACxB,UAAU,CAAC,mBAAmB;EAC9B,SAAS,YAAY;
|
|
1
|
+
{"version":3,"file":"useOAuth2.mjs","names":[],"sources":["../../../../src/api/useAuth/useOAuth2.ts"],"sourcesContent":["'use client';\n\nimport { getOAuthAPI } from '@intlayer/api';\nimport type { OAuth2Token } from '@intlayer/backend';\nimport { editor } from '@intlayer/config/built';\nimport { useConfiguration } from '@intlayer/editor-react';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { useQuery } from '@tanstack/react-query';\nimport { defu } from 'defu';\nimport { useIsAuthFetchEnabled } from '../../providers/DeferredAuthProvider';\n\nexport type UseOAuth2Result = {\n /** The OAuth2 token: `undefined` while unfetched, `null` when none was issued. */\n oAuth2AccessToken: OAuth2Token | null | undefined;\n};\n\n/**\n * Fetches the OAuth2 client-credentials token for the configured editor client.\n *\n * The query stays disabled when no client credentials are configured, and — under\n * a `DeferredAuthProvider` — until the page has finished loading.\n */\nexport const useOAuth2 = (\n intlayerConfiguration?: Pick<IntlayerConfig, 'editor'>\n): UseOAuth2Result => {\n const configuration = useConfiguration();\n const config = defu(intlayerConfiguration, configuration, {\n editor,\n }) as IntlayerConfig;\n\n const intlayerAPI = getOAuthAPI(undefined, config);\n const isAuthFetchEnabled = useIsAuthFetchEnabled();\n\n const { data } = useQuery({\n queryKey: ['oAuth2AccessToken'],\n queryFn: intlayerAPI.getOAuth2AccessToken,\n // `client_credentials` is a confidential-client grant, so this query only\n // ever runs where a `clientSecret` is legitimately available — never in a\n // browser, since the generated build-time configuration omits it and\n // `@intlayer/config/secrets` resolves to an inert stub there.\n //\n // Held back until after page load when a `DeferredAuthProvider` is mounted\n // (content sites); unconditional everywhere else (the CMS dashboard).\n enabled:\n !!(config.editor.clientId && config.editor.clientSecret) &&\n isAuthFetchEnabled,\n staleTime: 0,\n gcTime: 0,\n refetchOnWindowFocus: false,\n refetchOnMount: false,\n refetchOnReconnect: false,\n refetchInterval: false,\n refetchIntervalInBackground: false,\n });\n\n const oAuth2AccessToken = data?.data;\n\n return {\n oAuth2AccessToken,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAsBA,MAAa,aACX,0BACoB;CACpB,MAAM,gBAAgB,iBAAiB;CACvC,MAAM,SAAS,KAAK,uBAAuB,eAAe,EACxD,OACF,CAAC;CAED,MAAM,cAAc,YAAY,QAAW,MAAM;CACjD,MAAM,qBAAqB,sBAAsB;CAEjD,MAAM,EAAE,SAAS,SAAS;EACxB,UAAU,CAAC,mBAAmB;EAC9B,SAAS,YAAY;EAQrB,SACE,CAAC,EAAE,OAAO,OAAO,YAAY,OAAO,OAAO,iBAC3C;EACF,WAAW;EACX,QAAQ;EACR,sBAAsB;EACtB,gBAAgB;EAChB,oBAAoB;EACpB,iBAAiB;EACjB,6BAA6B;CAC/B,CAAC;CAID,OAAO,EACL,mBAHwB,MAAM,KAIhC;AACF"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { getAuthAPI } from "../libs/auth.mjs";
|
|
4
|
-
import { useAuth } from "./useAuth/useAuth.mjs";
|
|
5
4
|
import { getIntlayerAPI } from "@intlayer/api";
|
|
6
5
|
import { getAiAPI } from "@intlayer/api/ai";
|
|
7
6
|
import { getAnalyticsAPI } from "@intlayer/api/analytics";
|
|
@@ -29,9 +28,8 @@ import { useConfiguration } from "@intlayer/editor-react";
|
|
|
29
28
|
//#region src/api/useIntlayerAPI.ts
|
|
30
29
|
const useIntlayerOAuthOptions = (props) => {
|
|
31
30
|
const configuration = useConfiguration();
|
|
32
|
-
const { oAuth2AccessToken } = useAuth();
|
|
33
31
|
const options = {
|
|
34
|
-
...
|
|
32
|
+
...props?.sessionToken && { headers: { Authorization: `Bearer ${props.sessionToken}` } },
|
|
35
33
|
...props?.options ?? {}
|
|
36
34
|
};
|
|
37
35
|
const rawConfig = props?.intlayerConfiguration ?? configuration;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useIntlayerAPI.mjs","names":["getOAuthAPI"],"sources":["../../../src/api/useIntlayerAPI.ts"],"sourcesContent":["'use client';\n\nimport {\n type FetcherOptions,\n getIntlayerAPI,\n type IntlayerAPI,\n} from '@intlayer/api';\nimport { getAiAPI } from '@intlayer/api/ai';\nimport { getAnalyticsAPI } from '@intlayer/api/analytics';\nimport { getAssetAPI } from '@intlayer/api/asset';\nimport { getAuditAPI } from '@intlayer/api/audit';\nimport { getBitbucketAPI } from '@intlayer/api/bitbucket';\nimport { getDictionaryAPI } from '@intlayer/api/dictionary';\nimport { getEditorAPI } from '@intlayer/api/editor';\nimport { getEnvironmentAPI } from '@intlayer/api/environment';\nimport { getGithubAPI } from '@intlayer/api/github';\nimport { getGitlabAPI } from '@intlayer/api/gitlab';\nimport { getNewsletterAPI } from '@intlayer/api/newsletter';\nimport { getOAuthAPI } from '@intlayer/api/oAuth';\nimport { getOrganizationAPI } from '@intlayer/api/organization';\nimport { getProjectAPI } from '@intlayer/api/project';\nimport { getReviewerAPI } from '@intlayer/api/reviewer';\nimport { getSearchAPI } from '@intlayer/api/search';\nimport { getShowcaseProjectAPI } from '@intlayer/api/showcaseProject';\nimport { getStripeAPI } from '@intlayer/api/stripe';\nimport { getTagAPI } from '@intlayer/api/tag';\nimport { getTranslateAPI } from '@intlayer/api/translate';\nimport { getUserAPI } from '@intlayer/api/user';\nimport { useConfiguration } from '@intlayer/editor-react';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { type AuthAPI, getAuthAPI } from '../libs/auth';\nimport { useAuth } from './useAuth';\n\nexport type UseIntlayerAuthProps = {\n options?: FetcherOptions;\n intlayerConfiguration?: IntlayerConfig;\n};\n\nexport const useIntlayerOAuthOptions = (props?: UseIntlayerAuthProps) => {\n const configuration = useConfiguration();\n const { oAuth2AccessToken } = useAuth();\n\n const options = {\n ...(oAuth2AccessToken && {\n headers: {\n Authorization: `Bearer ${oAuth2AccessToken.accessToken}`,\n },\n }),\n ...(props?.options ?? {}),\n };\n\n const rawConfig = props?.intlayerConfiguration ?? configuration;\n // Only use the config if it's fully populated; an empty object (e.g. while\n // the session is still loading) has no `editor` key and would crash API\n // getters that do `intlayerConfig.editor.backendURL`.\n const resolvedConfig = rawConfig?.editor ? rawConfig : undefined;\n\n return { options, resolvedConfig };\n};\n\nexport const useIntlayerOAuth = (props?: UseIntlayerAuthProps): IntlayerAPI => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getIntlayerAPI(options, resolvedConfig);\n};\n\nexport const useIntlayerAuth = (props?: UseIntlayerAuthProps): AuthAPI => {\n const configuration = useConfiguration();\n\n return getAuthAPI(props?.intlayerConfiguration ?? configuration);\n};\n\nexport const useOrganizationAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getOrganizationAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getOrganizationAPI(options, resolvedConfig);\n};\n\nexport const useProjectAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getProjectAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getProjectAPI(options, resolvedConfig);\n};\n\nexport const useUserAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getUserAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getUserAPI(options, resolvedConfig);\n};\n\nexport const useOAuthAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getOAuthAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getOAuthAPI(options, resolvedConfig);\n};\n\nexport const useDictionaryAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getDictionaryAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getDictionaryAPI(options, resolvedConfig);\n};\n\nexport const useStripeAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getStripeAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getStripeAPI(options, resolvedConfig);\n};\n\nexport const useAiAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getAiAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getAiAPI(options, resolvedConfig);\n};\n\nexport const useAuditAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getAuditAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getAuditAPI(options, resolvedConfig);\n};\n\nexport const useAnalyticsAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getAnalyticsAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getAnalyticsAPI(options, resolvedConfig);\n};\n\nexport const useTagAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getTagAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getTagAPI(options, resolvedConfig);\n};\n\nexport const useSearchAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getSearchAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getSearchAPI(options, resolvedConfig);\n};\n\nexport const useEditorAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getEditorAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getEditorAPI(options, resolvedConfig);\n};\n\nexport const useNewsletterAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getNewsletterAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getNewsletterAPI(options, resolvedConfig);\n};\n\nexport const useGithubAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getGithubAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getGithubAPI(options, resolvedConfig);\n};\n\nexport const useGitlabAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getGitlabAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getGitlabAPI(options, resolvedConfig);\n};\n\nexport const useBitbucketAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getBitbucketAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getBitbucketAPI(options, resolvedConfig);\n};\n\nexport const useShowcaseProjectAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getShowcaseProjectAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getShowcaseProjectAPI(options, resolvedConfig);\n};\n\nexport const useTranslateAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getTranslateAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getTranslateAPI(options, resolvedConfig);\n};\n\nexport const useReviewerAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getReviewerAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getReviewerAPI(options, resolvedConfig);\n};\n\nexport const useEnvironmentAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getEnvironmentAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getEnvironmentAPI(options, resolvedConfig);\n};\n\nexport const useAssetAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getAssetAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getAssetAPI(options, resolvedConfig);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useIntlayerAPI.mjs","names":["getOAuthAPI"],"sources":["../../../src/api/useIntlayerAPI.ts"],"sourcesContent":["'use client';\n\nimport {\n type FetcherOptions,\n getIntlayerAPI,\n type IntlayerAPI,\n} from '@intlayer/api';\nimport { getAiAPI } from '@intlayer/api/ai';\nimport { getAnalyticsAPI } from '@intlayer/api/analytics';\nimport { getAssetAPI } from '@intlayer/api/asset';\nimport { getAuditAPI } from '@intlayer/api/audit';\nimport { getBitbucketAPI } from '@intlayer/api/bitbucket';\nimport { getDictionaryAPI } from '@intlayer/api/dictionary';\nimport { getEditorAPI } from '@intlayer/api/editor';\nimport { getEnvironmentAPI } from '@intlayer/api/environment';\nimport { getGithubAPI } from '@intlayer/api/github';\nimport { getGitlabAPI } from '@intlayer/api/gitlab';\nimport { getNewsletterAPI } from '@intlayer/api/newsletter';\nimport { getOAuthAPI } from '@intlayer/api/oAuth';\nimport { getOrganizationAPI } from '@intlayer/api/organization';\nimport { getProjectAPI } from '@intlayer/api/project';\nimport { getReviewerAPI } from '@intlayer/api/reviewer';\nimport { getSearchAPI } from '@intlayer/api/search';\nimport { getShowcaseProjectAPI } from '@intlayer/api/showcaseProject';\nimport { getStripeAPI } from '@intlayer/api/stripe';\nimport { getTagAPI } from '@intlayer/api/tag';\nimport { getTranslateAPI } from '@intlayer/api/translate';\nimport { getUserAPI } from '@intlayer/api/user';\nimport { useConfiguration } from '@intlayer/editor-react';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { type AuthAPI, getAuthAPI } from '../libs/auth';\n\nexport type UseIntlayerAuthProps = {\n options?: FetcherOptions;\n intlayerConfiguration?: IntlayerConfig;\n /**\n * A short-lived token identifying the **signed-in user**, sent as a bearer.\n *\n * Only needed when the session cookie cannot be used — the backend allows\n * credentialed requests for first-party origins only, so an app served from\n * another origin has to carry the user's identity explicitly.\n *\n * The project's `clientId` / `clientSecret` are deliberately not an option\n * here: they authenticate as the *project*, grant write access to all of its\n * content, and must never reach a browser.\n */\n sessionToken?: string;\n};\n\nexport const useIntlayerOAuthOptions = (props?: UseIntlayerAuthProps) => {\n const configuration = useConfiguration();\n\n // On a first-party origin the session cookie authenticates every call (the\n // fetcher sends `credentials: 'include'`), so no header is needed. Elsewhere\n // the caller supplies a user session token.\n const options = {\n ...(props?.sessionToken && {\n headers: {\n Authorization: `Bearer ${props.sessionToken}`,\n },\n }),\n ...(props?.options ?? {}),\n };\n\n const rawConfig = props?.intlayerConfiguration ?? configuration;\n // Only use the config if it's fully populated; an empty object (e.g. while\n // the session is still loading) has no `editor` key and would crash API\n // getters that do `intlayerConfig.editor.backendURL`.\n const resolvedConfig = rawConfig?.editor ? rawConfig : undefined;\n\n return { options, resolvedConfig };\n};\n\nexport const useIntlayerOAuth = (props?: UseIntlayerAuthProps): IntlayerAPI => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getIntlayerAPI(options, resolvedConfig);\n};\n\nexport const useIntlayerAuth = (props?: UseIntlayerAuthProps): AuthAPI => {\n const configuration = useConfiguration();\n\n return getAuthAPI(props?.intlayerConfiguration ?? configuration);\n};\n\nexport const useOrganizationAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getOrganizationAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getOrganizationAPI(options, resolvedConfig);\n};\n\nexport const useProjectAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getProjectAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getProjectAPI(options, resolvedConfig);\n};\n\nexport const useUserAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getUserAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getUserAPI(options, resolvedConfig);\n};\n\nexport const useOAuthAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getOAuthAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getOAuthAPI(options, resolvedConfig);\n};\n\nexport const useDictionaryAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getDictionaryAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getDictionaryAPI(options, resolvedConfig);\n};\n\nexport const useStripeAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getStripeAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getStripeAPI(options, resolvedConfig);\n};\n\nexport const useAiAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getAiAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getAiAPI(options, resolvedConfig);\n};\n\nexport const useAuditAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getAuditAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getAuditAPI(options, resolvedConfig);\n};\n\nexport const useAnalyticsAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getAnalyticsAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getAnalyticsAPI(options, resolvedConfig);\n};\n\nexport const useTagAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getTagAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getTagAPI(options, resolvedConfig);\n};\n\nexport const useSearchAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getSearchAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getSearchAPI(options, resolvedConfig);\n};\n\nexport const useEditorAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getEditorAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getEditorAPI(options, resolvedConfig);\n};\n\nexport const useNewsletterAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getNewsletterAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getNewsletterAPI(options, resolvedConfig);\n};\n\nexport const useGithubAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getGithubAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getGithubAPI(options, resolvedConfig);\n};\n\nexport const useGitlabAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getGitlabAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getGitlabAPI(options, resolvedConfig);\n};\n\nexport const useBitbucketAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getBitbucketAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getBitbucketAPI(options, resolvedConfig);\n};\n\nexport const useShowcaseProjectAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getShowcaseProjectAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getShowcaseProjectAPI(options, resolvedConfig);\n};\n\nexport const useTranslateAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getTranslateAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getTranslateAPI(options, resolvedConfig);\n};\n\nexport const useReviewerAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getReviewerAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getReviewerAPI(options, resolvedConfig);\n};\n\nexport const useEnvironmentAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getEnvironmentAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getEnvironmentAPI(options, resolvedConfig);\n};\n\nexport const useAssetAPI = (\n props?: UseIntlayerAuthProps\n): ReturnType<typeof getAssetAPI> => {\n const { options, resolvedConfig } = useIntlayerOAuthOptions(props);\n return getAssetAPI(options, resolvedConfig);\n};\n"],"mappings":"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"}
|
|
@@ -45,7 +45,7 @@ import { ChevronDown } from "lucide-react";
|
|
|
45
45
|
* <p>Controlled content</p>
|
|
46
46
|
* </Accordion>
|
|
47
47
|
*/
|
|
48
|
-
const Accordion = ({ children, header, isOpen, defaultIsOpen = false, onToggle, onClick, disabled = false, contentClassName, headerClassName, contentId, "aria-label": ariaLabel, ...props }) => {
|
|
48
|
+
const Accordion = ({ children, header, isOpen, defaultIsOpen = false, onToggle, onClick, disabled = false, contentClassName, headerClassName, contentId, "aria-label": ariaLabel, className, ...props }) => {
|
|
49
49
|
const isControlled = isOpen !== void 0;
|
|
50
50
|
const [internalIsOpen, setInternalIsOpen] = useState(defaultIsOpen);
|
|
51
51
|
const id = useId();
|
|
@@ -66,7 +66,7 @@ const Accordion = ({ children, header, isOpen, defaultIsOpen = false, onToggle,
|
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
return /* @__PURE__ */ jsxs("div", {
|
|
69
|
-
className: "w-full",
|
|
69
|
+
className: cn("w-full", className),
|
|
70
70
|
children: [/* @__PURE__ */ jsx(Button, {
|
|
71
71
|
variant: "hoverable",
|
|
72
72
|
color: "text",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Accordion.mjs","names":[],"sources":["../../../../src/components/Accordion/Accordion.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { ChevronDown } from 'lucide-react';\nimport {\n type FC,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n useId,\n useState,\n} from 'react';\nimport { Button, type ButtonProps } from '../Button';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\n\nexport interface AccordionProps\n extends Omit<ButtonProps, 'children' | 'onToggle'> {\n /** The content displayed in the accordion header */\n header: ReactNode;\n /** The collapsible content inside the accordion */\n children: ReactNode;\n /** Controls whether the accordion is open (controlled mode) */\n isOpen?: boolean;\n /** Default open state (uncontrolled mode) */\n defaultIsOpen?: boolean;\n /** Called when the accordion state changes */\n onToggle?: (isOpen: boolean) => void;\n /** Disable the accordion interaction */\n disabled?: boolean;\n /** Custom class for the content container */\n contentClassName?: string;\n /** Custom class for the header container */\n headerClassName?: string;\n /** Accessible label for screen readers */\n 'aria-label'?: string;\n /** ID for the accordion content (for aria-controls) */\n contentId?: string;\n}\n\n/**\n * Accordion component that allows the user to expand and collapse content.\n * It provides a header with a chevron icon that controls the visibility of the content.\n *\n * Features:\n * - Supports both controlled and uncontrolled modes\n * - Accessible with proper ARIA attributes\n * - Keyboard navigation support\n * - Smooth animations for expand/collapse\n * - Customizable styling\n *\n * @param header - The content of the header.\n * @param children - The content to be expanded and collapsed.\n * @param isOpen - Controlled state for whether the content is expanded.\n * @param defaultIsOpen - Default open state for uncontrolled mode.\n * @param onToggle - Callback when the accordion state changes.\n * @param disabled - Whether the accordion is disabled.\n * @param contentClassName - Custom class for the content container.\n * @param headerClassName - Custom class for the header.\n * @param contentId - ID for the content (used for aria-controls).\n *\n * @example\n * // Uncontrolled mode\n * <Accordion header=\"Accordion Header\" defaultIsOpen={true}>\n * <p>Accordion content</p>\n * </Accordion>\n *\n * @example\n * // Controlled mode\n * <Accordion\n * header=\"Controlled Accordion\"\n * isOpen={isOpen}\n * onToggle={setIsOpen}\n * >\n * <p>Controlled content</p>\n * </Accordion>\n */\nexport const Accordion: FC<AccordionProps> = ({\n children,\n header,\n isOpen,\n defaultIsOpen = false,\n onToggle,\n onClick,\n disabled = false,\n contentClassName,\n headerClassName,\n contentId,\n 'aria-label': ariaLabel,\n ...props\n}) => {\n // Determine if we're in controlled or uncontrolled mode\n const isControlled = isOpen !== undefined;\n const [internalIsOpen, setInternalIsOpen] = useState(defaultIsOpen);\n const id = useId();\n\n // Use controlled value if provided, otherwise use internal state\n const isExpandedState = isControlled ? isOpen : internalIsOpen;\n const isHidden = !isExpandedState;\n\n // Generate unique ID for content if not provided\n const generatedContentId = contentId ?? `${id}-accordion-content`;\n\n const handleToggle = (e: MouseEvent<HTMLButtonElement>) => {\n if (disabled) return;\n\n const newIsOpen = !isExpandedState;\n\n // Update internal state if uncontrolled\n if (!isControlled) {\n setInternalIsOpen(newIsOpen);\n }\n\n // Call external handlers\n onToggle?.(newIsOpen);\n onClick?.(e);\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n // Enter and Space should toggle the accordion\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleToggle(e as any);\n }\n };\n\n return (\n <div className
|
|
1
|
+
{"version":3,"file":"Accordion.mjs","names":[],"sources":["../../../../src/components/Accordion/Accordion.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { ChevronDown } from 'lucide-react';\nimport {\n type FC,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n useId,\n useState,\n} from 'react';\nimport { Button, type ButtonProps } from '../Button';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\n\nexport interface AccordionProps\n extends Omit<ButtonProps, 'children' | 'onToggle'> {\n /** The content displayed in the accordion header */\n header: ReactNode;\n /** The collapsible content inside the accordion */\n children: ReactNode;\n /** Controls whether the accordion is open (controlled mode) */\n isOpen?: boolean;\n /** Default open state (uncontrolled mode) */\n defaultIsOpen?: boolean;\n /** Called when the accordion state changes */\n onToggle?: (isOpen: boolean) => void;\n /** Disable the accordion interaction */\n disabled?: boolean;\n /** Custom class for the content container */\n contentClassName?: string;\n /** Custom class for the header container */\n headerClassName?: string;\n /** Accessible label for screen readers */\n 'aria-label'?: string;\n /** ID for the accordion content (for aria-controls) */\n contentId?: string;\n}\n\n/**\n * Accordion component that allows the user to expand and collapse content.\n * It provides a header with a chevron icon that controls the visibility of the content.\n *\n * Features:\n * - Supports both controlled and uncontrolled modes\n * - Accessible with proper ARIA attributes\n * - Keyboard navigation support\n * - Smooth animations for expand/collapse\n * - Customizable styling\n *\n * @param header - The content of the header.\n * @param children - The content to be expanded and collapsed.\n * @param isOpen - Controlled state for whether the content is expanded.\n * @param defaultIsOpen - Default open state for uncontrolled mode.\n * @param onToggle - Callback when the accordion state changes.\n * @param disabled - Whether the accordion is disabled.\n * @param contentClassName - Custom class for the content container.\n * @param headerClassName - Custom class for the header.\n * @param contentId - ID for the content (used for aria-controls).\n *\n * @example\n * // Uncontrolled mode\n * <Accordion header=\"Accordion Header\" defaultIsOpen={true}>\n * <p>Accordion content</p>\n * </Accordion>\n *\n * @example\n * // Controlled mode\n * <Accordion\n * header=\"Controlled Accordion\"\n * isOpen={isOpen}\n * onToggle={setIsOpen}\n * >\n * <p>Controlled content</p>\n * </Accordion>\n */\nexport const Accordion: FC<AccordionProps> = ({\n children,\n header,\n isOpen,\n defaultIsOpen = false,\n onToggle,\n onClick,\n disabled = false,\n contentClassName,\n headerClassName,\n contentId,\n 'aria-label': ariaLabel,\n className,\n ...props\n}) => {\n // Determine if we're in controlled or uncontrolled mode\n const isControlled = isOpen !== undefined;\n const [internalIsOpen, setInternalIsOpen] = useState(defaultIsOpen);\n const id = useId();\n\n // Use controlled value if provided, otherwise use internal state\n const isExpandedState = isControlled ? isOpen : internalIsOpen;\n const isHidden = !isExpandedState;\n\n // Generate unique ID for content if not provided\n const generatedContentId = contentId ?? `${id}-accordion-content`;\n\n const handleToggle = (e: MouseEvent<HTMLButtonElement>) => {\n if (disabled) return;\n\n const newIsOpen = !isExpandedState;\n\n // Update internal state if uncontrolled\n if (!isControlled) {\n setInternalIsOpen(newIsOpen);\n }\n\n // Call external handlers\n onToggle?.(newIsOpen);\n onClick?.(e);\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n // Enter and Space should toggle the accordion\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleToggle(e as any);\n }\n };\n\n return (\n <div className={cn('w-full', className)}>\n <Button\n variant=\"hoverable\"\n color=\"text\"\n onClick={handleToggle}\n onKeyDown={handleKeyDown}\n disabled={disabled}\n isFullWidth\n className={cn(\n 'flex items-center justify-between gap-2',\n headerClassName\n )}\n IconRight={ChevronDown}\n iconClassName={cn(\n 'transform transition-transform duration-500 ease-in-out',\n isExpandedState ? 'rotate-0' : '-rotate-180'\n )}\n aria-expanded={isExpandedState}\n aria-controls={generatedContentId}\n aria-label={ariaLabel}\n role=\"button\"\n {...props}\n >\n {header}\n </Button>\n\n <MaxHeightSmoother\n id={generatedContentId}\n tabIndex={isHidden ? -1 : undefined}\n isHidden={isHidden}\n className={contentClassName}\n role=\"region\"\n aria-labelledby={generatedContentId}\n >\n {children}\n </MaxHeightSmoother>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,MAAa,aAAiC,EAC5C,UACA,QACA,QACA,gBAAgB,OAChB,UACA,SACA,WAAW,OACX,kBACA,iBACA,WACA,cAAc,WACd,WACA,GAAG,YACC;CAEJ,MAAM,eAAe,WAAW;CAChC,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,aAAa;CAClE,MAAM,KAAK,MAAM;CAGjB,MAAM,kBAAkB,eAAe,SAAS;CAChD,MAAM,WAAW,CAAC;CAGlB,MAAM,qBAAqB,aAAa,GAAG,GAAG;CAE9C,MAAM,gBAAgB,MAAqC;EACzD,IAAI,UAAU;EAEd,MAAM,YAAY,CAAC;EAGnB,IAAI,CAAC,cACH,kBAAkB,SAAS;EAI7B,WAAW,SAAS;EACpB,UAAU,CAAC;CACb;CAEA,MAAM,iBAAiB,MAAwC;EAE7D,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;GACtC,EAAE,eAAe;GACjB,aAAa,CAAQ;EACvB;CACF;CAEA,OACE,qBAAC,OAAD;EAAK,WAAW,GAAG,UAAU,SAAS;YAAtC,CACE,oBAAC,QAAD;GACE,SAAQ;GACR,OAAM;GACN,SAAS;GACT,WAAW;GACD;GACV;GACA,WAAW,GACT,2CACA,eACF;GACA,WAAW;GACX,eAAe,GACb,2DACA,kBAAkB,aAAa,aACjC;GACA,iBAAe;GACf,iBAAe;GACf,cAAY;GACZ,MAAK;GACL,GAAI;aAEH;EACK,IAER,oBAAC,mBAAD;GACE,IAAI;GACJ,UAAU,WAAW,KAAK;GAChB;GACV,WAAW;GACX,MAAK;GACL,mBAAiB;GAEhB;EACgB,EAChB;;AAET"}
|
|
@@ -12,6 +12,14 @@ import { useIntlayer } from "react-intlayer";
|
|
|
12
12
|
//#region src/components/Carousel/index.tsx
|
|
13
13
|
const SWIPE_THRESHOLD_DIVISOR = 5;
|
|
14
14
|
const TRANSITION_DELAY_MS = 50;
|
|
15
|
+
const CONTAINER_VERTICAL_PADDING_PX = 40;
|
|
16
|
+
const CONTAINER_MIN_HEIGHT_PX = 400;
|
|
17
|
+
/**
|
|
18
|
+
* Border-box height carried by a `ResizeObserverEntry`, equivalent to
|
|
19
|
+
* `offsetHeight` but already computed by the browser during layout, so reading
|
|
20
|
+
* it never forces a reflow.
|
|
21
|
+
*/
|
|
22
|
+
const getEntryBlockSize = (entry) => entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height;
|
|
15
23
|
const CarouselContext = createContext(null);
|
|
16
24
|
const useCarousel = () => {
|
|
17
25
|
const context = useContext(CarouselContext);
|
|
@@ -142,29 +150,11 @@ const CarouselRoot = ({ children, className, initialIndex = 0, onIndexChange, ..
|
|
|
142
150
|
const [displayedIndex, setDisplayedIndex] = useState(initialIndex);
|
|
143
151
|
const [containerHeight, setContainerHeight] = useState(0);
|
|
144
152
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
145
|
-
useLayoutEffect(() => {
|
|
146
|
-
const calculateDimensions = () => {
|
|
147
|
-
if (!containerRef.current) return;
|
|
148
|
-
const width = containerRef.current.clientWidth;
|
|
149
|
-
setContainerWidth(width);
|
|
150
|
-
const heights = itemsRef.current.map((item) => item?.offsetHeight || 0);
|
|
151
|
-
const maxHeight = Math.max(0, ...heights);
|
|
152
|
-
if (maxHeight > 0) setContainerHeight(maxHeight + 40);
|
|
153
|
-
};
|
|
154
|
-
calculateDimensions();
|
|
155
|
-
const observer = new ResizeObserver(() => {
|
|
156
|
-
calculateDimensions();
|
|
157
|
-
});
|
|
158
|
-
if (containerRef.current) observer.observe(containerRef.current);
|
|
159
|
-
itemsRef.current.forEach((item) => {
|
|
160
|
-
if (item) observer.observe(item);
|
|
161
|
-
});
|
|
162
|
-
return () => observer.disconnect();
|
|
163
|
-
}, [totalItems]);
|
|
164
153
|
const [startX, setStartX] = useState(0);
|
|
165
154
|
const [isDragging, setIsDragging] = useState(false);
|
|
166
155
|
const containerRef = useRef(null);
|
|
167
156
|
const itemsRef = useRef([]);
|
|
157
|
+
const itemHeightsRef = useRef(/* @__PURE__ */ new Map());
|
|
168
158
|
const handleSwitchItem = (diff) => {
|
|
169
159
|
if (containerWidth === 0) return;
|
|
170
160
|
const swipeStep = containerWidth / SWIPE_THRESHOLD_DIVISOR;
|
|
@@ -215,20 +205,48 @@ const CarouselRoot = ({ children, className, initialIndex = 0, onIndexChange, ..
|
|
|
215
205
|
}, TRANSITION_DELAY_MS);
|
|
216
206
|
return () => clearInterval(interval);
|
|
217
207
|
}, [selectedIndex, displayedIndex]);
|
|
208
|
+
/**
|
|
209
|
+
* Tracks the container width and the height of the tallest slide.
|
|
210
|
+
*
|
|
211
|
+
* The container height is the *output* of this measurement, so the observer
|
|
212
|
+
* must never read geometry back from the DOM: React commits the new inline
|
|
213
|
+
* height between two observer callbacks, and every read after that commit is
|
|
214
|
+
* a forced reflow. `ResizeObserverEntry` already carries the boxes the
|
|
215
|
+
* browser computed during layout, which are free to read.
|
|
216
|
+
*/
|
|
218
217
|
useLayoutEffect(() => {
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
218
|
+
const container = containerRef.current;
|
|
219
|
+
if (!container) return;
|
|
220
|
+
const items = itemsRef.current.filter((item) => item !== null);
|
|
221
|
+
const itemHeights = itemHeightsRef.current;
|
|
222
|
+
const publishMaxHeight = () => {
|
|
223
|
+
const maxHeight = Math.max(0, ...itemHeights.values());
|
|
224
|
+
if (maxHeight > 0) setContainerHeight(maxHeight + CONTAINER_VERTICAL_PADDING_PX);
|
|
223
225
|
};
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
226
|
+
const initialWidth = container.clientWidth;
|
|
227
|
+
items.forEach((item) => {
|
|
228
|
+
itemHeights.set(item, item.offsetHeight);
|
|
227
229
|
});
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
setContainerWidth(initialWidth);
|
|
231
|
+
publishMaxHeight();
|
|
232
|
+
const observer = new ResizeObserver((entries) => {
|
|
233
|
+
entries.forEach((entry) => {
|
|
234
|
+
if (entry.target === container) {
|
|
235
|
+
setContainerWidth(entry.contentRect.width);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
itemHeights.set(entry.target, getEntryBlockSize(entry));
|
|
239
|
+
});
|
|
240
|
+
publishMaxHeight();
|
|
230
241
|
});
|
|
231
|
-
|
|
242
|
+
observer.observe(container);
|
|
243
|
+
items.forEach((item) => {
|
|
244
|
+
observer.observe(item);
|
|
245
|
+
});
|
|
246
|
+
return () => {
|
|
247
|
+
observer.disconnect();
|
|
248
|
+
itemHeights.clear();
|
|
249
|
+
};
|
|
232
250
|
}, [totalItems]);
|
|
233
251
|
return /* @__PURE__ */ jsx(CarouselContext.Provider, {
|
|
234
252
|
value: {
|
|
@@ -243,7 +261,7 @@ const CarouselRoot = ({ children, className, initialIndex = 0, onIndexChange, ..
|
|
|
243
261
|
className: cn("relative flex w-full cursor-grab select-none items-center overflow-hidden pb-4 outline-none transition-[height] duration-300 ease-in-out focus:outline-none focus:outline-none focus:ring-0 active:cursor-grabbing", "max-w-350", className),
|
|
244
262
|
style: {
|
|
245
263
|
height: containerHeight > 0 ? containerHeight : "auto",
|
|
246
|
-
minHeight:
|
|
264
|
+
minHeight: `${CONTAINER_MIN_HEIGHT_PX}px`
|
|
247
265
|
},
|
|
248
266
|
onMouseDown: handleMouseDown,
|
|
249
267
|
onMouseMove: handleMouseMove,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/components/Carousel/index.tsx"],"sourcesContent":["'use client';\n\nimport { Button } from '@components/Button';\nimport { KeyboardShortcut } from '@components/KeyboardShortcut';\nimport { Popover } from '@components/Popover';\nimport { cn } from '@utils/cn';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport {\n Children,\n createContext,\n type FC,\n type HTMLAttributes,\n isValidElement,\n type MouseEventHandler,\n type ReactElement,\n type ReactNode,\n type TouchEventHandler,\n useContext,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\n\n// ------------------------------------------------------------------\n// Configuration\n// ------------------------------------------------------------------\nconst SWIPE_THRESHOLD_DIVISOR = 5;\nconst TRANSITION_DELAY_MS = 50;\n\n// ------------------------------------------------------------------\n// Context Definition\n// ------------------------------------------------------------------\ntype CarouselContextValue = {\n selectedIndex: number;\n setSelectedIndex: (index: number) => void;\n totalItems: number;\n handlePrev: () => void;\n handleNext: () => void;\n};\n\nconst CarouselContext = createContext<CarouselContextValue | null>(null);\n\nconst useCarousel = () => {\n const context = useContext(CarouselContext);\n if (!context) {\n throw new Error('useCarousel must be used within a Carousel');\n }\n return context;\n};\n\n// ------------------------------------------------------------------\n// Helper Functions\n// ------------------------------------------------------------------\nconst getCardStyle = (index: number, displayedIndex: number) => {\n const diff = Math.abs(index - displayedIndex);\n switch (diff) {\n case 0:\n return 'opacity-100 z-40';\n case 1:\n return 'opacity-75 z-30 cursor-pointer';\n case 2:\n return 'opacity-50 z-20 pointer-events-none';\n default:\n return 'opacity-0 z-10 pointer-events-none';\n }\n};\n\nconst getCardScale = (index: number, displayedIndex: number) => {\n const diff = Math.abs(index - displayedIndex);\n switch (diff) {\n case 0:\n return 1;\n case 1:\n return 0.9;\n case 2:\n return 0.8;\n default:\n return 0.7;\n }\n};\n\n// This allows the calculation to work on SSR without hydration mismatch.\n// Your original logic: (3 * screenWidth) / 10 === 30% of viewport width\nconst getCardPositionX = (\n index: number,\n displayedIndex: number,\n containerWidth: number\n) => {\n const diff = index - displayedIndex;\n const gapPercentage = containerWidth < 600 ? 0.15 : 0.3; // Dropped to 15% for a tighter cluster\n const step = Math.min(containerWidth * gapPercentage, 300);\n\n // The 'px' here is mandatory\n return `${diff * step}px`;\n};\n\n// ------------------------------------------------------------------\n// Sub-Component: Item\n// ------------------------------------------------------------------\ntype CarouselItemProps = HTMLAttributes<HTMLDivElement> & {\n children: ReactNode;\n};\n\nconst CarouselItem: FC<CarouselItemProps> & { isCarouselItem: true } =\n Object.assign(\n ({ children, className, ...props }: CarouselItemProps) => (\n <div className={cn('size-full', className)} {...props}>\n {children}\n </div>\n ),\n { isCarouselItem: true as const }\n );\n\n// ------------------------------------------------------------------\n// Sub-Component: Indicators (Controller)\n// ------------------------------------------------------------------\ntype CarouselIndicatorsProps = HTMLAttributes<HTMLDivElement> & {\n disableKeyboardShortcuts?: boolean;\n};\n\nconst CarouselIndicators: FC<CarouselIndicatorsProps> = ({\n className,\n disableKeyboardShortcuts = false,\n ...props\n}) => {\n const {\n selectedIndex,\n setSelectedIndex,\n totalItems,\n handlePrev,\n handleNext,\n } = useCarousel();\n const { goToSlide, previousSlide, nextSlide } = useIntlayer('carousel');\n\n if (totalItems <= 1) return null;\n\n return (\n <div\n className={cn(\n 'absolute bottom-0 left-1/2 z-50 flex -translate-x-1/2 flex-row items-center gap-2',\n className\n )}\n {...props}\n >\n <Popover identifier=\"carousel-prev\">\n <Button\n variant=\"hoverable\"\n color=\"neutral\"\n label={previousSlide.value}\n roundedSize=\"full\"\n onClick={(e) => {\n e.stopPropagation();\n handlePrev();\n }}\n Icon={ChevronLeft}\n size=\"icon-md\"\n disabled={selectedIndex === 0}\n />\n\n <Popover.Detail identifier=\"carousel-prev\">\n <div className=\"flex items-center gap-2 p-2\">\n <span className=\"whitespace-nowrap text-neutral text-xs\">\n {previousSlide.value}\n </span>\n <KeyboardShortcut\n shortcut=\"ArrowLeft\"\n disabled={disableKeyboardShortcuts}\n size=\"sm\"\n onTriggered={handlePrev}\n />\n </div>\n </Popover.Detail>\n </Popover>\n\n {Array.from({ length: totalItems }).map((_, index) => {\n const isActive = index === selectedIndex;\n return (\n <button\n key={index}\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setSelectedIndex(index);\n }}\n aria-label={goToSlide({ index: index + 1 }).value}\n className={cn(\n 'h-2.5 w-2.5 rounded-full transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-neutral-400 focus:ring-offset-2',\n isActive ? 'scale-110 bg-text' : 'bg-text/20 hover:bg-text/40'\n )}\n />\n );\n })}\n\n <Popover identifier=\"carousel-next\">\n <Button\n variant=\"hoverable\"\n color=\"neutral\"\n roundedSize=\"full\"\n label={nextSlide.value}\n onClick={(e) => {\n e.stopPropagation();\n handleNext();\n }}\n Icon={ChevronRight}\n size=\"icon-md\"\n disabled={selectedIndex === totalItems - 1}\n />\n\n <Popover.Detail identifier=\"carousel-next\">\n <div className=\"flex items-center gap-2 p-2\">\n <span className=\"whitespace-nowrap text-neutral text-xs\">\n {nextSlide.value}\n </span>\n <KeyboardShortcut\n shortcut=\"ArrowRight\"\n size=\"sm\"\n onTriggered={handleNext}\n disabled={disableKeyboardShortcuts}\n />\n </div>\n </Popover.Detail>\n </Popover>\n </div>\n );\n};\n\n// ------------------------------------------------------------------\n// Main Component: Carousel Root\n// ------------------------------------------------------------------\ntype CarouselProps = HTMLAttributes<HTMLDivElement> & {\n children: ReactNode;\n initialIndex?: number;\n onIndexChange?: (index: number) => void;\n};\n\nconst partitionCarouselChildren = (\n children: ReactNode[]\n): [ReactElement[], ReactNode[]] => {\n const slides: ReactElement[] = [];\n const others: ReactNode[] = [];\n\n children.forEach((child) => {\n if (\n isValidElement(child) &&\n (child.type === CarouselItem ||\n (child.type as { isCarouselItem?: boolean }).isCarouselItem === true)\n ) {\n slides.push(child);\n } else {\n others.push(child);\n }\n });\n\n return [slides, others];\n};\n\nconst CarouselRoot: FC<CarouselProps> = ({\n children,\n className,\n initialIndex = 0,\n onIndexChange,\n ...props\n}) => {\n const allChildren = Children.toArray(children);\n const [slides, others] = partitionCarouselChildren(allChildren);\n const totalItems = slides.length;\n\n // State Management\n const [selectedIndex, setSelectedIndex] = useState<number>(initialIndex);\n const [displayedIndex, setDisplayedIndex] = useState<number>(initialIndex);\n const [containerHeight, setContainerHeight] = useState<number>(0);\n const [containerWidth, setContainerWidth] = useState<number>(0);\n\n useLayoutEffect(() => {\n const calculateDimensions = () => {\n if (!containerRef.current) return;\n\n // Track Width\n const width = containerRef.current.clientWidth;\n setContainerWidth(width);\n\n // Track Height (existing logic)\n const heights = itemsRef.current.map((item) => item?.offsetHeight || 0);\n const maxHeight = Math.max(0, ...heights);\n if (maxHeight > 0) {\n setContainerHeight(maxHeight + 40);\n }\n };\n\n calculateDimensions();\n\n const observer = new ResizeObserver(() => {\n calculateDimensions();\n });\n\n if (containerRef.current) observer.observe(containerRef.current);\n itemsRef.current.forEach((item) => {\n if (item) observer.observe(item);\n });\n\n return () => observer.disconnect();\n }, [totalItems]);\n\n // Drag State\n const [startX, setStartX] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n\n // Refs\n const containerRef = useRef<HTMLDivElement>(null);\n const itemsRef = useRef<(HTMLDivElement | null)[]>([]);\n\n // Navigation Logic\n const handleSwitchItem = (diff: number) => {\n if (containerWidth === 0) return;\n\n // Use container width for the threshold\n const swipeStep = containerWidth / SWIPE_THRESHOLD_DIVISOR;\n const numSwipe = Math.round(diff / swipeStep);\n\n if (Math.abs(numSwipe) >= 1) {\n const newIndex = displayedIndex - numSwipe;\n const clampedIndex = Math.max(0, Math.min(newIndex, totalItems - 1));\n\n if (clampedIndex !== selectedIndex) {\n setSelectedIndex(clampedIndex);\n setStartX((prev) => prev + diff);\n }\n }\n };\n\n const handleNext = () => {\n setSelectedIndex((prev) => Math.min(prev + 1, totalItems - 1));\n };\n\n const handlePrev = () => {\n setSelectedIndex((prev) => Math.max(prev - 1, 0));\n };\n\n // Input Handlers\n const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {\n setIsDragging(true);\n setStartX(e.clientX);\n };\n const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {\n if (isDragging) handleSwitchItem(e.clientX - startX);\n };\n const handleDragEnd = () => setIsDragging(false);\n const handleTouchStart: TouchEventHandler<HTMLDivElement> = (e) => {\n setIsDragging(true);\n setStartX(e.touches[0]!.clientX);\n };\n const handleTouchMove: TouchEventHandler<HTMLDivElement> = (e) => {\n if (isDragging) handleSwitchItem(e.touches[0]!.clientX - startX);\n };\n\n // Effects\n useEffect(() => {\n if (selectedIndex) onIndexChange?.(selectedIndex);\n }, [selectedIndex, onIndexChange]);\n\n useEffect(() => {\n let interval: NodeJS.Timeout;\n\n if (selectedIndex !== displayedIndex) {\n interval = setInterval(() => {\n setDisplayedIndex((prev) => {\n if (prev === selectedIndex) {\n clearInterval(interval);\n return prev;\n }\n return prev < selectedIndex ? prev + 1 : prev - 1;\n });\n }, TRANSITION_DELAY_MS);\n }\n return () => clearInterval(interval);\n }, [selectedIndex, displayedIndex]);\n\n // Calculate height based on the MAX height of ALL items\n useLayoutEffect(() => {\n const calculateMaxHeight = () => {\n const heights = itemsRef.current.map((item) => item?.offsetHeight || 0);\n const maxHeight = Math.max(0, ...heights);\n\n if (maxHeight > 0) {\n setContainerHeight(maxHeight + 40);\n }\n };\n\n calculateMaxHeight();\n\n const observer = new ResizeObserver(() => {\n calculateMaxHeight();\n });\n\n itemsRef.current.forEach((item) => {\n if (item) observer.observe(item);\n });\n\n return () => observer.disconnect();\n }, [totalItems]); // Removed isMounted dependency\n\n return (\n <CarouselContext.Provider\n value={{\n selectedIndex,\n setSelectedIndex,\n totalItems,\n handlePrev,\n handleNext,\n }}\n >\n <div\n ref={containerRef}\n className={cn(\n 'relative flex w-full cursor-grab select-none items-center overflow-hidden pb-4 outline-none transition-[height] duration-300 ease-in-out focus:outline-none focus:outline-none focus:ring-0 active:cursor-grabbing',\n 'max-w-350',\n className\n )}\n style={{\n height: containerHeight > 0 ? containerHeight : 'auto',\n minHeight: '400px',\n }}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleDragEnd}\n onMouseLeave={handleDragEnd}\n onTouchStart={handleTouchStart}\n onTouchMove={handleTouchMove}\n onTouchEnd={handleDragEnd}\n role=\"region\"\n aria-label=\"Carousel\"\n {...props}\n >\n {slides.map((child, index) => {\n return (\n <div\n key={index}\n role=\"button\"\n tabIndex={0}\n ref={(el) => {\n itemsRef.current[index] = el;\n }}\n // FIX 2: Removed isMounted checks and invisible classes.\n // CSS units allow correct SSR rendering.\n className={cn(\n 'absolute left-1/2 -translate-x-1/2 transition-all duration-300 ease-in-out',\n 'outline-none ring-0 focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0',\n getCardStyle(index, displayedIndex)\n )}\n onClick={(e) => {\n e.stopPropagation();\n setSelectedIndex(index);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') setSelectedIndex(index);\n }}\n style={{\n transform: `\n translateX(${getCardPositionX(\n index,\n displayedIndex,\n containerWidth\n )})\n scale(${getCardScale(index, displayedIndex)})\n `,\n }}\n >\n {child}\n </div>\n );\n })}\n\n {others}\n </div>\n </CarouselContext.Provider>\n );\n};\n\nexport const Carousel = Object.assign(CarouselRoot, {\n Item: CarouselItem,\n Indicators: CarouselIndicators,\n});\n"],"mappings":";;;;;;;;;;;;AA4BA,MAAM,0BAA0B;AAChC,MAAM,sBAAsB;AAa5B,MAAM,kBAAkB,cAA2C,IAAI;AAEvE,MAAM,oBAAoB;CACxB,MAAM,UAAU,WAAW,eAAe;CAC1C,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,4CAA4C;CAE9D,OAAO;AACT;AAKA,MAAM,gBAAgB,OAAe,mBAA2B;CAE9D,QADa,KAAK,IAAI,QAAQ,cACnB,GAAX;EACE,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,SACE,OAAO;CACX;AACF;AAEA,MAAM,gBAAgB,OAAe,mBAA2B;CAE9D,QADa,KAAK,IAAI,QAAQ,cACnB,GAAX;EACE,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,SACE,OAAO;CACX;AACF;AAIA,MAAM,oBACJ,OACA,gBACA,mBACG;CAMH,OAAO,IALM,QAAQ,kBAER,KAAK,IAAI,kBADA,iBAAiB,MAAM,MAAO,KACE,GAGlC,EAAE;AACxB;AASA,MAAM,eACJ,OAAO,QACJ,EAAE,UAAU,WAAW,GAAG,YACzB,oBAAC,OAAD;CAAK,WAAW,GAAG,aAAa,SAAS;CAAG,GAAI;CAC7C;AACE,IAEP,EAAE,gBAAgB,KAAc,CAClC;AASF,MAAM,sBAAmD,EACvD,WACA,2BAA2B,OAC3B,GAAG,YACC;CACJ,MAAM,EACJ,eACA,kBACA,YACA,YACA,eACE,YAAY;CAChB,MAAM,EAAE,WAAW,eAAe,cAAc,YAAY,UAAU;CAEtE,IAAI,cAAc,GAAG,OAAO;CAE5B,OACE,qBAAC,OAAD;EACE,WAAW,GACT,qFACA,SACF;EACA,GAAI;YALN;GAOE,qBAAC,SAAD;IAAS,YAAW;cAApB,CACE,oBAAC,QAAD;KACE,SAAQ;KACR,OAAM;KACN,OAAO,cAAc;KACrB,aAAY;KACZ,UAAU,MAAM;MACd,EAAE,gBAAgB;MAClB,WAAW;KACb;KACA,MAAM;KACN,MAAK;KACL,UAAU,kBAAkB;IAC7B,IAED,oBAAC,QAAQ,QAAT;KAAgB,YAAW;eACzB,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,oBAAC,QAAD;OAAM,WAAU;iBACb,cAAc;MACX,IACN,oBAAC,kBAAD;OACE,UAAS;OACT,UAAU;OACV,MAAK;OACL,aAAa;MACd,EACE;;IACS,EACT;;GAER,MAAM,KAAK,EAAE,QAAQ,WAAW,CAAC,CAAC,CAAC,KAAK,GAAG,UAAU;IACpD,MAAM,WAAW,UAAU;IAC3B,OACE,oBAAC,UAAD;KAEE,MAAK;KACL,UAAU,MAAM;MACd,EAAE,gBAAgB;MAClB,iBAAiB,KAAK;KACxB;KACA,cAAY,UAAU,EAAE,OAAO,QAAQ,EAAE,CAAC,CAAC,CAAC;KAC5C,WAAW,GACT,mIACA,WAAW,sBAAsB,6BACnC;IACD,GAXM,KAWN;GAEL,CAAC;GAED,qBAAC,SAAD;IAAS,YAAW;cAApB,CACE,oBAAC,QAAD;KACE,SAAQ;KACR,OAAM;KACN,aAAY;KACZ,OAAO,UAAU;KACjB,UAAU,MAAM;MACd,EAAE,gBAAgB;MAClB,WAAW;KACb;KACA,MAAM;KACN,MAAK;KACL,UAAU,kBAAkB,aAAa;IAC1C,IAED,oBAAC,QAAQ,QAAT;KAAgB,YAAW;eACzB,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,oBAAC,QAAD;OAAM,WAAU;iBACb,UAAU;MACP,IACN,oBAAC,kBAAD;OACE,UAAS;OACT,MAAK;OACL,aAAa;OACb,UAAU;MACX,EACE;;IACS,EACT;;EACN;;AAET;AAWA,MAAM,6BACJ,aACkC;CAClC,MAAM,SAAyB,CAAC;CAChC,MAAM,SAAsB,CAAC;CAE7B,SAAS,SAAS,UAAU;EAC1B,IACE,eAAe,KAAK,MACnB,MAAM,SAAS,gBACb,MAAM,KAAsC,mBAAmB,OAElE,OAAO,KAAK,KAAK;OAEjB,OAAO,KAAK,KAAK;CAErB,CAAC;CAED,OAAO,CAAC,QAAQ,MAAM;AACxB;AAEA,MAAM,gBAAmC,EACvC,UACA,WACA,eAAe,GACf,eACA,GAAG,YACC;CACJ,MAAM,cAAc,SAAS,QAAQ,QAAQ;CAC7C,MAAM,CAAC,QAAQ,UAAU,0BAA0B,WAAW;CAC9D,MAAM,aAAa,OAAO;CAG1B,MAAM,CAAC,eAAe,oBAAoB,SAAiB,YAAY;CACvE,MAAM,CAAC,gBAAgB,qBAAqB,SAAiB,YAAY;CACzE,MAAM,CAAC,iBAAiB,sBAAsB,SAAiB,CAAC;CAChE,MAAM,CAAC,gBAAgB,qBAAqB,SAAiB,CAAC;CAE9D,sBAAsB;EACpB,MAAM,4BAA4B;GAChC,IAAI,CAAC,aAAa,SAAS;GAG3B,MAAM,QAAQ,aAAa,QAAQ;GACnC,kBAAkB,KAAK;GAGvB,MAAM,UAAU,SAAS,QAAQ,KAAK,SAAS,MAAM,gBAAgB,CAAC;GACtE,MAAM,YAAY,KAAK,IAAI,GAAG,GAAG,OAAO;GACxC,IAAI,YAAY,GACd,mBAAmB,YAAY,EAAE;EAErC;EAEA,oBAAoB;EAEpB,MAAM,WAAW,IAAI,qBAAqB;GACxC,oBAAoB;EACtB,CAAC;EAED,IAAI,aAAa,SAAS,SAAS,QAAQ,aAAa,OAAO;EAC/D,SAAS,QAAQ,SAAS,SAAS;GACjC,IAAI,MAAM,SAAS,QAAQ,IAAI;EACjC,CAAC;EAED,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,UAAU,CAAC;CAGf,MAAM,CAAC,QAAQ,aAAa,SAAS,CAAC;CACtC,MAAM,CAAC,YAAY,iBAAiB,SAAS,KAAK;CAGlD,MAAM,eAAe,OAAuB,IAAI;CAChD,MAAM,WAAW,OAAkC,CAAC,CAAC;CAGrD,MAAM,oBAAoB,SAAiB;EACzC,IAAI,mBAAmB,GAAG;EAG1B,MAAM,YAAY,iBAAiB;EACnC,MAAM,WAAW,KAAK,MAAM,OAAO,SAAS;EAE5C,IAAI,KAAK,IAAI,QAAQ,KAAK,GAAG;GAC3B,MAAM,WAAW,iBAAiB;GAClC,MAAM,eAAe,KAAK,IAAI,GAAG,KAAK,IAAI,UAAU,aAAa,CAAC,CAAC;GAEnE,IAAI,iBAAiB,eAAe;IAClC,iBAAiB,YAAY;IAC7B,WAAW,SAAS,OAAO,IAAI;GACjC;EACF;CACF;CAEA,MAAM,mBAAmB;EACvB,kBAAkB,SAAS,KAAK,IAAI,OAAO,GAAG,aAAa,CAAC,CAAC;CAC/D;CAEA,MAAM,mBAAmB;EACvB,kBAAkB,SAAS,KAAK,IAAI,OAAO,GAAG,CAAC,CAAC;CAClD;CAGA,MAAM,mBAAsD,MAAM;EAChE,cAAc,IAAI;EAClB,UAAU,EAAE,OAAO;CACrB;CACA,MAAM,mBAAsD,MAAM;EAChE,IAAI,YAAY,iBAAiB,EAAE,UAAU,MAAM;CACrD;CACA,MAAM,sBAAsB,cAAc,KAAK;CAC/C,MAAM,oBAAuD,MAAM;EACjE,cAAc,IAAI;EAClB,UAAU,EAAE,QAAQ,EAAE,CAAE,OAAO;CACjC;CACA,MAAM,mBAAsD,MAAM;EAChE,IAAI,YAAY,iBAAiB,EAAE,QAAQ,EAAE,CAAE,UAAU,MAAM;CACjE;CAGA,gBAAgB;EACd,IAAI,eAAe,gBAAgB,aAAa;CAClD,GAAG,CAAC,eAAe,aAAa,CAAC;CAEjC,gBAAgB;EACd,IAAI;EAEJ,IAAI,kBAAkB,gBACpB,WAAW,kBAAkB;GAC3B,mBAAmB,SAAS;IAC1B,IAAI,SAAS,eAAe;KAC1B,cAAc,QAAQ;KACtB,OAAO;IACT;IACA,OAAO,OAAO,gBAAgB,OAAO,IAAI,OAAO;GAClD,CAAC;EACH,GAAG,mBAAmB;EAExB,aAAa,cAAc,QAAQ;CACrC,GAAG,CAAC,eAAe,cAAc,CAAC;CAGlC,sBAAsB;EACpB,MAAM,2BAA2B;GAC/B,MAAM,UAAU,SAAS,QAAQ,KAAK,SAAS,MAAM,gBAAgB,CAAC;GACtE,MAAM,YAAY,KAAK,IAAI,GAAG,GAAG,OAAO;GAExC,IAAI,YAAY,GACd,mBAAmB,YAAY,EAAE;EAErC;EAEA,mBAAmB;EAEnB,MAAM,WAAW,IAAI,qBAAqB;GACxC,mBAAmB;EACrB,CAAC;EAED,SAAS,QAAQ,SAAS,SAAS;GACjC,IAAI,MAAM,SAAS,QAAQ,IAAI;EACjC,CAAC;EAED,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,UAAU,CAAC;CAEf,OACE,oBAAC,gBAAgB,UAAjB;EACE,OAAO;GACL;GACA;GACA;GACA;GACA;EACF;YAEA,qBAAC,OAAD;GACE,KAAK;GACL,WAAW,GACT,sNACA,aACA,SACF;GACA,OAAO;IACL,QAAQ,kBAAkB,IAAI,kBAAkB;IAChD,WAAW;GACb;GACA,aAAa;GACb,aAAa;GACb,WAAW;GACX,cAAc;GACd,cAAc;GACd,aAAa;GACb,YAAY;GACZ,MAAK;GACL,cAAW;GACX,GAAI;aApBN,CAsBG,OAAO,KAAK,OAAO,UAAU;IAC5B,OACE,oBAAC,OAAD;KAEE,MAAK;KACL,UAAU;KACV,MAAM,OAAO;MACX,SAAS,QAAQ,SAAS;KAC5B;KAGA,WAAW,GACT,8EACA,uJACA,aAAa,OAAO,cAAc,CACpC;KACA,UAAU,MAAM;MACd,EAAE,gBAAgB;MAClB,iBAAiB,KAAK;KACxB;KACA,YAAY,MAAM;MAChB,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK,iBAAiB,KAAK;KAChE;KACA,OAAO,EACL,WAAW;+BACI,iBACX,OACA,gBACA,cACF,EAAE;0BACM,aAAa,OAAO,cAAc,EAAE;kBAEhD;eAEC;IACE,GAhCE,KAgCF;GAET,CAAC,GAEA,MACE;;CACmB;AAE9B;AAEA,MAAa,WAAW,OAAO,OAAO,cAAc;CAClD,MAAM;CACN,YAAY;AACd,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/components/Carousel/index.tsx"],"sourcesContent":["'use client';\n\nimport { Button } from '@components/Button';\nimport { KeyboardShortcut } from '@components/KeyboardShortcut';\nimport { Popover } from '@components/Popover';\nimport { cn } from '@utils/cn';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport {\n Children,\n createContext,\n type FC,\n type HTMLAttributes,\n isValidElement,\n type MouseEventHandler,\n type ReactElement,\n type ReactNode,\n type TouchEventHandler,\n useContext,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\n\n// ------------------------------------------------------------------\n// Configuration\n// ------------------------------------------------------------------\nconst SWIPE_THRESHOLD_DIVISOR = 5;\nconst TRANSITION_DELAY_MS = 50;\nconst CONTAINER_VERTICAL_PADDING_PX = 40;\nconst CONTAINER_MIN_HEIGHT_PX = 400;\n\n/**\n * Border-box height carried by a `ResizeObserverEntry`, equivalent to\n * `offsetHeight` but already computed by the browser during layout, so reading\n * it never forces a reflow.\n */\nconst getEntryBlockSize = (entry: ResizeObserverEntry): number =>\n entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height;\n\n// ------------------------------------------------------------------\n// Context Definition\n// ------------------------------------------------------------------\ntype CarouselContextValue = {\n selectedIndex: number;\n setSelectedIndex: (index: number) => void;\n totalItems: number;\n handlePrev: () => void;\n handleNext: () => void;\n};\n\nconst CarouselContext = createContext<CarouselContextValue | null>(null);\n\nconst useCarousel = () => {\n const context = useContext(CarouselContext);\n if (!context) {\n throw new Error('useCarousel must be used within a Carousel');\n }\n return context;\n};\n\n// ------------------------------------------------------------------\n// Helper Functions\n// ------------------------------------------------------------------\nconst getCardStyle = (index: number, displayedIndex: number) => {\n const diff = Math.abs(index - displayedIndex);\n switch (diff) {\n case 0:\n return 'opacity-100 z-40';\n case 1:\n return 'opacity-75 z-30 cursor-pointer';\n case 2:\n return 'opacity-50 z-20 pointer-events-none';\n default:\n return 'opacity-0 z-10 pointer-events-none';\n }\n};\n\nconst getCardScale = (index: number, displayedIndex: number) => {\n const diff = Math.abs(index - displayedIndex);\n switch (diff) {\n case 0:\n return 1;\n case 1:\n return 0.9;\n case 2:\n return 0.8;\n default:\n return 0.7;\n }\n};\n\n// This allows the calculation to work on SSR without hydration mismatch.\n// Your original logic: (3 * screenWidth) / 10 === 30% of viewport width\nconst getCardPositionX = (\n index: number,\n displayedIndex: number,\n containerWidth: number\n) => {\n const diff = index - displayedIndex;\n const gapPercentage = containerWidth < 600 ? 0.15 : 0.3; // Dropped to 15% for a tighter cluster\n const step = Math.min(containerWidth * gapPercentage, 300);\n\n // The 'px' here is mandatory\n return `${diff * step}px`;\n};\n\n// ------------------------------------------------------------------\n// Sub-Component: Item\n// ------------------------------------------------------------------\ntype CarouselItemProps = HTMLAttributes<HTMLDivElement> & {\n children: ReactNode;\n};\n\nconst CarouselItem: FC<CarouselItemProps> & { isCarouselItem: true } =\n Object.assign(\n ({ children, className, ...props }: CarouselItemProps) => (\n <div className={cn('size-full', className)} {...props}>\n {children}\n </div>\n ),\n { isCarouselItem: true as const }\n );\n\n// ------------------------------------------------------------------\n// Sub-Component: Indicators (Controller)\n// ------------------------------------------------------------------\ntype CarouselIndicatorsProps = HTMLAttributes<HTMLDivElement> & {\n disableKeyboardShortcuts?: boolean;\n};\n\nconst CarouselIndicators: FC<CarouselIndicatorsProps> = ({\n className,\n disableKeyboardShortcuts = false,\n ...props\n}) => {\n const {\n selectedIndex,\n setSelectedIndex,\n totalItems,\n handlePrev,\n handleNext,\n } = useCarousel();\n const { goToSlide, previousSlide, nextSlide } = useIntlayer('carousel');\n\n if (totalItems <= 1) return null;\n\n return (\n <div\n className={cn(\n 'absolute bottom-0 left-1/2 z-50 flex -translate-x-1/2 flex-row items-center gap-2',\n className\n )}\n {...props}\n >\n <Popover identifier=\"carousel-prev\">\n <Button\n variant=\"hoverable\"\n color=\"neutral\"\n label={previousSlide.value}\n roundedSize=\"full\"\n onClick={(e) => {\n e.stopPropagation();\n handlePrev();\n }}\n Icon={ChevronLeft}\n size=\"icon-md\"\n disabled={selectedIndex === 0}\n />\n\n <Popover.Detail identifier=\"carousel-prev\">\n <div className=\"flex items-center gap-2 p-2\">\n <span className=\"whitespace-nowrap text-neutral text-xs\">\n {previousSlide.value}\n </span>\n <KeyboardShortcut\n shortcut=\"ArrowLeft\"\n disabled={disableKeyboardShortcuts}\n size=\"sm\"\n onTriggered={handlePrev}\n />\n </div>\n </Popover.Detail>\n </Popover>\n\n {Array.from({ length: totalItems }).map((_, index) => {\n const isActive = index === selectedIndex;\n return (\n <button\n key={index}\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setSelectedIndex(index);\n }}\n aria-label={goToSlide({ index: index + 1 }).value}\n className={cn(\n 'h-2.5 w-2.5 rounded-full transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-neutral-400 focus:ring-offset-2',\n isActive ? 'scale-110 bg-text' : 'bg-text/20 hover:bg-text/40'\n )}\n />\n );\n })}\n\n <Popover identifier=\"carousel-next\">\n <Button\n variant=\"hoverable\"\n color=\"neutral\"\n roundedSize=\"full\"\n label={nextSlide.value}\n onClick={(e) => {\n e.stopPropagation();\n handleNext();\n }}\n Icon={ChevronRight}\n size=\"icon-md\"\n disabled={selectedIndex === totalItems - 1}\n />\n\n <Popover.Detail identifier=\"carousel-next\">\n <div className=\"flex items-center gap-2 p-2\">\n <span className=\"whitespace-nowrap text-neutral text-xs\">\n {nextSlide.value}\n </span>\n <KeyboardShortcut\n shortcut=\"ArrowRight\"\n size=\"sm\"\n onTriggered={handleNext}\n disabled={disableKeyboardShortcuts}\n />\n </div>\n </Popover.Detail>\n </Popover>\n </div>\n );\n};\n\n// ------------------------------------------------------------------\n// Main Component: Carousel Root\n// ------------------------------------------------------------------\ntype CarouselProps = HTMLAttributes<HTMLDivElement> & {\n children: ReactNode;\n initialIndex?: number;\n onIndexChange?: (index: number) => void;\n};\n\nconst partitionCarouselChildren = (\n children: ReactNode[]\n): [ReactElement[], ReactNode[]] => {\n const slides: ReactElement[] = [];\n const others: ReactNode[] = [];\n\n children.forEach((child) => {\n if (\n isValidElement(child) &&\n (child.type === CarouselItem ||\n (child.type as { isCarouselItem?: boolean }).isCarouselItem === true)\n ) {\n slides.push(child);\n } else {\n others.push(child);\n }\n });\n\n return [slides, others];\n};\n\nconst CarouselRoot: FC<CarouselProps> = ({\n children,\n className,\n initialIndex = 0,\n onIndexChange,\n ...props\n}) => {\n const allChildren = Children.toArray(children);\n const [slides, others] = partitionCarouselChildren(allChildren);\n const totalItems = slides.length;\n\n // State Management\n const [selectedIndex, setSelectedIndex] = useState<number>(initialIndex);\n const [displayedIndex, setDisplayedIndex] = useState<number>(initialIndex);\n const [containerHeight, setContainerHeight] = useState<number>(0);\n const [containerWidth, setContainerWidth] = useState<number>(0);\n\n // Drag State\n const [startX, setStartX] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n\n // Refs\n const containerRef = useRef<HTMLDivElement>(null);\n const itemsRef = useRef<(HTMLDivElement | null)[]>([]);\n const itemHeightsRef = useRef<Map<Element, number>>(new Map());\n\n // Navigation Logic\n const handleSwitchItem = (diff: number) => {\n if (containerWidth === 0) return;\n\n // Use container width for the threshold\n const swipeStep = containerWidth / SWIPE_THRESHOLD_DIVISOR;\n const numSwipe = Math.round(diff / swipeStep);\n\n if (Math.abs(numSwipe) >= 1) {\n const newIndex = displayedIndex - numSwipe;\n const clampedIndex = Math.max(0, Math.min(newIndex, totalItems - 1));\n\n if (clampedIndex !== selectedIndex) {\n setSelectedIndex(clampedIndex);\n setStartX((prev) => prev + diff);\n }\n }\n };\n\n const handleNext = () => {\n setSelectedIndex((prev) => Math.min(prev + 1, totalItems - 1));\n };\n\n const handlePrev = () => {\n setSelectedIndex((prev) => Math.max(prev - 1, 0));\n };\n\n // Input Handlers\n const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {\n setIsDragging(true);\n setStartX(e.clientX);\n };\n const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {\n if (isDragging) handleSwitchItem(e.clientX - startX);\n };\n const handleDragEnd = () => setIsDragging(false);\n const handleTouchStart: TouchEventHandler<HTMLDivElement> = (e) => {\n setIsDragging(true);\n setStartX(e.touches[0]!.clientX);\n };\n const handleTouchMove: TouchEventHandler<HTMLDivElement> = (e) => {\n if (isDragging) handleSwitchItem(e.touches[0]!.clientX - startX);\n };\n\n // Effects\n useEffect(() => {\n if (selectedIndex) onIndexChange?.(selectedIndex);\n }, [selectedIndex, onIndexChange]);\n\n useEffect(() => {\n let interval: NodeJS.Timeout;\n\n if (selectedIndex !== displayedIndex) {\n interval = setInterval(() => {\n setDisplayedIndex((prev) => {\n if (prev === selectedIndex) {\n clearInterval(interval);\n return prev;\n }\n return prev < selectedIndex ? prev + 1 : prev - 1;\n });\n }, TRANSITION_DELAY_MS);\n }\n return () => clearInterval(interval);\n }, [selectedIndex, displayedIndex]);\n\n /**\n * Tracks the container width and the height of the tallest slide.\n *\n * The container height is the *output* of this measurement, so the observer\n * must never read geometry back from the DOM: React commits the new inline\n * height between two observer callbacks, and every read after that commit is\n * a forced reflow. `ResizeObserverEntry` already carries the boxes the\n * browser computed during layout, which are free to read.\n */\n useLayoutEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const items = itemsRef.current.filter((item) => item !== null);\n const itemHeights = itemHeightsRef.current;\n\n const publishMaxHeight = () => {\n const maxHeight = Math.max(0, ...itemHeights.values());\n\n if (maxHeight > 0) {\n setContainerHeight(maxHeight + CONTAINER_VERTICAL_PADDING_PX);\n }\n };\n\n // First pass: every read happens before any state write, so the initial\n // measurement costs a single layout.\n const initialWidth = container.clientWidth;\n items.forEach((item) => {\n itemHeights.set(item, item.offsetHeight);\n });\n\n setContainerWidth(initialWidth);\n publishMaxHeight();\n\n const observer = new ResizeObserver((entries) => {\n entries.forEach((entry) => {\n if (entry.target === container) {\n setContainerWidth(entry.contentRect.width);\n return;\n }\n\n itemHeights.set(entry.target, getEntryBlockSize(entry));\n });\n\n publishMaxHeight();\n });\n\n observer.observe(container);\n items.forEach((item) => {\n observer.observe(item);\n });\n\n return () => {\n observer.disconnect();\n itemHeights.clear();\n };\n }, [totalItems]);\n\n return (\n <CarouselContext.Provider\n value={{\n selectedIndex,\n setSelectedIndex,\n totalItems,\n handlePrev,\n handleNext,\n }}\n >\n <div\n ref={containerRef}\n className={cn(\n 'relative flex w-full cursor-grab select-none items-center overflow-hidden pb-4 outline-none transition-[height] duration-300 ease-in-out focus:outline-none focus:outline-none focus:ring-0 active:cursor-grabbing',\n 'max-w-350',\n className\n )}\n style={{\n height: containerHeight > 0 ? containerHeight : 'auto',\n minHeight: `${CONTAINER_MIN_HEIGHT_PX}px`,\n }}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleDragEnd}\n onMouseLeave={handleDragEnd}\n onTouchStart={handleTouchStart}\n onTouchMove={handleTouchMove}\n onTouchEnd={handleDragEnd}\n role=\"region\"\n aria-label=\"Carousel\"\n {...props}\n >\n {slides.map((child, index) => {\n return (\n <div\n key={index}\n role=\"button\"\n tabIndex={0}\n ref={(el) => {\n itemsRef.current[index] = el;\n }}\n // FIX 2: Removed isMounted checks and invisible classes.\n // CSS units allow correct SSR rendering.\n className={cn(\n 'absolute left-1/2 -translate-x-1/2 transition-all duration-300 ease-in-out',\n 'outline-none ring-0 focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0',\n getCardStyle(index, displayedIndex)\n )}\n onClick={(e) => {\n e.stopPropagation();\n setSelectedIndex(index);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') setSelectedIndex(index);\n }}\n style={{\n transform: `\n translateX(${getCardPositionX(\n index,\n displayedIndex,\n containerWidth\n )})\n scale(${getCardScale(index, displayedIndex)})\n `,\n }}\n >\n {child}\n </div>\n );\n })}\n\n {others}\n </div>\n </CarouselContext.Provider>\n );\n};\n\nexport const Carousel = Object.assign(CarouselRoot, {\n Item: CarouselItem,\n Indicators: CarouselIndicators,\n});\n"],"mappings":"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"}
|
|
@@ -75,7 +75,7 @@ const ExpandCollapse = ({ isRollable = true, minHeight = DEFAULT_MIN_HEIGHT, chi
|
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
const observer = new ResizeObserver(([entry]) => {
|
|
78
|
-
if (entry) setCodeContainerHeight(entry.
|
|
78
|
+
if (entry) setCodeContainerHeight(entry.contentRect.height);
|
|
79
79
|
});
|
|
80
80
|
observer.observe(container);
|
|
81
81
|
resizeObserverRef.current = observer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpandCollapse.mjs","names":[],"sources":["../../../../src/components/ExpandCollapse/ExpandCollapse.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport {\n type FC,\n type ReactNode,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\n\n/**\n * Props for the ExpandCollapse component\n */\nexport type ExpandCollapseProps = {\n /** Whether the component should provide expand/collapse functionality. If false, renders children directly */\n isRollable?: boolean;\n /** Minimum height in pixels before showing the expand/collapse toggle */\n minHeight?: number;\n /** Content that may overflow and trigger the expand/collapse behavior */\n children: ReactNode;\n /** Additional CSS classes for styling customization */\n className?: string;\n};\n\n/** Default minimum height threshold for triggering expand/collapse behavior */\nconst DEFAULT_MIN_HEIGHT = 700;\n\n/**\n * ExpandCollapse Component\n *\n * A smart content container that automatically provides expand/collapse functionality\n * when content exceeds a specified height threshold. Features smooth animations,\n * internationalized toggle text, and intelligent height detection.\n *\n * @example\n * ```tsx\n * <ExpandCollapse minHeight={300}>\n * <div>Very long content that will be collapsed...</div>\n * </ExpandCollapse>\n * ```\n *\n * ## Key Features\n * - **Smart Detection**: Automatically detects when content exceeds height threshold\n * - **Smooth Animations**: Uses CSS transitions for smooth expand/collapse effects\n * - **Internationalization**: Toggle text supports multiple languages via Intlayer\n * - **Customizable Height**: Configurable minimum height threshold\n * - **Performance Optimized**: Only applies collapse behavior when necessary\n * - **Accessibility**: Proper ARIA attributes and keyboard support\n *\n * ## Behavior Logic\n * 1. **Measurement Phase**: Measures actual content height on mount\n * 2. **Comparison**: Compares content height against minHeight threshold\n * 3. **Conditional Rendering**:\n * - If content ≤ minHeight: Renders normally without collapse functionality\n * - If content > minHeight: Enables expand/collapse with toggle button\n * 4. **State Management**: Manages collapsed/expanded state with smooth transitions\n *\n * ## When to Use\n * - Long-form content (articles, documentation, code blocks)\n * - Lists or tables that may grow beyond comfortable viewing height\n * - User-generated content with unpredictable length\n * - FAQ sections or expandable content cards\n * - Code examples or JSON data display\n *\n * ## Accessibility Features\n * - **Keyboard Navigation**: Toggle button is focusable and keyboard accessible\n * - **Screen Reader Support**: Proper ARIA labels and state announcements\n * - **Visual Indicators**: Clear visual cues for collapsed/expanded states\n * - **Smooth Animations**: Respects user preferences for reduced motion\n *\n * ## Internationalization\n * - Supports multiple languages through Intlayer integration\n * - Toggle text automatically adapts to current locale\n * - Includes translations for \"Show all\" and \"Show less\" states\n *\n * @param props - ExpandCollapseProps\n * @returns React functional component\n */\nexport const ExpandCollapse: FC<ExpandCollapseProps> = ({\n isRollable = true,\n minHeight = DEFAULT_MIN_HEIGHT,\n children,\n className,\n}) => {\n const [codeContainerHeight, setCodeContainerHeight] = useState(0);\n const [isCollapsed, setIsCollapsed] = useState(true);\n const resizeObserverRef = useRef<ResizeObserver | null>(null);\n const { expandCollapseContent } = useIntlayer('expand-collapse');\n\n const isTooBig = codeContainerHeight > minHeight;\n\n /*\n * A `ResizeObserver` is handed the size the browser has already computed,\n * where reading `clientHeight` from an effect forces it to lay the page out\n * again on the spot. That distinction matters here because a documentation\n * page mounts one of these per code block: dozens of synchronous reflows over\n * a very large document. The observer also reports later size changes, which\n * is what the window `resize` listener used to cover.\n *\n * It attaches through a callback ref rather than an effect because crossing\n * the `minHeight` threshold re-parents the container into `MaxHeightSmoother`\n * — a different element, which an effect bound to mount would never observe.\n */\n const observeContainer = useCallback((container: HTMLDivElement | null) => {\n resizeObserverRef.current?.disconnect();\n resizeObserverRef.current = null;\n\n if (!container) return;\n\n if (typeof ResizeObserver === 'undefined') {\n setCodeContainerHeight(container.clientHeight);\n return;\n }\n\n const observer = new ResizeObserver(([entry]) => {\n //
|
|
1
|
+
{"version":3,"file":"ExpandCollapse.mjs","names":[],"sources":["../../../../src/components/ExpandCollapse/ExpandCollapse.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport {\n type FC,\n type ReactNode,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\n\n/**\n * Props for the ExpandCollapse component\n */\nexport type ExpandCollapseProps = {\n /** Whether the component should provide expand/collapse functionality. If false, renders children directly */\n isRollable?: boolean;\n /** Minimum height in pixels before showing the expand/collapse toggle */\n minHeight?: number;\n /** Content that may overflow and trigger the expand/collapse behavior */\n children: ReactNode;\n /** Additional CSS classes for styling customization */\n className?: string;\n};\n\n/** Default minimum height threshold for triggering expand/collapse behavior */\nconst DEFAULT_MIN_HEIGHT = 700;\n\n/**\n * ExpandCollapse Component\n *\n * A smart content container that automatically provides expand/collapse functionality\n * when content exceeds a specified height threshold. Features smooth animations,\n * internationalized toggle text, and intelligent height detection.\n *\n * @example\n * ```tsx\n * <ExpandCollapse minHeight={300}>\n * <div>Very long content that will be collapsed...</div>\n * </ExpandCollapse>\n * ```\n *\n * ## Key Features\n * - **Smart Detection**: Automatically detects when content exceeds height threshold\n * - **Smooth Animations**: Uses CSS transitions for smooth expand/collapse effects\n * - **Internationalization**: Toggle text supports multiple languages via Intlayer\n * - **Customizable Height**: Configurable minimum height threshold\n * - **Performance Optimized**: Only applies collapse behavior when necessary\n * - **Accessibility**: Proper ARIA attributes and keyboard support\n *\n * ## Behavior Logic\n * 1. **Measurement Phase**: Measures actual content height on mount\n * 2. **Comparison**: Compares content height against minHeight threshold\n * 3. **Conditional Rendering**:\n * - If content ≤ minHeight: Renders normally without collapse functionality\n * - If content > minHeight: Enables expand/collapse with toggle button\n * 4. **State Management**: Manages collapsed/expanded state with smooth transitions\n *\n * ## When to Use\n * - Long-form content (articles, documentation, code blocks)\n * - Lists or tables that may grow beyond comfortable viewing height\n * - User-generated content with unpredictable length\n * - FAQ sections or expandable content cards\n * - Code examples or JSON data display\n *\n * ## Accessibility Features\n * - **Keyboard Navigation**: Toggle button is focusable and keyboard accessible\n * - **Screen Reader Support**: Proper ARIA labels and state announcements\n * - **Visual Indicators**: Clear visual cues for collapsed/expanded states\n * - **Smooth Animations**: Respects user preferences for reduced motion\n *\n * ## Internationalization\n * - Supports multiple languages through Intlayer integration\n * - Toggle text automatically adapts to current locale\n * - Includes translations for \"Show all\" and \"Show less\" states\n *\n * @param props - ExpandCollapseProps\n * @returns React functional component\n */\nexport const ExpandCollapse: FC<ExpandCollapseProps> = ({\n isRollable = true,\n minHeight = DEFAULT_MIN_HEIGHT,\n children,\n className,\n}) => {\n const [codeContainerHeight, setCodeContainerHeight] = useState(0);\n const [isCollapsed, setIsCollapsed] = useState(true);\n const resizeObserverRef = useRef<ResizeObserver | null>(null);\n const { expandCollapseContent } = useIntlayer('expand-collapse');\n\n const isTooBig = codeContainerHeight > minHeight;\n\n /*\n * A `ResizeObserver` is handed the size the browser has already computed,\n * where reading `clientHeight` from an effect forces it to lay the page out\n * again on the spot. That distinction matters here because a documentation\n * page mounts one of these per code block: dozens of synchronous reflows over\n * a very large document. The observer also reports later size changes, which\n * is what the window `resize` listener used to cover.\n *\n * It attaches through a callback ref rather than an effect because crossing\n * the `minHeight` threshold re-parents the container into `MaxHeightSmoother`\n * — a different element, which an effect bound to mount would never observe.\n */\n const observeContainer = useCallback((container: HTMLDivElement | null) => {\n resizeObserverRef.current?.disconnect();\n resizeObserverRef.current = null;\n\n if (!container) return;\n\n if (typeof ResizeObserver === 'undefined') {\n setCodeContainerHeight(container.clientHeight);\n return;\n }\n\n const observer = new ResizeObserver(([entry]) => {\n // The entry already carries the measured box, so nothing is read back\n // from the element. `clientHeight` here would still force a reflow\n // whenever an earlier observer in the same delivery had dirtied the\n // styles — and a documentation page delivers one per code block.\n if (entry) setCodeContainerHeight(entry.contentRect.height);\n });\n\n observer.observe(container);\n resizeObserverRef.current = observer;\n }, []);\n\n useEffect(() => () => resizeObserverRef.current?.disconnect(), []);\n\n if (!isRollable) {\n return children;\n }\n\n if (!isTooBig) {\n return (\n <div className={cn('grid w-full', className)} ref={observeContainer}>\n {children}\n </div>\n );\n }\n\n return (\n <MaxHeightSmoother\n isHidden={isCollapsed}\n minHeight={minHeight}\n className=\"w-full overflow-x-auto overflow-y-hidden\"\n >\n <div className={cn('grid w-full', className)} ref={observeContainer}>\n {children}\n </div>\n <button\n className={cn(\n 'absolute right-0 bottom-0 flex w-full cursor-pointer items-center justify-center rounded-t-2xl bg-linear-to-t from-card/80 to-transparent px-3 py-0.5 text-md text-neutral-700 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur transition-all duration-300 hover:py-1 dark:text-neutral-400',\n isCollapsed ? 'w-full' : 'w-32'\n )}\n type=\"button\"\n onClick={() => setIsCollapsed((prev) => !prev)}\n >\n {expandCollapseContent(isCollapsed)}\n </button>\n </MaxHeightSmoother>\n );\n};\n"],"mappings":";;;;;;;;;;AA6BA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqD3B,MAAa,kBAA2C,EACtD,aAAa,MACb,YAAY,oBACZ,UACA,gBACI;CACJ,MAAM,CAAC,qBAAqB,0BAA0B,SAAS,CAAC;CAChE,MAAM,CAAC,aAAa,kBAAkB,SAAS,IAAI;CACnD,MAAM,oBAAoB,OAA8B,IAAI;CAC5D,MAAM,EAAE,0BAA0B,YAAY,iBAAiB;CAE/D,MAAM,WAAW,sBAAsB;CAcvC,MAAM,mBAAmB,aAAa,cAAqC;EACzE,kBAAkB,SAAS,WAAW;EACtC,kBAAkB,UAAU;EAE5B,IAAI,CAAC,WAAW;EAEhB,IAAI,OAAO,mBAAmB,aAAa;GACzC,uBAAuB,UAAU,YAAY;GAC7C;EACF;EAEA,MAAM,WAAW,IAAI,gBAAgB,CAAC,WAAW;GAK/C,IAAI,OAAO,uBAAuB,MAAM,YAAY,MAAM;EAC5D,CAAC;EAED,SAAS,QAAQ,SAAS;EAC1B,kBAAkB,UAAU;CAC9B,GAAG,CAAC,CAAC;CAEL,sBAAsB,kBAAkB,SAAS,WAAW,GAAG,CAAC,CAAC;CAEjE,IAAI,CAAC,YACH,OAAO;CAGT,IAAI,CAAC,UACH,OACE,oBAAC,OAAD;EAAK,WAAW,GAAG,eAAe,SAAS;EAAG,KAAK;EAChD;CACE;CAIT,OACE,qBAAC,mBAAD;EACE,UAAU;EACC;EACX,WAAU;YAHZ,CAKE,oBAAC,OAAD;GAAK,WAAW,GAAG,eAAe,SAAS;GAAG,KAAK;GAChD;EACE,IACL,oBAAC,UAAD;GACE,WAAW,GACT,qSACA,cAAc,WAAW,MAC3B;GACA,MAAK;GACL,eAAe,gBAAgB,SAAS,CAAC,IAAI;aAE5C,sBAAsB,WAAW;EAC5B,EACS;;AAEvB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useItemSelector } from "../../../hooks/useItemSelector.mjs";
|
|
4
3
|
import { InputIndicator, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "../../Input/OTPInput.mjs";
|
|
4
|
+
import { useItemSelector } from "../../../hooks/useItemSelector.mjs";
|
|
5
5
|
import { FormField, useFormField } from "../FormField.mjs";
|
|
6
6
|
import { FormItemLayout } from "../layout/FormItemLayout.mjs";
|
|
7
7
|
import { useEffect, useRef } from "react";
|
|
@@ -13,12 +13,12 @@ const selector = (option) => option?.getAttribute("aria-selected") === "true";
|
|
|
13
13
|
const OTPFieldContent = ({ field, name, label, description, isRequired, info, showErrorMessage, children, slots = 6, maxLength = 6, ...props }) => {
|
|
14
14
|
const { error } = useFormField();
|
|
15
15
|
const optionsRefs = useRef([]);
|
|
16
|
-
const { choiceIndicatorPosition,
|
|
16
|
+
const { choiceIndicatorPosition, scheduleMeasurement } = useItemSelector(optionsRefs, {
|
|
17
17
|
selector,
|
|
18
18
|
isHoverable: true
|
|
19
19
|
});
|
|
20
20
|
useEffect(() => {
|
|
21
|
-
|
|
21
|
+
scheduleMeasurement();
|
|
22
22
|
}, []);
|
|
23
23
|
return /* @__PURE__ */ jsx(FormItemLayout, {
|
|
24
24
|
htmlFor: name,
|
|
@@ -33,7 +33,7 @@ const OTPFieldContent = ({ field, name, label, description, isRequired, info, sh
|
|
|
33
33
|
defaultValue: field.value,
|
|
34
34
|
maxLength,
|
|
35
35
|
pattern: "^[0-9]+$",
|
|
36
|
-
onActiveSlotChange:
|
|
36
|
+
onActiveSlotChange: scheduleMeasurement,
|
|
37
37
|
...props,
|
|
38
38
|
children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
39
39
|
/* @__PURE__ */ jsxs(InputOTPGroup, { children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OTPElement.mjs","names":[],"sources":["../../../../../src/components/Form/elements/OTPElement.tsx"],"sourcesContent":["'use client';\n\nimport {\n InputIndicator,\n InputOTP,\n InputOTPGroup,\n InputOTPSeparator,\n InputOTPSlot,\n} from '@components/Input/OTPInput';\nimport { useItemSelector } from '@hooks/useItemSelector';\nimport { type ComponentProps, type ReactNode, useEffect, useRef } from 'react';\nimport { useFormContext } from 'react-hook-form';\nimport { FormField, useFormField } from '../FormField';\nimport { FormItemLayout } from '../layout/FormItemLayout';\nimport type { FormElementProps } from './FormElement';\n\ntype OTPElementsProps = Omit<FormElementProps<typeof InputOTP>, 'Element'> &\n Omit<ComponentProps<typeof InputOTP>, 'children'> & {\n name: string;\n description?: string;\n placeholder?: string;\n className?: string;\n children?: ReactNode;\n /** Number of OTP slots (default: 6) */\n slots?: number;\n };\n\nconst selector = (option: HTMLElement) =>\n option?.getAttribute('aria-selected') === 'true';\n\nconst OTPFieldContent = ({\n field,\n name,\n label,\n description,\n isRequired,\n info,\n showErrorMessage,\n children,\n slots = 6,\n maxLength = 6,\n ...props\n}: Omit<OTPElementsProps, 'control'> & { field: any }) => {\n const { error } = useFormField();\n\n const optionsRefs = useRef<HTMLElement[]>([]);\n const { choiceIndicatorPosition,
|
|
1
|
+
{"version":3,"file":"OTPElement.mjs","names":[],"sources":["../../../../../src/components/Form/elements/OTPElement.tsx"],"sourcesContent":["'use client';\n\nimport {\n InputIndicator,\n InputOTP,\n InputOTPGroup,\n InputOTPSeparator,\n InputOTPSlot,\n} from '@components/Input/OTPInput';\nimport { useItemSelector } from '@hooks/useItemSelector';\nimport { type ComponentProps, type ReactNode, useEffect, useRef } from 'react';\nimport { useFormContext } from 'react-hook-form';\nimport { FormField, useFormField } from '../FormField';\nimport { FormItemLayout } from '../layout/FormItemLayout';\nimport type { FormElementProps } from './FormElement';\n\ntype OTPElementsProps = Omit<FormElementProps<typeof InputOTP>, 'Element'> &\n Omit<ComponentProps<typeof InputOTP>, 'children'> & {\n name: string;\n description?: string;\n placeholder?: string;\n className?: string;\n children?: ReactNode;\n /** Number of OTP slots (default: 6) */\n slots?: number;\n };\n\nconst selector = (option: HTMLElement) =>\n option?.getAttribute('aria-selected') === 'true';\n\nconst OTPFieldContent = ({\n field,\n name,\n label,\n description,\n isRequired,\n info,\n showErrorMessage,\n children,\n slots = 6,\n maxLength = 6,\n ...props\n}: Omit<OTPElementsProps, 'control'> & { field: any }) => {\n const { error } = useFormField();\n\n const optionsRefs = useRef<HTMLElement[]>([]);\n const { choiceIndicatorPosition, scheduleMeasurement } = useItemSelector(\n optionsRefs,\n {\n selector,\n isHoverable: true,\n }\n );\n\n useEffect(() => {\n scheduleMeasurement();\n }, []);\n\n return (\n <FormItemLayout\n htmlFor={name}\n label={label}\n description={description}\n isRequired={isRequired}\n info={info}\n showErrorMessage={showErrorMessage}\n aria-invalid={!!error}\n >\n <InputOTP\n onChange={field.onChange}\n defaultValue={field.value}\n maxLength={maxLength}\n pattern=\"^[0-9]+$\"\n onActiveSlotChange={scheduleMeasurement}\n {...(props as any)}\n >\n {children ?? (\n <>\n <InputOTPGroup>\n <InputOTPSlot\n index={0}\n ref={(el) => {\n optionsRefs.current[0] = el!;\n }}\n />\n <InputOTPSlot\n index={1}\n ref={(el) => {\n optionsRefs.current[1] = el!;\n }}\n />\n <InputOTPSlot\n index={2}\n ref={(el) => {\n optionsRefs.current[2] = el!;\n }}\n />\n </InputOTPGroup>\n <InputOTPSeparator />\n <InputOTPGroup>\n <InputOTPSlot\n index={3}\n ref={(el) => {\n optionsRefs.current[3] = el!;\n }}\n />\n <InputOTPSlot\n index={4}\n ref={(el) => {\n optionsRefs.current[4] = el!;\n }}\n />\n <InputOTPSlot\n index={5}\n ref={(el) => {\n optionsRefs.current[5] = el!;\n }}\n />\n </InputOTPGroup>\n {choiceIndicatorPosition && (\n <InputIndicator style={choiceIndicatorPosition} />\n )}\n </>\n )}\n </InputOTP>\n </FormItemLayout>\n );\n};\n\nexport const OTPElement = ({\n name,\n description,\n label,\n isRequired,\n info,\n showErrorMessage,\n children,\n slots = 6,\n maxLength = 6,\n ...props\n}: OTPElementsProps) => {\n const { control } = useFormContext();\n\n return (\n <FormField\n control={control}\n name={name}\n render={({ field }) => (\n <OTPFieldContent\n field={field}\n name={name}\n label={label}\n description={description}\n isRequired={isRequired}\n info={info}\n showErrorMessage={showErrorMessage}\n slots={slots}\n maxLength={maxLength}\n {...props}\n >\n {children}\n </OTPFieldContent>\n )}\n />\n );\n};\n"],"mappings":";;;;;;;;;;;AA2BA,MAAM,YAAY,WAChB,QAAQ,aAAa,eAAe,MAAM;AAE5C,MAAM,mBAAmB,EACvB,OACA,MACA,OACA,aACA,YACA,MACA,kBACA,UACA,QAAQ,GACR,YAAY,GACZ,GAAG,YACqD;CACxD,MAAM,EAAE,UAAU,aAAa;CAE/B,MAAM,cAAc,OAAsB,CAAC,CAAC;CAC5C,MAAM,EAAE,yBAAyB,wBAAwB,gBACvD,aACA;EACE;EACA,aAAa;CACf,CACF;CAEA,gBAAgB;EACd,oBAAoB;CACtB,GAAG,CAAC,CAAC;CAEL,OACE,oBAAC,gBAAD;EACE,SAAS;EACF;EACM;EACD;EACN;EACY;EAClB,gBAAc,CAAC,CAAC;YAEhB,oBAAC,UAAD;GACE,UAAU,MAAM;GAChB,cAAc,MAAM;GACT;GACX,SAAQ;GACR,oBAAoB;GACpB,GAAK;aAEJ,YACC;IACE,qBAAC,eAAD;KACE,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;KACD,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;KACD,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;IACY;IACf,oBAAC,mBAAD,CAAoB;IACpB,qBAAC,eAAD;KACE,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;KACD,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;KACD,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;IACY;IACd,2BACC,oBAAC,gBAAD,EAAgB,OAAO,wBAA0B;GAEnD;EAEI;CACI;AAEpB;AAEA,MAAa,cAAc,EACzB,MACA,aACA,OACA,YACA,MACA,kBACA,UACA,QAAQ,GACR,YAAY,GACZ,GAAG,YACmB;CACtB,MAAM,EAAE,YAAY,eAAe;CAEnC,OACE,oBAAC,WAAD;EACW;EACH;EACN,SAAS,EAAE,YACT,oBAAC,iBAAD;GACS;GACD;GACC;GACM;GACD;GACN;GACY;GACX;GACI;GACX,GAAI;GAEH;EACc;CAEpB;AAEL"}
|
|
@@ -155,17 +155,23 @@ const isTextChildren = (children) => {
|
|
|
155
155
|
return false;
|
|
156
156
|
};
|
|
157
157
|
const Link = (props) => {
|
|
158
|
-
const { variant = "default", color = "custom", roundedSize, children, label, className, isActive, underlined, locale, size: sizeProp, isExternalLink: isExternalLinkProp, isPageSection: isPageSectionProp, href: hrefProp, ...otherProps } = props;
|
|
158
|
+
const { variant = "default", color = "custom", roundedSize, children, label, className, isActive, underlined, locale, size: sizeProp, isExternalLink: isExternalLinkProp, isPageSection: isPageSectionProp, href: hrefProp, rel: relProp, target: targetProp, ...otherProps } = props;
|
|
159
159
|
const isButton = variant === "button" || variant === "button-outlined";
|
|
160
160
|
const size = sizeProp ?? (isButton ? "md" : "custom");
|
|
161
161
|
const isExternalLink = isExternalLinkProp ?? checkIsExternalLink(props);
|
|
162
162
|
const isPageSection = isPageSectionProp ?? hrefProp?.startsWith("#") ?? false;
|
|
163
163
|
const isChildrenString = isTextChildren(children);
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
/**
|
|
165
|
+
* External links always carry the full safety/SEO `rel`, so a caller cannot
|
|
166
|
+
* accidentally strip `nofollow`. Internal links keep whatever the caller asked for.
|
|
167
|
+
*/
|
|
168
|
+
const rel = isExternalLink ? "noopener noreferrer nofollow" : relProp;
|
|
169
|
+
const target = isExternalLink ? "_blank" : targetProp ?? "_self";
|
|
166
170
|
const resolvedHref = locale && hrefProp && !isExternalLink && !isPageSection ? getLocalizedUrl(hrefProp, locale) : hrefProp;
|
|
171
|
+
const href = resolvedHref === "" ? void 0 : resolvedHref;
|
|
167
172
|
return /* @__PURE__ */ jsxs("a", {
|
|
168
|
-
|
|
173
|
+
...otherProps,
|
|
174
|
+
href,
|
|
169
175
|
"aria-label": label,
|
|
170
176
|
rel,
|
|
171
177
|
target,
|
|
@@ -179,7 +185,6 @@ const Link = (props) => {
|
|
|
179
185
|
size,
|
|
180
186
|
className
|
|
181
187
|
})),
|
|
182
|
-
...otherProps,
|
|
183
188
|
children: [
|
|
184
189
|
isButton && isChildrenString ? /* @__PURE__ */ jsx("span", { children }) : children,
|
|
185
190
|
isExternalLink && isChildrenString && /* @__PURE__ */ jsx(ExternalLink, { className: "ml-2 inline-block size-4" }),
|