Skip to content

Commit fb87899

Browse files
committed
Steal tanStack way of augmenting interface
1 parent ef3783a commit fb87899

File tree

9 files changed

+67
-43
lines changed

9 files changed

+67
-43
lines changed

src/Header/Header.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { fr } from "../lib";
44
import { createComponentI18nApi } from "../lib/i18n";
55
import { symToStr } from "tsafe/symToStr";
66
import { cx } from "../lib/tools/cx";
7-
import type { LinkProps } from "../lib/routing";
7+
import type { RegisteredLinkProps } from "../lib/routing";
88
import { useLink } from "../lib/routing";
99
import type { MainNavigationProps } from "./MainNavigation";
1010
import { MainNavigation } from "./MainNavigation";
@@ -18,7 +18,7 @@ export type HeaderProps = {
1818
serviceTitle?: ReactNode;
1919
serviceTagline?: ReactNode;
2020
/** Don't forget the title on the link for accessibility*/
21-
homeLinkProps: LinkProps;
21+
homeLinkProps: RegisteredLinkProps;
2222
navItems?: MainNavigationProps.Item[];
2323
/** There should be at most three of them */
2424
quickAccessItems?: HeaderProps.QuickAccessItem[];
@@ -69,7 +69,7 @@ export namespace HeaderProps {
6969
};
7070

7171
export type Link = Common & {
72-
linkProps: LinkProps;
72+
linkProps: RegisteredLinkProps;
7373
buttonProps?: undefined;
7474
};
7575

src/Header/MainNavigation/MainNavigation.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { createComponentI18nApi } from "../../lib/i18n";
44
import { symToStr } from "tsafe/symToStr";
55
import { assert } from "tsafe/assert";
66
import type { Equals } from "tsafe";
7-
import type { LinkProps } from "../../lib/routing";
7+
import type { RegisteredLinkProps } from "../../lib/routing";
88
import { fr } from "../../lib";
99
import { cx } from "../../lib/tools/cx";
1010
import { useLink } from "../../lib/routing";
@@ -30,7 +30,7 @@ export namespace MainNavigationProps {
3030
};
3131

3232
export type Link = Common & {
33-
linkProps: LinkProps;
33+
linkProps: RegisteredLinkProps;
3434
menuProps?: undefined;
3535
megaMenuProps?: undefined;
3636
};

src/Header/MainNavigation/Menu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import { fr } from "../../lib";
55
import { cx } from "../../lib/tools/cx";
66
import { assert } from "tsafe/assert";
77
import type { Equals } from "tsafe";
8-
import type { LinkProps } from "../../lib/routing";
8+
import type { RegisteredLinkProps } from "../../lib/routing";
99
import { useLink } from "../../lib/routing";
1010

1111
export type MenuProps = {
1212
className?: string;
1313
classes?: Partial<Record<"root" | "list", string>>;
1414
items: {
1515
text: ReactNode;
16-
linkProps: LinkProps;
16+
linkProps: RegisteredLinkProps;
1717
isActive?: boolean;
1818
}[];
1919
};

src/lib/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { cx } from "./cx";
1010
export type { FrCxArg } from "./cx";
1111
export { DsfrLangProvider } from "./i18n";
1212
export { createDsfrLinkProvider } from "./routing";
13-
export type { LinkProps, HTMLAnchorProps } from "./routing";
13+
export type { RegisterLink, RegisteredLinkProps, HTMLAnchorProps } from "./routing";
1414

1515
export const fr = {
1616
breakpoints,

src/lib/routing.tsx

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,40 @@
11
import React, { createContext, useContext } from "react";
22
import type { ReactNode } from "react";
3+
import { assert } from "tsafe/assert";
34

45
import type { DetailedHTMLProps, AnchorHTMLAttributes } from "react";
56

67
// eslint-disable-next-line @typescript-eslint/no-empty-interface
8+
9+
/*
710
export interface LinkProps extends React.AriaAttributes {
811
className?: string;
912
children?: ReactNode;
1013
}
14+
*/
1115

1216
export type HTMLAnchorProps = DetailedHTMLProps<
1317
AnchorHTMLAttributes<HTMLAnchorElement>,
1418
HTMLAnchorElement
1519
>;
1620

17-
//NOTE: Here we have use as any because the module augmentation that we define in ../next applies unfortunately.
18-
const context = createContext<CreateLinkProviderPrams["Link"]>(props => <a {...(props as any)} />);
21+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
22+
export interface RegisterLink {
23+
// Link: typeof Link
24+
}
25+
26+
export type RegisteredLinkProps = RegisterLink extends {
27+
Link: (props: infer LinkProps) => any;
28+
}
29+
? Omit<LinkProps, "children">
30+
: RegisterLink extends { Link: "a" }
31+
? Omit<HTMLAnchorProps, "children">
32+
: React.AriaAttributes & { className?: string };
33+
34+
const context = createContext<CreateLinkProviderPrams["Link"] | undefined>(undefined);
1935

2036
type CreateLinkProviderPrams = {
21-
Link: (props: LinkProps) => ReturnType<React.FC>;
37+
Link: ((props: RegisteredLinkProps & { children: ReactNode }) => ReturnType<React.FC>) | "a";
2238
};
2339

2440
export function createDsfrLinkProvider(params: CreateLinkProviderPrams) {
@@ -38,7 +54,16 @@ export function createDsfrLinkProvider(params: CreateLinkProviderPrams) {
3854
}
3955

4056
export function useLink() {
41-
const Link = useContext(context);
57+
let Link = useContext(context);
58+
59+
assert(
60+
Link !== undefined,
61+
"You need to specify what routing library is in use in your project, see: https://react-dsfr.etalab.studio/routing"
62+
);
63+
64+
if (Link === "a") {
65+
Link = props => <a {...props} />;
66+
}
4267

4368
return { Link };
4469
}

src/next.tsx

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ import DefaultDocument from "next/document";
3434
import { getAssetUrl } from "./lib/tools/getAssetUrl";
3535
import { setLangToUseIfProviderNotUsed } from "./lib/i18n";
3636
import { getColors } from "./lib/colors";
37-
import { createDsfrLinkProvider } from "./lib/routing";
38-
import Link from "next/link";
3937
import "./dsfr/dsfr.css";
4038
import "./dsfr/utility/icons/icons.css";
4139

@@ -52,15 +50,6 @@ const fontUrlByFileBasename = {
5250
"Spectral-ExtraBold": spectralExtraBoldWoff2Url
5351
} as const;
5452

55-
type InferLinkProps<Link> = Link extends React.ForwardRefExoticComponent<infer Props>
56-
? Props
57-
: never;
58-
59-
declare module "./lib/routing" {
60-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
61-
export interface LinkProps extends InferLinkProps<typeof Link> {}
62-
}
63-
6453
export type Params = Params.WithDarkModeCookie | Params.WithoutDarkModeCookie;
6554
export namespace Params {
6655
export type Common = StartDsfrReactParams & {
@@ -146,8 +135,6 @@ export function createNextDsfrIntegrationApi(params: Params): NextDsfrIntegratio
146135
}
147136
}
148137

149-
const { DsfrLinkProvider } = createDsfrLinkProvider({ Link });
150-
151138
const isDarkPropKey = "dsfrIsDark";
152139

153140
function withDsfr<AppComponent extends NextComponentType<any, any, any>>(
@@ -168,7 +155,7 @@ export function createNextDsfrIntegrationApi(params: Params): NextDsfrIntegratio
168155
}, []);
169156

170157
return (
171-
<DsfrLinkProvider>
158+
<>
172159
<Head>
173160
{process.env.NODE_ENV !== "development" &&
174161
objectKeys(fontUrlByFileBasename)
@@ -210,7 +197,7 @@ export function createNextDsfrIntegrationApi(params: Params): NextDsfrIntegratio
210197
<App {...(props as any)} />
211198
</SsrIsDarkProvider>
212199
)}
213-
</DsfrLinkProvider>
200+
</>
214201
);
215202
}
216203

test/integration/cra/src/index.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@ import { Mui } from "./Mui";
66
import { useRoute, RouteProvider } from "./router";
77
import { Header } from "@codegouvfr/react-dsfr/Header";
88
import { fr } from "@codegouvfr/react-dsfr";
9-
import type { Link as TypeRouteLink } from "type-route";
109
import { routes } from "./router";
10+
import { createDsfrLinkProvider } from "@codegouvfr/react-dsfr";
1111

1212
declare module "@codegouvfr/react-dsfr" {
13-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
14-
export interface LinkProps extends TypeRouteLink { }
15-
13+
interface RegisterLink {
14+
Link: "a";
15+
}
1616
}
1717

18+
const { DsfrLinkProvider } = createDsfrLinkProvider({
19+
"Link": "a"
20+
});
21+
1822
startReactDsfr({
1923
"defaultColorScheme": "system"
2024
});
@@ -32,7 +36,7 @@ function Root() {
3236
const route = useRoute();
3337

3438
return (
35-
<>
39+
<DsfrLinkProvider>
3640
<Header
3741
brandTop={<>INTITULE<br />OFFICIEL</>}
3842
serviceTitle="Nom du site / service"
@@ -63,8 +67,7 @@ function Root() {
6367
}
6468
})()}
6569
</div>
66-
</>
67-
70+
</DsfrLinkProvider>
6871
);
6972

7073

test/integration/next/pages/_app.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ import { Header } from "@codegouvfr/react-dsfr/Header";
66
import { createEmotionSsrAdvancedApproach } from "tss-react/next";
77
import { useStyles } from "tss-react/dsfr";
88
import { fr } from "@codegouvfr/react-dsfr";
9+
import { createDsfrLinkProvider } from "@codegouvfr/react-dsfr";
10+
import Link from "next/link";
11+
12+
declare module "@codegouvfr/react-dsfr" {
13+
interface RegisterLink {
14+
Link: typeof Link;
15+
}
16+
}
17+
18+
const { DsfrLinkProvider } = createDsfrLinkProvider({ Link });
919

1020
const {
1121
withDsfr,
@@ -39,7 +49,7 @@ function App({ Component, pageProps }: AppProps) {
3949
const router = useRouter()
4050

4151
return (
42-
<>
52+
<DsfrLinkProvider>
4353
<Header
4454
brandTop={<>INTITULE<br />OFFICIEL</>}
4555
serviceTitle="Nom du site / service"
@@ -72,7 +82,7 @@ function App({ Component, pageProps }: AppProps) {
7282
<Component {...pageProps} />
7383
</div>
7484
<Display />
75-
</>
85+
</DsfrLinkProvider>
7686
);
7787
}
7888

test/integration/vite/src/main.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@ import { startReactDsfr, createDsfrLinkProvider, fr } from "@codegouvfr/react-ds
66
import { Header } from "@codegouvfr/react-dsfr/Header";
77
import { BrowserRouter } from "react-router-dom";
88
import { Routes, Route, Link, useLocation } from "react-router-dom";
9-
import type { LinkProps as ReactRouterLinkProps } from "react-router-dom";
109
startReactDsfr({ "defaultColorScheme": "system" });
1110

1211
declare module "@codegouvfr/react-dsfr" {
13-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
14-
export interface LinkProps extends ReactRouterLinkProps { }
15-
12+
interface RegisterLink {
13+
Link: typeof Link;
14+
}
1615
}
1716

18-
const { DsfrLinkProvider } = createDsfrLinkProvider({ Link })
17+
const { DsfrLinkProvider } = createDsfrLinkProvider({ Link });
1918

2019
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
2120
<React.StrictMode>
@@ -32,7 +31,7 @@ function Root() {
3231
const location = useLocation();
3332

3433
return (
35-
<>
34+
<DsfrLinkProvider>
3635
<Header
3736
brandTop={<>INTITULE<br />OFFICIEL</>}
3837
serviceTitle="Nom du site / service"
@@ -65,7 +64,7 @@ function Root() {
6564
<Route path="*" element={<h1>404</h1>} />
6665
</Routes>
6766
</div>
68-
</>
67+
</DsfrLinkProvider>
6968

7069
);
7170

0 commit comments

Comments
 (0)