From 5620f61688597796d5c82847f6d22884f1eac99b Mon Sep 17 00:00:00 2001 From: Kenneth Date: Fri, 26 Sep 2025 13:41:37 +0700 Subject: [PATCH 1/6] refactor: refactor DashboardTemplate --- .../DashboardTemplate/DashboardTemplate.tsx | 2 +- .../DashboardTemplate/Sider/index.tsx | 4 +- .../DashboardTemplate/Sider/types.ts | 39 +++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/templates/DashboardTemplate/DashboardTemplate.tsx b/src/templates/DashboardTemplate/DashboardTemplate.tsx index 89bc441..7849128 100644 --- a/src/templates/DashboardTemplate/DashboardTemplate.tsx +++ b/src/templates/DashboardTemplate/DashboardTemplate.tsx @@ -55,7 +55,7 @@ const DashboardTemplateInternal: RdDashboardTemplateComponent = forwardRef((prop ); - + const renderContentHeightLayout = () => ( {headerProps && ( diff --git a/src/templates/DashboardTemplate/Sider/index.tsx b/src/templates/DashboardTemplate/Sider/index.tsx index 8945d06..fb247fd 100644 --- a/src/templates/DashboardTemplate/Sider/index.tsx +++ b/src/templates/DashboardTemplate/Sider/index.tsx @@ -37,7 +37,7 @@ export const DashboardTemplateSider: RdDashboardTemplateSiderComponent = forward useEffect(() => { // On smaller screens, we want the sidebar to be fixed and overlay the content - if (!screens.lg && sidebarMode === 'fullHeight') { + if (!screens[breakpoint] && sidebarMode === 'fullHeight') { setFixed(true); if (sidebarMode === 'fullHeight') { @@ -49,7 +49,7 @@ export const DashboardTemplateSider: RdDashboardTemplateSiderComponent = forward setFixed(false); setCollapsedWidth(DEFAULT_COLLAPSED_WIDTH); } - }, [screens.lg]); + }, [screens[breakpoint]]); return ( ) => React.ReactNode; + /** + * Custom render function for the sidebar header section. + * + * @param collapsed - Indicates whether the sidebar is currently collapsed. + * @returns A React node that will be rendered as the sidebar header. + */ headerSidebar?: (collapsed: boolean) => React.ReactNode; + + /** + * Determines how the sidebar should be positioned relative to the header and content. + * + * @see SidebarMode + */ sidebarMode?: SidebarMode; }; //#endregion From 94d6fda956bd01d9ef7a403abf4ab5882e86eef1 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Wed, 1 Oct 2025 15:12:12 +0700 Subject: [PATCH 2/6] feat: Add Alert.ErrorBoundary component --- src/molecules/Alert/Alert.tsx | 3 ++ src/molecules/Alert/ErrorBoundary.tsx | 33 +++++++++++++ src/molecules/Alert/styles.tsx | 5 ++ src/molecules/Alert/types.ts | 68 +++++++++++++++++++++++++-- 4 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 src/molecules/Alert/ErrorBoundary.tsx diff --git a/src/molecules/Alert/Alert.tsx b/src/molecules/Alert/Alert.tsx index 5522cd3..c1bf97e 100644 --- a/src/molecules/Alert/Alert.tsx +++ b/src/molecules/Alert/Alert.tsx @@ -1,9 +1,12 @@ import { forwardRef } from 'react'; import { AlertStyled } from './styles'; import { RdAlertComponent, RdAlertCompoundedComponent } from './types'; +import { AlertErrorBoundary } from './ErrorBoundary'; export const InternalAlert: RdAlertComponent = forwardRef((props, ref) => { return ; }); export const Alert: RdAlertCompoundedComponent = InternalAlert as RdAlertCompoundedComponent; + +Alert.ErrorBoundary = AlertErrorBoundary; diff --git a/src/molecules/Alert/ErrorBoundary.tsx b/src/molecules/Alert/ErrorBoundary.tsx new file mode 100644 index 0000000..0983203 --- /dev/null +++ b/src/molecules/Alert/ErrorBoundary.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { AlertErrorBoundaryStyled } from './styles'; +import { + RdAlertErrorBoundaryComponent, + RdAlertErrorBoundaryProps, + RdAlertErrorBoundaryStates, +} from './types'; + +export class AlertErrorBoundary + extends React.Component + implements RdAlertErrorBoundaryComponent +{ + state: RdAlertErrorBoundaryStates = { + error: undefined, + info: { + componentStack: '', + }, + }; + + componentDidCatch(error: Error | null, info: object): void { + // update state with error + stack trace from Antd + this.setState({ + error, + info: { + componentStack: (info as any).componentStack ?? '', + }, + }); + } + + render() { + return ; + } +} diff --git a/src/molecules/Alert/styles.tsx b/src/molecules/Alert/styles.tsx index df89122..ddfe420 100644 --- a/src/molecules/Alert/styles.tsx +++ b/src/molecules/Alert/styles.tsx @@ -1,4 +1,9 @@ import styled from '@emotion/styled'; import { Alert } from 'antd'; +import { RdAlertErrorBoundaryComponent } from './types'; export const AlertStyled = styled(Alert)``; + +export const AlertErrorBoundaryStyled = styled( + Alert.ErrorBoundary as typeof RdAlertErrorBoundaryComponent +)``; diff --git a/src/molecules/Alert/types.ts b/src/molecules/Alert/types.ts index 7cc0469..e1dcc04 100644 --- a/src/molecules/Alert/types.ts +++ b/src/molecules/Alert/types.ts @@ -1,11 +1,61 @@ import { Alert, GetProps } from 'antd'; import { AlertRef } from 'antd/es/alert/Alert'; import { ComponentToken as AlertComponentTokenAntd } from 'antd/es/drawer/style'; -import { ComponentProps } from 'react'; +import React, { ComponentProps } from 'react'; type AlertPropsAntd = GetProps; -type AlertErrorBoundaryPropsAntd = GetProps; +/** + * ๐Ÿ“ Explanation: + * + * For most Ant Design components, we can simply get the props type using: + * type XxxPropsAntd = GetProps; + * + * However, `Alert.ErrorBoundary` is a special case: + * - Ant Design does NOT export a public props interface for ErrorBoundary. + * - If we directly use `GetProps`, + * TypeScript will try to expose internal types and throw an error: + * ts(4023): "Exported variable '...' has or is using name '...' but cannot be named". + * + * ๐Ÿš‘ Workaround: + * - Use `ComponentProps` to get the props, + * then clone it via a mapped type to produce a "public" type. + * - This way we avoid referencing Ant Designโ€™s internal types. + * + * ๐Ÿ”ฎ Note: + * - If Ant Design exports a proper `AlertErrorBoundaryProps` type in the future, + * we can safely remove this workaround and write: + * type AlertErrorBoundaryPropsAntd = GetProps; + */ +type AlertErrorBoundaryPropsAntd = { + [K in keyof ComponentProps]: ComponentProps< + typeof Alert.ErrorBoundary + >[K]; +}; + +/** + * ๐Ÿ“ Explanation: + * + * Ant Design also does not export the `ErrorBoundaryStates` type. + * Therefore, instead of extracting it automatically, we need to define this interface manually. + * + * The state shape is referenced from the source code: + * state: { + * error: undefined; + * info: { + * componentStack: string; + * }; + * } + * + * This is simply a copied definition to ensure type safety for our extended components. + * If Ant Design exports `ErrorBoundaryStates` in the future, we should switch to that instead. + */ +interface AlertErrorBoundaryStatesAntd { + error?: Error | null; + info?: { + componentStack?: string; + }; +} type AlertRefAntd = AlertRef; //#endregion @@ -17,6 +67,7 @@ type AlertComponentTokenExtend = {}; //#region Define extended types type AlertPropsExtend = {}; type AlertErrorBoundaryPropsExtend = {}; +type AlertErrorBoundaryStatesExtend = {}; type AlertRefExtend = {}; //#endregion @@ -24,17 +75,26 @@ type AlertRefExtend = {}; //#region Export types export type RdAlertProps = AlertPropsAntd & AlertPropsExtend; export type RdAlertErrorBoundaryProps = AlertErrorBoundaryPropsAntd & AlertErrorBoundaryPropsExtend; +export type RdAlertErrorBoundaryStates = AlertErrorBoundaryStatesAntd & + AlertErrorBoundaryStatesExtend; export type RdAlertRef = AlertRefAntd & AlertRefExtend; export type RdAlertComponentToken = AlertComponentTokenAntd & AlertComponentTokenExtend; //#endregion //#region Define component types -// export type RdAlertComponent = React.FC; export type RdAlertComponent = React.ForwardRefExoticComponent< RdAlertProps & React.RefAttributes >; +export declare class RdAlertErrorBoundaryComponent extends React.Component< + RdAlertErrorBoundaryProps, + RdAlertErrorBoundaryStates +> { + state: RdAlertErrorBoundaryStates; + componentDidCatch(error: Error | null, info: object): void; + render(): React.ReactNode; +} export type RdAlertCompoundedComponent = RdAlertComponent & { - // Timer: RdAlertErrorBoundaryComponent; + ErrorBoundary: typeof RdAlertErrorBoundaryComponent; }; //#endregion From 1d4956583085c0326abfb4f9b6e643310d0cab19 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Wed, 1 Oct 2025 15:12:24 +0700 Subject: [PATCH 3/6] feat: add Segmented component --- src/molecules/Segmented/Segmented.tsx | 15 ++++++++++++++ src/molecules/Segmented/index.tsx | 2 ++ src/molecules/Segmented/styles.tsx | 7 +++++++ src/molecules/Segmented/types.ts | 30 +++++++++++++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 src/molecules/Segmented/Segmented.tsx create mode 100644 src/molecules/Segmented/index.tsx create mode 100644 src/molecules/Segmented/styles.tsx create mode 100644 src/molecules/Segmented/types.ts diff --git a/src/molecules/Segmented/Segmented.tsx b/src/molecules/Segmented/Segmented.tsx new file mode 100644 index 0000000..8b8848c --- /dev/null +++ b/src/molecules/Segmented/Segmented.tsx @@ -0,0 +1,15 @@ +import { useMemo } from 'react'; +import { SegmentedStyledFunc } from './styles'; +import { RdSegmentedComponent, RdSegmentedProps } from './types'; + +export const InternalSegmented = ( + props: RdSegmentedProps +) => { + const SegmentedStyled = useMemo(() => { + return SegmentedStyledFunc(); + }, [SegmentedStyledFunc]); + + return ; +}; + +export const Segmented = InternalSegmented as RdSegmentedComponent; diff --git a/src/molecules/Segmented/index.tsx b/src/molecules/Segmented/index.tsx new file mode 100644 index 0000000..c59bd98 --- /dev/null +++ b/src/molecules/Segmented/index.tsx @@ -0,0 +1,2 @@ +export { Segmented } from './Segmented'; +export * from './types'; \ No newline at end of file diff --git a/src/molecules/Segmented/styles.tsx b/src/molecules/Segmented/styles.tsx new file mode 100644 index 0000000..b0a7365 --- /dev/null +++ b/src/molecules/Segmented/styles.tsx @@ -0,0 +1,7 @@ +import styled from '@emotion/styled'; +import { Segmented } from 'antd'; + +export const SegmentedStyles = styled(Segmented)``; + +export const SegmentedStyledFunc = () => + styled(Segmented)``; diff --git a/src/molecules/Segmented/types.ts b/src/molecules/Segmented/types.ts new file mode 100644 index 0000000..b5ef299 --- /dev/null +++ b/src/molecules/Segmented/types.ts @@ -0,0 +1,30 @@ +import { GetProps, Segmented } from 'antd'; +import { ComponentToken as SegmentedComponentTokenAntd } from 'antd/es/affix/style'; +import { InternalSegmented } from './Segmented'; + +//#region Define Ant Design types +type SegmentedPropsAntd = GetProps>; +//#endregion + +//#region Define extended component tokens +type SegmentedComponentTokenExtend = {}; +//#endregion + +//#region Define extended types +type SegmentedPropsExtend = {}; + +type SegmentedRefExtend = {}; +//#endregion + +//#region Export types +export type RdSegmentedProps = SegmentedPropsAntd & SegmentedPropsExtend; + +export type RdSegmentedComponentToken = SegmentedComponentTokenAntd & SegmentedComponentTokenExtend; +//#endregion + +//#region Define component types +export type RdSegmentedComponent = (( + props: RdSegmentedProps & React.RefAttributes +) => ReturnType) & + Pick; +//#endregion From 2e7bf368f4c3a7259a1b3dc1f0e85a6bc9f1a065 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Wed, 1 Oct 2025 15:16:00 +0700 Subject: [PATCH 4/6] refactor: add options field for DefaultOptionTypeExtend --- src/molecules/Select/types.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/molecules/Select/types.ts b/src/molecules/Select/types.ts index d5d7240..17182d9 100644 --- a/src/molecules/Select/types.ts +++ b/src/molecules/Select/types.ts @@ -61,7 +61,18 @@ type SelectPropsExtend = { type SelectOptionPropsExtend = {}; type SelectOptionGroupPropsExtend = {}; type BaseOptionTypeExtend = {}; -type DefaultOptionTypeExtend = {}; +type DefaultOptionTypeExtend = { + /** + * Extends Ant Design's DefaultOptionType. + * + * Reason: + * - Ant Design's