From 2ac7b1832173cd27c3268eb461d04e8ced9b554e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Pe=CC=81rard?= Date: Mon, 28 Apr 2025 15:26:11 +0200 Subject: [PATCH 1/7] wip --- app/lib/ui-state.ts | 61 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/app/lib/ui-state.ts b/app/lib/ui-state.ts index 05f098ce3..35af79a3b 100644 --- a/app/lib/ui-state.ts +++ b/app/lib/ui-state.ts @@ -7,17 +7,10 @@ type AvailableStatus = | 'default' | (string & {}); // Allows extra status -export const getUiState = < +type UiState< Status extends AvailableStatus, Data extends Record, ->( - getState: ( - set: >( - status: S, - data?: D - ) => { __status: S } & D - ) => { __status: Status } & Data -): { +> = { with: ( status: S ) => { @@ -27,20 +20,64 @@ export const getUiState = < state: { __status: Status; } & Data; -} => { + match: ( + status: Array, + handler: (data: Data) => React.ReactNode, + __matched?: boolean, + render?: () => React.ReactNode + ) => UiState & { render: () => React.ReactNode }; +}; + +export const getUiState = < + Status extends AvailableStatus, + Data extends Record, +>( + getState: ( + set: >( + status: S, + data?: D + ) => { __status: S } & D + ) => { __status: Status } & Data +): UiState => { const state = getState((status, data = {} as ExplicitAny) => { return { __status: status, ...data, }; }); - return { + + const isMatching = ( + status: Array + ): status is Array => status.includes(state.__status); + + const uiState: UiState = { + state, with: (status) => ({ __status: status, }), is: (status) => { return state.__status === status; }, - state, + match: (status, handler, __matched = false, render = () => null) => { + if (!__matched && isMatching(status)) { + return { + ...uiState, + __matched: true, + render: () => handler(state), + match: (status, _handler) => + uiState.match(status, _handler, true, () => handler(uiState.state)), + }; + } + + return { + ...uiState, + __matched, + render, + match: (status, handler) => + uiState.match(status, handler, __matched, render), + }; + }, }; + + return uiState; }; From 5750f8782b992f9d594288f9cd85838fa6659ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Pe=CC=81rard?= Date: Tue, 29 Apr 2025 16:03:23 +0200 Subject: [PATCH 2/7] working ? --- app/lib/ui-state.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/lib/ui-state.ts b/app/lib/ui-state.ts index 35af79a3b..2afb1a78f 100644 --- a/app/lib/ui-state.ts +++ b/app/lib/ui-state.ts @@ -22,7 +22,12 @@ type UiState< } & Data; match: ( status: Array, - handler: (data: Data) => React.ReactNode, + handler: ( + data: Omit< + Extract['state'], { __status: S }>, + '__status' + > + ) => React.ReactNode, __matched?: boolean, render?: () => React.ReactNode ) => UiState & { render: () => React.ReactNode }; @@ -63,9 +68,11 @@ export const getUiState = < return { ...uiState, __matched: true, - render: () => handler(state), + render: () => handler(state as ExplicitAny), match: (status, _handler) => - uiState.match(status, _handler, true, () => handler(uiState.state)), + uiState.match(status, _handler, true, () => + handler(uiState.state as ExplicitAny) + ), }; } From 4fde3e4e35b71889a055d12fcc665d58346fd592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Pe=CC=81rard?= Date: Tue, 29 Apr 2025 16:12:44 +0200 Subject: [PATCH 3/7] allow array and string --- app/lib/ui-state.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/lib/ui-state.ts b/app/lib/ui-state.ts index 2afb1a78f..84d95d276 100644 --- a/app/lib/ui-state.ts +++ b/app/lib/ui-state.ts @@ -21,7 +21,7 @@ type UiState< __status: Status; } & Data; match: ( - status: Array, + status: S | Array, handler: ( data: Omit< Extract['state'], { __status: S }>, @@ -51,7 +51,10 @@ export const getUiState = < }; }); - const isMatching = ( + const isMatching = (status: Status): status is S => + status === state.__status; + + const isMatchingArray = ( status: Array ): status is Array => status.includes(state.__status); @@ -64,7 +67,11 @@ export const getUiState = < return state.__status === status; }, match: (status, handler, __matched = false, render = () => null) => { - if (!__matched && isMatching(status)) { + if ( + !__matched && typeof status === 'string' + ? isMatching(status) + : isMatchingArray(status as Array) + ) { return { ...uiState, __matched: true, From ccca921c475c44e4b718545c5e57996ef04277d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Pe=CC=81rard?= Date: Tue, 29 Apr 2025 16:15:57 +0200 Subject: [PATCH 4/7] integrate ui state match in repos --- .../repository/app/page-repositories.tsx | 93 +++++++++++++------ 1 file changed, 63 insertions(+), 30 deletions(-) diff --git a/app/features/repository/app/page-repositories.tsx b/app/features/repository/app/page-repositories.tsx index e432625a7..e95b9b8dc 100644 --- a/app/features/repository/app/page-repositories.tsx +++ b/app/features/repository/app/page-repositories.tsx @@ -41,36 +41,69 @@ export const PageRepositories = () => { Repositories - - {match(ui.state) - .with(ui.with('pending'), () => <>Loading...) // TODO Design - .with(ui.with('error'), () => ) - .with(ui.with('empty'), () => <>No Repo) // TODO Design - .with(ui.with('default'), ({ items }) => ( - <> - {items.map((item) => ( - - Repo: {item.name} - - ))} - {repositoriesQuery.hasNextPage && ( - - )} - - )) - .exhaustive()} + +
+ {ui + .match('pending', () => <>Loading...) + .match('error', () => ) + .match('empty', () => <>No repo) + .match('default', ({ items }) => ( + <> + {items.map((item) => ( + + Repo: {item.name} + + ))} + {repositoriesQuery.hasNextPage && ( + + )} + + )) + .render()} +
+
+ {match(ui.state) + .with(ui.with('pending'), () => <>Loading...) // TODO Design + .with(ui.with('error'), () => ) + .with(ui.with('empty'), () => <>No Repo) // TODO Design + .with(ui.with('default'), ({ items }) => ( + <> + {items.map((item) => ( + + Repo: {item.name} + + ))} + {repositoriesQuery.hasNextPage && ( + + )} + + )) + .exhaustive()} +
); From 29c60a377ff5e11ee5b789abf5f1246d1545dda1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Pe=CC=81rard?= Date: Tue, 29 Apr 2025 18:14:29 +0200 Subject: [PATCH 5/7] exhaustive render --- app/lib/ui-state.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/lib/ui-state.ts b/app/lib/ui-state.ts index 84d95d276..2ecd29929 100644 --- a/app/lib/ui-state.ts +++ b/app/lib/ui-state.ts @@ -30,7 +30,10 @@ type UiState< ) => React.ReactNode, __matched?: boolean, render?: () => React.ReactNode - ) => UiState & { render: () => React.ReactNode }; + ) => UiState, Data> & + (Exclude extends never + ? { render: () => React.ReactNode } + : void); }; export const getUiState = < @@ -73,7 +76,7 @@ export const getUiState = < : isMatchingArray(status as Array) ) { return { - ...uiState, + ...(uiState as ExplicitAny), __matched: true, render: () => handler(state as ExplicitAny), match: (status, _handler) => From bebd59d5a268a79f6f59ccf4cd358f6aeb3edd3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Pe=CC=81rard?= Date: Wed, 30 Apr 2025 09:37:03 +0200 Subject: [PATCH 6/7] use ui match instead of ts-pattern --- app/components/ui/select.tsx | 16 +--- .../repository/app/page-repositories.tsx | 94 ++++++------------- .../repository/app/page-repository.tsx | 25 +++-- .../repository/manager/page-repositories.tsx | 15 ++- .../repository/manager/page-repository.tsx | 25 +++-- .../user/manager/page-user-update.tsx | 13 +-- app/features/user/manager/page-user.tsx | 38 ++++---- app/features/user/manager/page-users.tsx | 15 ++- app/lib/ui-state.ts | 15 +-- 9 files changed, 96 insertions(+), 160 deletions(-) diff --git a/app/components/ui/select.tsx b/app/components/ui/select.tsx index 4d4aaffce..27889662c 100644 --- a/app/components/ui/select.tsx +++ b/app/components/ui/select.tsx @@ -7,7 +7,6 @@ import { Portal } from '@ark-ui/react/portal'; import { ChevronDown, X } from 'lucide-react'; import { ComponentProps, ReactNode, useMemo, useState } from 'react'; import { isNonNullish, isNullish } from 'remeda'; -import { match } from 'ts-pattern'; import { cn } from '@/lib/tailwind/utils'; import { getUiState } from '@/lib/ui-state'; @@ -157,15 +156,10 @@ export const Select =