Skip to content

Commit 414a6b5

Browse files
authored
Create React fromDialogRef, fromDrawerRef, and fromTableRef helpers (#2818)
# Pull Request ## 🤨 Rationale Resolves #2784 by adding helpers and docs for casting refs for React components that are generic ## 👩‍💻 Implementation - Added `fromDialogRef`, `fromDrawerRef`, and `fromTableRef` helpers and docs - Updated the example react app to link to the source of the react libraries so you don't need to rebuild them to iteratively develop. ## 🧪 Testing Verify builds still type check ## ✅ Checklist - [x] I have updated the project documentation to reflect my changes or determined no changes are needed. --------- Co-authored-by: rajsite <1588923+rajsite@users.noreply.github.com>
1 parent 0660a54 commit 414a6b5

13 files changed

Lines changed: 169 additions & 77 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "Create fromDialogRef, fromDrawerRef, and fromTableRef helpers",
4+
"packageName": "@ni/nimble-react",
5+
"email": "1588923+rajsite@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import { Dialog, UserDismissed as DialogUserDismissed } from '@ni/nimble-components/dist/esm/dialog';
2-
import type { LegacyRef } from 'react';
2+
import type { RefAttributes, RefObject } from 'react';
33
import { wrap } from '../utilities/react-wrapper';
44

55
export { type Dialog, DialogUserDismissed };
66
export const NimbleDialog = wrap(Dialog);
77

8-
export type DialogRef = LegacyRef<Dialog>;
8+
/**
9+
* Helper to assign Dialog refs with generics to ref bindings
10+
* See: https://github.com/ni/nimble/issues/2784
11+
* @param dialogRef A ref to a dialog created with `useRef`
12+
* @returns A ref type compatible with normal `ref` bindings
13+
*/
14+
export const fromDialogRef = <T>(dialogRef: RefObject<Dialog<T> | null>): RefAttributes<Dialog>['ref'] => dialogRef as RefAttributes<Dialog>['ref'];
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import { Drawer, UserDismissed as DrawerUserDismissed } from '@ni/nimble-components/dist/esm/drawer';
22
import { DrawerLocation } from '@ni/nimble-components/dist/esm/drawer/types';
3-
import type { LegacyRef } from 'react';
3+
import type { RefAttributes, RefObject } from 'react';
44
import { wrap } from '../utilities/react-wrapper';
55

66
export { type Drawer, DrawerUserDismissed, DrawerLocation };
77
export const NimbleDrawer = wrap(Drawer);
88

9-
export type DrawerRef = LegacyRef<Drawer>;
9+
/**
10+
* Helper to assign Drawer refs with generics to ref bindings
11+
* See: https://github.com/ni/nimble/issues/2784
12+
* @param drawerRef A ref to a drawer created with `useRef`
13+
* @returns A ref type compatible with normal `ref` bindings
14+
*/
15+
export const fromDrawerRef = <T>(drawerRef: RefObject<Drawer<T> | null>): RefAttributes<Drawer>['ref'] => drawerRef as RefAttributes<Drawer>['ref'];

packages/react-workspace/nimble-react/src/table/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
TableRecord,
88
TableSetRecordHierarchyOptions
99
} from '@ni/nimble-components/dist/esm/table/types';
10-
import type { LegacyRef } from 'react';
10+
import type { RefAttributes, RefObject } from 'react';
1111
import { wrap, type EventName } from '../utilities/react-wrapper';
1212

1313
export { type Table, type TableRecord, type TableSetRecordHierarchyOptions };
@@ -36,4 +36,10 @@ export interface TableRowExpandToggleEvent extends CustomEvent<TableRowExpansion
3636
target: Table;
3737
}
3838

39-
export type TableRef = LegacyRef<Table>;
39+
/**
40+
* Helper to assign Table refs with generics to ref bindings
41+
* See: https://github.com/ni/nimble/issues/2784
42+
* @param tableRef A ref to a table created with `useRef`
43+
* @returns A ref type compatible with normal `ref` bindings
44+
*/
45+
export const fromTableRef = <T extends TableRecord>(tableRef: RefObject<Table<T> | null>): RefAttributes<Table>['ref'] => tableRef as RefAttributes<Table>['ref'];

packages/react-workspace/react-client-app/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"scripts": {
77
"start": "vite",
88
"build": "tsc -b && vite build",
9-
"test": "tsc -p ./tsconfig.legacy.json",
109
"lint": "eslint .",
1110
"format": "eslint . --fix",
1211
"pack": "npm pack"

packages/react-workspace/react-client-app/src/components/App.tsx

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import { NimbleCheckbox } from '@ni/nimble-react/checkbox';
1616
import { NimbleRadioGroup } from '@ni/nimble-react/radio-group';
1717
import { NimbleRadio } from '@ni/nimble-react/radio';
1818
import { NimbleTextField } from '@ni/nimble-react/text-field';
19-
import { NimbleDialog, type Dialog, type DialogRef, DialogUserDismissed } from '@ni/nimble-react/dialog';
20-
import { NimbleDrawer, type Drawer, type DrawerRef, DrawerUserDismissed, DrawerLocation } from '@ni/nimble-react/drawer';
19+
import { NimbleDialog, type Dialog, DialogUserDismissed, fromDialogRef } from '@ni/nimble-react/dialog';
20+
import { NimbleDrawer, type Drawer, DrawerUserDismissed, DrawerLocation, fromDrawerRef } from '@ni/nimble-react/drawer';
2121
import { NimbleMenu } from '@ni/nimble-react/menu';
2222
import { NimbleMenuItem, type MenuItemChangeEvent } from '@ni/nimble-react/menu-item';
2323
import { NimbleAnchorMenuItem } from '@ni/nimble-react/anchor-menu-item';
@@ -27,7 +27,7 @@ import { NimbleIconCheck } from '@ni/nimble-react/icons/check';
2727
import { NimbleIconXmarkCheck } from '@ni/nimble-react/icons/xmark-check';
2828
import { NimbleSpinner } from '@ni/nimble-react/spinner';
2929
import { NimbleSwitch } from '@ni/nimble-react/switch';
30-
import { NimbleTable, type Table, type TableRef, type TableRowExpandToggleEvent, type TableRecord, type TableSetRecordHierarchyOptions } from '@ni/nimble-react/table';
30+
import { NimbleTable, type Table, type TableRowExpandToggleEvent, type TableRecord, type TableSetRecordHierarchyOptions, fromTableRef } from '@ni/nimble-react/table';
3131
import { NimbleTableColumnText } from '@ni/nimble-react/table-column/text';
3232
import { NimbleTableColumnAnchor } from '@ni/nimble-react/table-column/anchor';
3333
import { NimbleTableColumnDateText } from '@ni/nimble-react/table-column/date-text';
@@ -668,9 +668,7 @@ export function App(): React.JSX.Element {
668668
<div className="sub-container">
669669
<div className="container-label">Dialog</div>
670670
<NimbleDialog
671-
// Note: Generic types such as Dialog require using the workaround ref type
672-
// See: https://github.com/ni/nimble/issues/2784
673-
ref={dialogRef as unknown as DialogRef}
671+
ref={fromDialogRef(dialogRef)}
674672
>
675673
<span slot="title">This is a dialog</span>
676674
<div>It opened when you pushed the button</div>
@@ -691,9 +689,7 @@ export function App(): React.JSX.Element {
691689
<div className="sub-container">
692690
<div className="container-label">Drawer</div>
693691
<NimbleDrawer
694-
// Note: Generic types such as Drawer require using the workaround ref type
695-
// See: https://github.com/ni/nimble/issues/2784
696-
ref={drawerRef as unknown as DrawerRef}
692+
ref={fromDrawerRef(drawerRef)}
697693
location={drawerLocation}
698694
>
699695
<header>This is a drawer</header>
@@ -931,9 +927,7 @@ export function App(): React.JSX.Element {
931927
<div className="sub-container">
932928
<div className="container-label">Table</div>
933929
<NimbleTable
934-
// Note: Generic types such as Table require using the workaround ref type
935-
// See: https://github.com/ni/nimble/issues/2784
936-
ref={tableRef as unknown as TableRef}
930+
ref={fromTableRef(tableRef)}
937931
idFieldName="id"
938932
parentIdFieldName="parentId"
939933
selectionMode="multiple"
@@ -1028,9 +1022,7 @@ export function App(): React.JSX.Element {
10281022
<div className="sub-container">
10291023
<div className="container-label">Table with delayed hierarchy</div>
10301024
<NimbleTable
1031-
// Note: Generic types such as Table require using the workaround ref type
1032-
// See: https://github.com/ni/nimble/issues/2784
1033-
ref={delayedHierarchyTableRef as unknown as TableRef}
1025+
ref={fromTableRef(delayedHierarchyTableRef)}
10341026
idFieldName="id" parentIdFieldName="parentId" selectionMode="multiple"
10351027
onRowExpandToggle={onRowExpandToggle}
10361028
>

packages/react-workspace/react-client-app/tsconfig.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@
1212
"verbatimModuleSyntax": true,
1313
"jsx": "react-jsx",
1414
"incremental": true,
15+
16+
// Used for improved nimble dev workflow, not needed in normal apps
17+
// Keep in sync with vite.config.mjs
18+
"paths": {
19+
"@ni/nimble-react/*": [
20+
"../nimble-react/src/*"
21+
],
22+
"@ni/spright-react/*": [
23+
"../spright-react/src/*"
24+
],
25+
"@ni/ok-react/*": ["../ok-react/src/*"]
26+
}
1527
},
16-
"include": ["src"]
28+
"include": ["src"],
1729
}

packages/react-workspace/react-client-app/tsconfig.legacy.json

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,45 @@
1+
import * as path from 'node:path';
2+
import { createRequire } from 'node:module';
13
import { defineConfig } from 'vite';
24
import react from '@vitejs/plugin-react-swc';
35

6+
const require = createRequire(import.meta.url);
7+
48
export default defineConfig({
59
plugins: [react()],
610
base: './',
711
build: {
812
chunkSizeWarningLimit: 5 * 1024 * 1024
13+
},
14+
15+
// Used for improved nimble dev workflow, not needed in normal apps
16+
// Keep in sync with typescript.json
17+
resolve: {
18+
alias: [
19+
{
20+
find: /^@ni\/nimble-react\/styles\/(.*)/,
21+
replacement: `${getAbsolutePath('@ni/nimble-react')}/styles/$1.scss`
22+
},
23+
{
24+
find: /^@ni\/nimble-react\/icons\/(.*)/,
25+
replacement: `${getAbsolutePath('@ni/nimble-react')}/src/icons/$1.ts`
26+
},
27+
{
28+
find: /^@ni\/nimble-react\/(.*)/,
29+
replacement: `${getAbsolutePath('@ni/nimble-react')}/src/$1/index.ts`
30+
},
31+
{
32+
find: /^@ni\/spright-react\/(.*)/,
33+
replacement: `${getAbsolutePath('@ni/spright-react')}/src/$1/index.ts`
34+
},
35+
{
36+
find: /^@ni\/ok-react\/(.*)/,
37+
replacement: `${getAbsolutePath('@ni/ok-react')}/src/$1/index.ts`
38+
},
39+
]
940
}
1041
});
42+
43+
function getAbsolutePath(value) {
44+
return path.dirname(require.resolve(path.join(value, 'package.json')));
45+
}

packages/storybook/.storybook/main.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { dirname, join } from 'node:path';
1+
import * as path from 'node:path';
22
import { createRequire } from 'node:module';
33
import remarkGfm from 'remark-gfm';
44

@@ -45,6 +45,10 @@ export async function viteFinal(config) {
4545
find: '@ni/ok-components/dist/esm',
4646
replacement: '@ni/ok-components/src'
4747
},
48+
{
49+
find: /^@ni\/nimble-react\/styles\/(.*)/,
50+
replacement: `${getAbsolutePath('@ni/nimble-react')}/styles/$1.scss`
51+
},
4852
{
4953
find: /^@ni\/nimble-react\/icons\/(.*)/,
5054
replacement: `${getAbsolutePath('@ni/nimble-react')}/src/icons/$1.ts`
@@ -72,5 +76,5 @@ export const framework = {
7276
};
7377

7478
function getAbsolutePath(value) {
75-
return dirname(require.resolve(join(value, 'package.json')));
79+
return path.dirname(require.resolve(path.join(value, 'package.json')));
7680
}

0 commit comments

Comments
 (0)