From 2302519ea510c16504ecb4f0e401484ee39751f4 Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:15:44 +0100 Subject: [PATCH 01/18] docs: add editable table text --- stories/components/editable.stories.tsx | 165 ++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 stories/components/editable.stories.tsx diff --git a/stories/components/editable.stories.tsx b/stories/components/editable.stories.tsx new file mode 100644 index 00000000..b01848a6 --- /dev/null +++ b/stories/components/editable.stories.tsx @@ -0,0 +1,165 @@ +import { Icon } from '@blueprintjs/core'; +import styled from '@emotion/styled'; +import type { InputHTMLAttributes } from 'react'; +import { useCallback, useMemo, useState } from 'react'; + +import { createTableColumnHelper, Table } from '../../src/components/index.js'; + +export default { + title: 'Components / EditableTableText', +}; + +const StyledIcon = styled(Icon)` + display: none; + margin-right: 5px; +`; + +const StyledInput = styled.input` + width: 100%; + + :focus, + :hover { + box-shadow: 0 0 1px 1px #595959; + } +`; + +const InputContainer = styled.div` + position: relative; + display: flex; + align-items: center; + + :focus, + :hover > span { + position: absolute; + right: 0; + display: block; + color: #595959; + } +`; + +function Input(props: InputHTMLAttributes) { + return ( + + + + + ); +} + +interface TableData { + id: number; + label: string; + field: string; + format: string; + visible: boolean; +} + +const helper = createTableColumnHelper(); + +const data: TableData[] = [ + { label: 'Name', field: 'info.name', format: '', visible: true }, + { + label: 'Number of scans', + field: 'info.numberOfScans', + format: '0', + visible: true, + }, + { + label: 'Pulse sequence', + field: 'info.pulseSequence', + format: '', + visible: true, + }, + { + label: 'Frequency', + field: 'meta..DIGITSERRE', + format: '0', + visible: false, + }, +].map((item, index) => ({ id: index, ...item })); + +export function InsideTable() { + const [state, setState] = useState(data); + + const changeValue = useCallback( + (rowIndex: number, key: string, value: string) => { + setState((prev) => { + const element = prev.at(rowIndex); + + if (!element) { + return prev; + } + + return prev.map((item, index) => { + if (index === rowIndex) { + return { ...element, [key]: value }; + } + + return item; + }); + }); + }, + [], + ); + + const columns = useMemo(() => { + return [ + helper.accessor('id', { header: '#' }), + helper.accessor('label', { + header: 'Label', + cell: ({ getValue, row: { index } }) => ( + + changeValue(index, 'label', event.currentTarget.value) + } + /> + ), + }), + helper.accessor('field', { + header: 'Field', + cell: ({ getValue, row: { index } }) => ( + + changeValue(index, 'field', event.currentTarget.value) + } + /> + ), + }), + helper.accessor('format', { + header: 'Format', + cell: ({ getValue, row: { index } }) => ( + + changeValue(index, 'format', event.currentTarget.value) + } + /> + ), + }), + helper.accessor('visible', { header: 'Visible' }), + ]; + }, [changeValue]); + + return ( +
+
+ + data={state} + columns={columns} + estimatedRowHeight={() => 50} + /> +
+ +
{JSON.stringify(state, null, 2)}
+
+ ); +} From 1ec8f42ef68735734bd6e612d1b2e4cf7caaf605 Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Thu, 26 Dec 2024 15:37:43 +0100 Subject: [PATCH 02/18] feat: create switchable input can switch between value or input. based on `input` or `value` props. the switch is auto when you click on the value. --- src/components/input/SwitchableInput.tsx | 50 +++++++++++++++ stories/components/editable.stories.tsx | 80 +++++++++--------------- 2 files changed, 78 insertions(+), 52 deletions(-) create mode 100644 src/components/input/SwitchableInput.tsx diff --git a/src/components/input/SwitchableInput.tsx b/src/components/input/SwitchableInput.tsx new file mode 100644 index 00000000..bf6898c4 --- /dev/null +++ b/src/components/input/SwitchableInput.tsx @@ -0,0 +1,50 @@ +import styled from '@emotion/styled'; +import type { ReactElement } from 'react'; +import { cloneElement, useCallback, useState } from 'react'; + +export const SwitchableInput = styled.input` + width: 100%; + + :focus, + :hover { + box-shadow: 0 0 1px 1px #595959; + } +`; + +interface SwitchableInputRendererProps { + input: ReactElement; + value: string; +} + +export function SwitchableInputRenderer(props: SwitchableInputRendererProps) { + const { input, value } = props; + const [isInputRendered, setIsInputRendered] = useState(false); + + const toggle = useCallback( + (event: any) => { + if (isInputRendered) { + event.stopPropagation(); + } + + return setIsInputRendered((old) => !old); + }, + [isInputRendered], + ); + + if (isInputRendered) { + return cloneElement(input, { + ...input.props, + tabIndex: 0, + // @ts-expect-error autoFocus is only used for React + autoFocus: true, + onBlur: (event: any) => { + // @ts-expect-error onBlur is only used for React + input.props.onBlur?.(event); + setIsInputRendered(false); + }, + defaultValue: value, + }); + } + + return
{value}
; +} diff --git a/stories/components/editable.stories.tsx b/stories/components/editable.stories.tsx index b01848a6..93ecdb0d 100644 --- a/stories/components/editable.stories.tsx +++ b/stories/components/editable.stories.tsx @@ -1,51 +1,15 @@ -import { Icon } from '@blueprintjs/core'; -import styled from '@emotion/styled'; -import type { InputHTMLAttributes } from 'react'; import { useCallback, useMemo, useState } from 'react'; import { createTableColumnHelper, Table } from '../../src/components/index.js'; +import { + SwitchableInput, + SwitchableInputRenderer, +} from '../../src/components/input/SwitchableInput.js'; export default { title: 'Components / EditableTableText', }; -const StyledIcon = styled(Icon)` - display: none; - margin-right: 5px; -`; - -const StyledInput = styled.input` - width: 100%; - - :focus, - :hover { - box-shadow: 0 0 1px 1px #595959; - } -`; - -const InputContainer = styled.div` - position: relative; - display: flex; - align-items: center; - - :focus, - :hover > span { - position: absolute; - right: 0; - display: block; - color: #595959; - } -`; - -function Input(props: InputHTMLAttributes) { - return ( - - - - - ); -} - interface TableData { id: number; label: string; @@ -108,10 +72,14 @@ export function InsideTable() { helper.accessor('label', { header: 'Label', cell: ({ getValue, row: { index } }) => ( - - changeValue(index, 'label', event.currentTarget.value) + + changeValue(index, 'label', event.currentTarget.value) + } + /> } /> ), @@ -119,10 +87,14 @@ export function InsideTable() { helper.accessor('field', { header: 'Field', cell: ({ getValue, row: { index } }) => ( - - changeValue(index, 'field', event.currentTarget.value) + + changeValue(index, 'field', event.currentTarget.value) + } + /> } /> ), @@ -130,10 +102,14 @@ export function InsideTable() { helper.accessor('format', { header: 'Format', cell: ({ getValue, row: { index } }) => ( - - changeValue(index, 'format', event.currentTarget.value) + + changeValue(index, 'format', event.currentTarget.value) + } + /> } /> ), From 75406228da30dc343376e0c7efe9ff0f805ad33e Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Fri, 27 Dec 2024 15:53:41 +0100 Subject: [PATCH 03/18] wip: bug with input size that take the entire place (and more) when clicking --- src/components/input/SwitchableInput.tsx | 15 ++++++++++++--- stories/components/editable.stories.tsx | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/components/input/SwitchableInput.tsx b/src/components/input/SwitchableInput.tsx index bf6898c4..f2a8f54e 100644 --- a/src/components/input/SwitchableInput.tsx +++ b/src/components/input/SwitchableInput.tsx @@ -3,8 +3,17 @@ import type { ReactElement } from 'react'; import { cloneElement, useCallback, useState } from 'react'; export const SwitchableInput = styled.input` - width: 100%; + max-width: 100%; + box-shadow: 0 0 1px 1px #595959; + + :focus, + :hover { + box-shadow: 0 0 1px 1px #595959; + } +`; + +const Container = styled.div` :focus, :hover { box-shadow: 0 0 1px 1px #595959; @@ -18,7 +27,7 @@ interface SwitchableInputRendererProps { export function SwitchableInputRenderer(props: SwitchableInputRendererProps) { const { input, value } = props; - const [isInputRendered, setIsInputRendered] = useState(false); + const [isInputRendered, setIsInputRendered] = useState(true); const toggle = useCallback( (event: any) => { @@ -46,5 +55,5 @@ export function SwitchableInputRenderer(props: SwitchableInputRendererProps) { }); } - return
{value}
; + return {value}; } diff --git a/stories/components/editable.stories.tsx b/stories/components/editable.stories.tsx index 93ecdb0d..f868a56e 100644 --- a/stories/components/editable.stories.tsx +++ b/stories/components/editable.stories.tsx @@ -1,3 +1,4 @@ +import { Button } from '@blueprintjs/core'; import { useCallback, useMemo, useState } from 'react'; import { createTableColumnHelper, Table } from '../../src/components/index.js'; @@ -45,6 +46,10 @@ const data: TableData[] = [ export function InsideTable() { const [state, setState] = useState(data); + const deleteIndex = useCallback((index: number) => { + return setState((prev) => prev.filter((_, i) => i !== index)); + }, []); + const changeValue = useCallback( (rowIndex: number, key: string, value: string) => { setState((prev) => { @@ -115,8 +120,20 @@ export function InsideTable() { ), }), helper.accessor('visible', { header: 'Visible' }), + helper.display({ + header: ' ', + cell: ({ row: { index } }) => { + return ( + + )} /> @@ -156,3 +168,11 @@ export function InsideTable() { ); } + +export function Input() { + return ( +
+ } /> +
+ ); +} From a7f40b6c29349816374d2384f366ef3548c3bec6 Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Fri, 10 Jan 2025 11:01:15 +0100 Subject: [PATCH 05/18] feat: change inline editable renderer --- src/components/input/SwitchableInput.tsx | 7 +- .../input/inline_editable_renderer.tsx | 73 ++++++++++++++ src/components/table/table_root.tsx | 15 --- stories/components/editable.stories.tsx | 95 ++++++++++++------- 4 files changed, 135 insertions(+), 55 deletions(-) create mode 100644 src/components/input/inline_editable_renderer.tsx diff --git a/src/components/input/SwitchableInput.tsx b/src/components/input/SwitchableInput.tsx index 9054086a..96398456 100644 --- a/src/components/input/SwitchableInput.tsx +++ b/src/components/input/SwitchableInput.tsx @@ -57,7 +57,6 @@ export function SwitchableInputRenderer(props: SwitchableInputRendererProps) { ...input.props.style, visibility: isInputRendered ? 'visible' : 'hidden', }, - tabIndex: 0, onBlur: (event: any) => { // @ts-expect-error onBlur is only used for React input.props.onBlur?.(event); @@ -71,9 +70,9 @@ export function SwitchableInputRenderer(props: SwitchableInputRendererProps) { {Input} { - toggle(event); - }} + tabIndex={isInputRendered ? -1 : 0} + onFocus={() => setIsInputRendered(true)} + onClick={toggle} > {value} diff --git a/src/components/input/inline_editable_renderer.tsx b/src/components/input/inline_editable_renderer.tsx new file mode 100644 index 00000000..81cfa28c --- /dev/null +++ b/src/components/input/inline_editable_renderer.tsx @@ -0,0 +1,73 @@ +import styled from '@emotion/styled'; +import type { ReactNode } from 'react'; +import { useCallback, useMemo, useState } from 'react'; + +export interface InlineRendererEditableProps { + ref: (node: T | null) => void; + toggle: () => void; +} + +export interface InlineEditableRendererProps { + renderEditable: (props: InlineRendererEditableProps) => ReactNode; + children: ReactNode; +} + +export const SwitchableInput = styled.input` + // 22px is the padding of the container + max-width: calc(100% - 22px); + box-shadow: 0 0 1px 1px #595959; + position: absolute; + + :focus, + :hover { + box-shadow: 0 0 1px 1px #595959; + } +`; + +const Container = styled.div` + min-width: 100%; + height: 21px; + width: 100%; + :focus, + :hover { + box-shadow: 0 0 1px 1px #595959; + } +`; + +export function InlineEditableRenderer( + props: InlineEditableRendererProps, +) { + const { children, renderEditable } = props; + const [isInputRendered, setIsInputRendered] = useState(false); + + const toggle = useCallback(() => { + return setIsInputRendered((old) => !old); + }, []); + + const renderEditableProps = useMemo>(() => { + return { + isRendered: isInputRendered, + ref: (node) => { + if (!node) return; + node.focus(); + }, + toggle, + }; + }, [isInputRendered, toggle]); + + return ( + <> +
+ {renderEditable(renderEditableProps)} +
+ + setIsInputRendered(true)} + onClick={toggle} + > + {children} + + + ); +} diff --git a/src/components/table/table_root.tsx b/src/components/table/table_root.tsx index c0613c4e..4df5b4a4 100644 --- a/src/components/table/table_root.tsx +++ b/src/components/table/table_root.tsx @@ -25,8 +25,6 @@ import { useTableScroll } from './use_table_scroll.js'; const CustomHTMLTable = styled(HTMLTable, { shouldForwardProp: (prop) => prop !== 'striped' && prop !== 'stickyHeader', })<{ stickyHeader: boolean }>` - // width: 100%; - /* When using a sticky header, ensure that the borders are located below the last header instead of above the first row. */ ${(props) => { if (!props.stickyHeader) return ''; @@ -116,10 +114,6 @@ interface TableBaseProps { * Override the columns' header cell rendering. */ renderHeaderCell?: HeaderCellRenderer; - /** - * Add a footer to the table. - */ - renderFooterRow?: () => ReactNode; /** * A ref which will be set with a callback to scroll to a row in the @@ -168,7 +162,6 @@ export function Table(props: TableProps) { className, renderRowTr, renderHeaderCell, - renderFooterRow, virtualizeRows, getRowId, @@ -229,14 +222,6 @@ export function Table(props: TableProps) { virtualizer={tanstackVirtualizer} virtualizeRows={virtualizeRows} /> - - {renderFooterRow && ( - - - {renderFooterRow()} - - - )} ); diff --git a/stories/components/editable.stories.tsx b/stories/components/editable.stories.tsx index 9ffc65c8..30e9c6fc 100644 --- a/stories/components/editable.stories.tsx +++ b/stories/components/editable.stories.tsx @@ -2,10 +2,8 @@ import { Button } from '@blueprintjs/core'; import { useCallback, useMemo, useState } from 'react'; import { createTableColumnHelper, Table } from '../../src/components/index.js'; -import { - SwitchableInput, - SwitchableInputRenderer, -} from '../../src/components/input/SwitchableInput.js'; +import { SwitchableInput } from '../../src/components/input/SwitchableInput.js'; +import { InlineEditableRenderer } from '../../src/components/input/inline_editable_renderer.js'; export default { title: 'Components / EditableTableText', @@ -84,46 +82,58 @@ export function InsideTable() { helper.accessor('label', { header: 'Label', cell: ({ getValue, row: { index } }) => ( - ( - changeValue(index, 'label', event.currentTarget.value) - } + ref={ref} + defaultValue={getValue()} + onBlur={(event) => { + toggle(); + changeValue(index, 'label', event.currentTarget.value); + }} /> - } - /> + )} + > + {getValue()} + ), }), helper.accessor('field', { header: 'Field', cell: ({ getValue, row: { index } }) => ( - ( - changeValue(index, 'field', event.currentTarget.value) - } + ref={ref} + defaultValue={getValue()} + onBlur={(event) => { + toggle(); + changeValue(index, 'field', event.currentTarget.value); + }} /> - } - /> + )} + > + {getValue()} + ), }), helper.accessor('format', { header: 'Format', cell: ({ getValue, row: { index } }) => ( - ( - changeValue(index, 'format', event.currentTarget.value) - } + ref={ref} + defaultValue={getValue()} + onBlur={(event) => { + toggle(); + changeValue(index, 'format', event.currentTarget.value); + }} /> - } - /> + )} + > + {getValue()} + ), }), helper.accessor('visible', { header: 'Visible' }), @@ -134,6 +144,7 @@ export function InsideTable() { - )} /> @@ -169,10 +175,27 @@ export function InsideTable() { ); } -export function Input() { +export function InlineEditable() { + const [state, setState] = useState('value'); + return ( -
- } /> +
+ State: {state} + ( + { + toggle(); + + setState(event.currentTarget.value); + }} + /> + )} + > + {state} +
); } From 7ab1fe8772ea14ba1d34a4409797a33c29aaf0e7 Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Fri, 10 Jan 2025 13:44:38 +0100 Subject: [PATCH 06/18] refactor: make a poc --- src/components/input/inline_editable_renderer.tsx | 3 +-- stories/components/editable.stories.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/input/inline_editable_renderer.tsx b/src/components/input/inline_editable_renderer.tsx index 81cfa28c..d9f00375 100644 --- a/src/components/input/inline_editable_renderer.tsx +++ b/src/components/input/inline_editable_renderer.tsx @@ -13,8 +13,7 @@ export interface InlineEditableRendererProps { } export const SwitchableInput = styled.input` - // 22px is the padding of the container - max-width: calc(100% - 22px); + width: 100%; box-shadow: 0 0 1px 1px #595959; position: absolute; diff --git a/stories/components/editable.stories.tsx b/stories/components/editable.stories.tsx index 30e9c6fc..f0f6e9fc 100644 --- a/stories/components/editable.stories.tsx +++ b/stories/components/editable.stories.tsx @@ -2,8 +2,10 @@ import { Button } from '@blueprintjs/core'; import { useCallback, useMemo, useState } from 'react'; import { createTableColumnHelper, Table } from '../../src/components/index.js'; -import { SwitchableInput } from '../../src/components/input/SwitchableInput.js'; -import { InlineEditableRenderer } from '../../src/components/input/inline_editable_renderer.js'; +import { + InlineEditableRenderer, + SwitchableInput, +} from '../../src/components/input/inline_editable_renderer.js'; export default { title: 'Components / EditableTableText', From 45a70b66cfc1bc586071a9a0be61596010152c4d Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:57:37 +0100 Subject: [PATCH 07/18] refactor: remove hardcoded values --- src/components/input/inline_editable_renderer.tsx | 7 ++++--- stories/components/editable.stories.tsx | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/input/inline_editable_renderer.tsx b/src/components/input/inline_editable_renderer.tsx index d9f00375..e74df88c 100644 --- a/src/components/input/inline_editable_renderer.tsx +++ b/src/components/input/inline_editable_renderer.tsx @@ -14,8 +14,10 @@ export interface InlineEditableRendererProps { export const SwitchableInput = styled.input` width: 100%; + height: 100%; box-shadow: 0 0 1px 1px #595959; position: absolute; + inset: 0; :focus, :hover { @@ -25,7 +27,6 @@ export const SwitchableInput = styled.input` const Container = styled.div` min-width: 100%; - height: 21px; width: 100%; :focus, :hover { @@ -55,7 +56,7 @@ export function InlineEditableRenderer( }, [isInputRendered, toggle]); return ( - <> +
{renderEditable(renderEditableProps)}
@@ -67,6 +68,6 @@ export function InlineEditableRenderer( > {children} - +
); } diff --git a/stories/components/editable.stories.tsx b/stories/components/editable.stories.tsx index f0f6e9fc..e763ff7f 100644 --- a/stories/components/editable.stories.tsx +++ b/stories/components/editable.stories.tsx @@ -178,10 +178,10 @@ export function InsideTable() { } export function InlineEditable() { - const [state, setState] = useState('value'); + const [state, setState] = useState('Hello, World!'); return ( -
+
State: {state} ( From 076a31ba13a2bd5ba55d046729ce3c1042e4e5c5 Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:59:04 +0100 Subject: [PATCH 08/18] refactor: change folder names --- .../inline_editable_renderer/index.ts | 1 + .../inline_editable_renderer.tsx | 0 src/components/input/SwitchableInput.tsx | 81 ------------------- stories/components/editable.stories.tsx | 2 +- 4 files changed, 2 insertions(+), 82 deletions(-) create mode 100644 src/components/inline_editable_renderer/index.ts rename src/components/{input => inline_editable_renderer}/inline_editable_renderer.tsx (100%) delete mode 100644 src/components/input/SwitchableInput.tsx diff --git a/src/components/inline_editable_renderer/index.ts b/src/components/inline_editable_renderer/index.ts new file mode 100644 index 00000000..d2dd3714 --- /dev/null +++ b/src/components/inline_editable_renderer/index.ts @@ -0,0 +1 @@ +export * from './inline_editable_renderer.js'; diff --git a/src/components/input/inline_editable_renderer.tsx b/src/components/inline_editable_renderer/inline_editable_renderer.tsx similarity index 100% rename from src/components/input/inline_editable_renderer.tsx rename to src/components/inline_editable_renderer/inline_editable_renderer.tsx diff --git a/src/components/input/SwitchableInput.tsx b/src/components/input/SwitchableInput.tsx deleted file mode 100644 index 96398456..00000000 --- a/src/components/input/SwitchableInput.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import styled from '@emotion/styled'; -import type { ReactElement } from 'react'; -import { cloneElement, useCallback, useState } from 'react'; - -export const SwitchableInput = styled.input` - // 22px is the padding of the container - max-width: calc(100% - 22px); - box-shadow: 0 0 1px 1px #595959; - position: absolute; - - :focus, - :hover { - box-shadow: 0 0 1px 1px #595959; - } -`; - -const Container = styled.div` - min-width: 100%; - height: 21px; - - width: 100%; - - :focus, - :hover { - box-shadow: 0 0 1px 1px #595959; - } -`; - -interface SwitchableInputRendererProps { - input: ReactElement; - value: string; -} - -export function SwitchableInputRenderer(props: SwitchableInputRendererProps) { - const { input, value } = props; - const [isInputRendered, setIsInputRendered] = useState(false); - - const toggle = useCallback( - (event: any) => { - if (isInputRendered) { - event.stopPropagation(); - } - - return setIsInputRendered((old) => !old); - }, - [isInputRendered], - ); - - const Input = cloneElement(input, { - ...input.props, - // @ts-expect-error ref is only used for React - ref: (node: HTMLInputElement | null) => { - if (!node) return; - node.focus(); - }, - style: { - ...input.props.style, - visibility: isInputRendered ? 'visible' : 'hidden', - }, - onBlur: (event: any) => { - // @ts-expect-error onBlur is only used for React - input.props.onBlur?.(event); - setIsInputRendered(false); - }, - defaultValue: value, - }); - - return ( - <> - {Input} - - setIsInputRendered(true)} - onClick={toggle} - > - {value} - - - ); -} diff --git a/stories/components/editable.stories.tsx b/stories/components/editable.stories.tsx index e763ff7f..35b2adc1 100644 --- a/stories/components/editable.stories.tsx +++ b/stories/components/editable.stories.tsx @@ -5,7 +5,7 @@ import { createTableColumnHelper, Table } from '../../src/components/index.js'; import { InlineEditableRenderer, SwitchableInput, -} from '../../src/components/input/inline_editable_renderer.js'; +} from '../../src/components/inline_editable_renderer/index.js'; export default { title: 'Components / EditableTableText', From f29353b017f27b07c94dc0514b91ab96f117eb11 Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:45:35 +0100 Subject: [PATCH 09/18] refactor: change component name --- .../inline_editable_renderer.tsx | 2 +- stories/components/editable.stories.tsx | 80 +++++++++++-------- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/src/components/inline_editable_renderer/inline_editable_renderer.tsx b/src/components/inline_editable_renderer/inline_editable_renderer.tsx index e74df88c..da3c0358 100644 --- a/src/components/inline_editable_renderer/inline_editable_renderer.tsx +++ b/src/components/inline_editable_renderer/inline_editable_renderer.tsx @@ -12,7 +12,7 @@ export interface InlineEditableRendererProps { children: ReactNode; } -export const SwitchableInput = styled.input` +export const BorderlessEditableInput = styled.input` width: 100%; height: 100%; box-shadow: 0 0 1px 1px #595959; diff --git a/stories/components/editable.stories.tsx b/stories/components/editable.stories.tsx index 35b2adc1..ed7fdd3d 100644 --- a/stories/components/editable.stories.tsx +++ b/stories/components/editable.stories.tsx @@ -1,10 +1,11 @@ import { Button } from '@blueprintjs/core'; +import type { StoryObj } from '@storybook/react'; import { useCallback, useMemo, useState } from 'react'; import { createTableColumnHelper, Table } from '../../src/components/index.js'; import { + BorderlessEditableInput, InlineEditableRenderer, - SwitchableInput, } from '../../src/components/inline_editable_renderer/index.js'; export default { @@ -71,13 +72,6 @@ export function InsideTable() { [], ); - const addEmptyRow = useCallback(() => { - setState((prev) => [ - ...prev, - { id: prev.length, label: '', field: '', format: '', visible: true }, - ]); - }, []); - const columns = useMemo(() => { return [ helper.accessor('id', { header: '#' }), @@ -86,7 +80,7 @@ export function InsideTable() { cell: ({ getValue, row: { index } }) => ( ( - { @@ -105,7 +99,7 @@ export function InsideTable() { cell: ({ getValue, row: { index } }) => ( ( - { @@ -124,7 +118,7 @@ export function InsideTable() { cell: ({ getValue, row: { index } }) => ( ( - { @@ -177,27 +171,45 @@ export function InsideTable() { ); } -export function InlineEditable() { - const [state, setState] = useState('Hello, World!'); - - return ( -
- State: {state} - ( - { - toggle(); - - setState(event.currentTarget.value); - }} - /> - )} - > - {state} - +export const BorderlessEditableInputExample = { + render: () => , + decorators: (Story) => ( +
+
- ); -} + ), +} satisfies StoryObj; + +export const InlineEditable = { + render: () => { + const [state, setState] = useState('Hello, World!'); + + return ( +
+ State: {state} + ( + { + toggle(); + + setState(event.currentTarget.value); + }} + /> + )} + > + {state} + +
+ ); + }, +} satisfies StoryObj; From 25f31c9ae636bc95030413011a417eca66eb7de2 Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Mon, 13 Jan 2025 13:13:23 +0100 Subject: [PATCH 10/18] fix: run stylelint fix --- .../inline_editable_renderer/inline_editable_renderer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/inline_editable_renderer/inline_editable_renderer.tsx b/src/components/inline_editable_renderer/inline_editable_renderer.tsx index da3c0358..16085d26 100644 --- a/src/components/inline_editable_renderer/inline_editable_renderer.tsx +++ b/src/components/inline_editable_renderer/inline_editable_renderer.tsx @@ -28,6 +28,7 @@ export const BorderlessEditableInput = styled.input` const Container = styled.div` min-width: 100%; width: 100%; + :focus, :hover { box-shadow: 0 0 1px 1px #595959; From 98f1c2bb0debaa67299b387704b4f5a284db396d Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Tue, 14 Jan 2025 13:23:29 +0100 Subject: [PATCH 11/18] refactor: change component name --- .../inline_editable_renderer.tsx | 8 ++--- stories/components/editable.stories.tsx | 32 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/components/inline_editable_renderer/inline_editable_renderer.tsx b/src/components/inline_editable_renderer/inline_editable_renderer.tsx index 16085d26..634eac34 100644 --- a/src/components/inline_editable_renderer/inline_editable_renderer.tsx +++ b/src/components/inline_editable_renderer/inline_editable_renderer.tsx @@ -7,12 +7,12 @@ export interface InlineRendererEditableProps { toggle: () => void; } -export interface InlineEditableRendererProps { +export interface InlineEditableProps { renderEditable: (props: InlineRendererEditableProps) => ReactNode; children: ReactNode; } -export const BorderlessEditableInput = styled.input` +export const InlineEditableInput = styled.input` width: 100%; height: 100%; box-shadow: 0 0 1px 1px #595959; @@ -35,8 +35,8 @@ const Container = styled.div` } `; -export function InlineEditableRenderer( - props: InlineEditableRendererProps, +export function InlineEditable( + props: InlineEditableProps, ) { const { children, renderEditable } = props; const [isInputRendered, setIsInputRendered] = useState(false); diff --git a/stories/components/editable.stories.tsx b/stories/components/editable.stories.tsx index ed7fdd3d..a0c9c671 100644 --- a/stories/components/editable.stories.tsx +++ b/stories/components/editable.stories.tsx @@ -4,12 +4,12 @@ import { useCallback, useMemo, useState } from 'react'; import { createTableColumnHelper, Table } from '../../src/components/index.js'; import { - BorderlessEditableInput, - InlineEditableRenderer, + InlineEditable as InlineEditableComponent, + InlineEditableInput, } from '../../src/components/inline_editable_renderer/index.js'; export default { - title: 'Components / EditableTableText', + title: 'Components / InlineEditableComponent', }; interface TableData { @@ -78,9 +78,9 @@ export function InsideTable() { helper.accessor('label', { header: 'Label', cell: ({ getValue, row: { index } }) => ( - ( - { @@ -91,15 +91,15 @@ export function InsideTable() { )} > {getValue()} - + ), }), helper.accessor('field', { header: 'Field', cell: ({ getValue, row: { index } }) => ( - ( - { @@ -110,15 +110,15 @@ export function InsideTable() { )} > {getValue()} - + ), }), helper.accessor('format', { header: 'Format', cell: ({ getValue, row: { index } }) => ( - ( - { @@ -129,7 +129,7 @@ export function InsideTable() { )} > {getValue()} - + ), }), helper.accessor('visible', { header: 'Visible' }), @@ -172,7 +172,7 @@ export function InsideTable() { } export const BorderlessEditableInputExample = { - render: () => , + render: () => , decorators: (Story) => (
State: {state} - ( - { @@ -208,7 +208,7 @@ export const InlineEditable = { )} > {state} - +
); }, From f61769d74c9039381aa6ae9de8a514c22ea1228f Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Tue, 14 Jan 2025 13:26:01 +0100 Subject: [PATCH 12/18] refactor: add min height to empty value render --- .../inline_editable_renderer/inline_editable_renderer.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/inline_editable_renderer/inline_editable_renderer.tsx b/src/components/inline_editable_renderer/inline_editable_renderer.tsx index 634eac34..8706f849 100644 --- a/src/components/inline_editable_renderer/inline_editable_renderer.tsx +++ b/src/components/inline_editable_renderer/inline_editable_renderer.tsx @@ -15,6 +15,7 @@ export interface InlineEditableProps { export const InlineEditableInput = styled.input` width: 100%; height: 100%; + box-shadow: 0 0 1px 1px #595959; position: absolute; inset: 0; @@ -29,6 +30,9 @@ const Container = styled.div` min-width: 100%; width: 100%; + // min height of the input, if input value is empty + min-height: 21px; + :focus, :hover { box-shadow: 0 0 1px 1px #595959; From 1210df9d921bcafcb380bd6f901f6be456b69988 Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Tue, 14 Jan 2025 13:30:52 +0100 Subject: [PATCH 13/18] refactor: remove docs for stylelint --- .../inline_editable_renderer/inline_editable_renderer.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/inline_editable_renderer/inline_editable_renderer.tsx b/src/components/inline_editable_renderer/inline_editable_renderer.tsx index 8706f849..21b9ef0d 100644 --- a/src/components/inline_editable_renderer/inline_editable_renderer.tsx +++ b/src/components/inline_editable_renderer/inline_editable_renderer.tsx @@ -15,7 +15,6 @@ export interface InlineEditableProps { export const InlineEditableInput = styled.input` width: 100%; height: 100%; - box-shadow: 0 0 1px 1px #595959; position: absolute; inset: 0; @@ -29,8 +28,6 @@ export const InlineEditableInput = styled.input` const Container = styled.div` min-width: 100%; width: 100%; - - // min height of the input, if input value is empty min-height: 21px; :focus, From 6c4d4e765ff7007155002ec8bcdb85e6680effca Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:39:55 +0100 Subject: [PATCH 14/18] refactor: make some changes according to reviewers --- .../inline_editable_renderer.tsx | 15 ++++++++------- stories/components/editable.stories.tsx | 16 ---------------- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/src/components/inline_editable_renderer/inline_editable_renderer.tsx b/src/components/inline_editable_renderer/inline_editable_renderer.tsx index 21b9ef0d..ef21ba47 100644 --- a/src/components/inline_editable_renderer/inline_editable_renderer.tsx +++ b/src/components/inline_editable_renderer/inline_editable_renderer.tsx @@ -1,3 +1,4 @@ +import { Colors } from '@blueprintjs/core'; import styled from '@emotion/styled'; import type { ReactNode } from 'react'; import { useCallback, useMemo, useState } from 'react'; @@ -15,14 +16,9 @@ export interface InlineEditableProps { export const InlineEditableInput = styled.input` width: 100%; height: 100%; - box-shadow: 0 0 1px 1px #595959; + box-shadow: 0 0 1px 1px ${Colors.GRAY1}; position: absolute; inset: 0; - - :focus, - :hover { - box-shadow: 0 0 1px 1px #595959; - } `; const Container = styled.div` @@ -32,10 +28,15 @@ const Container = styled.div` :focus, :hover { - box-shadow: 0 0 1px 1px #595959; + box-shadow: 0 0 1px 1px ${Colors.GRAY1}; } `; +/** + * The `InlineEditable` component allows for inline editing of its content. + * It renders an editable input field when focused or clicked + * and toggles back to the original content when the input loses focus. + */ export function InlineEditable( props: InlineEditableProps, ) { diff --git a/stories/components/editable.stories.tsx b/stories/components/editable.stories.tsx index a0c9c671..8294a955 100644 --- a/stories/components/editable.stories.tsx +++ b/stories/components/editable.stories.tsx @@ -171,22 +171,6 @@ export function InsideTable() { ); } -export const BorderlessEditableInputExample = { - render: () => , - decorators: (Story) => ( -
- -
- ), -} satisfies StoryObj; - export const InlineEditable = { render: () => { const [state, setState] = useState('Hello, World!'); From fd7ef58158f2c1dbdbb5369b11392c9bc61963c1 Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Fri, 17 Jan 2025 15:37:28 +0100 Subject: [PATCH 15/18] refactor: change docs --- .../inline_editable_renderer/inline_editable_renderer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/inline_editable_renderer/inline_editable_renderer.tsx b/src/components/inline_editable_renderer/inline_editable_renderer.tsx index ef21ba47..54ce750b 100644 --- a/src/components/inline_editable_renderer/inline_editable_renderer.tsx +++ b/src/components/inline_editable_renderer/inline_editable_renderer.tsx @@ -34,7 +34,7 @@ const Container = styled.div` /** * The `InlineEditable` component allows for inline editing of its content. - * It renders an editable input field when focused or clicked + * It renders a component with `renderEditable` when focused or clicked * and toggles back to the original content when the input loses focus. */ export function InlineEditable( From 4d3f9c7988ada6b45a590b07851e260e391d7d66 Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:34:54 +0100 Subject: [PATCH 16/18] feat: press enter will save the input --- .../inline_editable_renderer.tsx | 14 +++++++++---- stories/components/editable.stories.tsx | 20 +++++++++++-------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/components/inline_editable_renderer/inline_editable_renderer.tsx b/src/components/inline_editable_renderer/inline_editable_renderer.tsx index 54ce750b..5b0e1698 100644 --- a/src/components/inline_editable_renderer/inline_editable_renderer.tsx +++ b/src/components/inline_editable_renderer/inline_editable_renderer.tsx @@ -1,11 +1,12 @@ import { Colors } from '@blueprintjs/core'; import styled from '@emotion/styled'; -import type { ReactNode } from 'react'; +import type { KeyboardEvent, ReactNode } from 'react'; import { useCallback, useMemo, useState } from 'react'; export interface InlineRendererEditableProps { ref: (node: T | null) => void; - toggle: () => void; + stopRendering: () => void; + onKeyDown: (event: KeyboardEvent) => void; } export interface InlineEditableProps { @@ -50,13 +51,18 @@ export function InlineEditable( const renderEditableProps = useMemo>(() => { return { isRendered: isInputRendered, + onKeyDown: (event) => { + if (event.key === 'Enter') { + setIsInputRendered(false); + } + }, ref: (node) => { if (!node) return; node.focus(); }, - toggle, + stopRendering: () => setIsInputRendered(false), }; - }, [isInputRendered, toggle]); + }, [isInputRendered]); return (
diff --git a/stories/components/editable.stories.tsx b/stories/components/editable.stories.tsx index 8294a955..44e15130 100644 --- a/stories/components/editable.stories.tsx +++ b/stories/components/editable.stories.tsx @@ -79,12 +79,13 @@ export function InsideTable() { header: 'Label', cell: ({ getValue, row: { index } }) => ( ( + renderEditable={({ ref, stopRendering, onKeyDown }) => ( { - toggle(); + stopRendering(); changeValue(index, 'label', event.currentTarget.value); }} /> @@ -98,12 +99,13 @@ export function InsideTable() { header: 'Field', cell: ({ getValue, row: { index } }) => ( ( + renderEditable={({ ref, stopRendering, onKeyDown }) => ( { - toggle(); + stopRendering(); changeValue(index, 'field', event.currentTarget.value); }} /> @@ -117,12 +119,13 @@ export function InsideTable() { header: 'Format', cell: ({ getValue, row: { index } }) => ( ( + renderEditable={({ ref, stopRendering, onKeyDown }) => ( { - toggle(); + stopRendering(); changeValue(index, 'format', event.currentTarget.value); }} /> @@ -179,12 +182,13 @@ export const InlineEditable = {
State: {state} ( + renderEditable={({ ref, stopRendering, onKeyDown }) => ( { - toggle(); + stopRendering(); setState(event.currentTarget.value); }} From 51510ab38545d75d90fc4162c28cd9890e1a059b Mon Sep 17 00:00:00 2001 From: Daniel Kostro Date: Tue, 21 Jan 2025 10:38:04 +0100 Subject: [PATCH 17/18] fix: remove outline from input --- .../inline_editable_renderer/inline_editable_renderer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/inline_editable_renderer/inline_editable_renderer.tsx b/src/components/inline_editable_renderer/inline_editable_renderer.tsx index 5b0e1698..f5dc4830 100644 --- a/src/components/inline_editable_renderer/inline_editable_renderer.tsx +++ b/src/components/inline_editable_renderer/inline_editable_renderer.tsx @@ -19,6 +19,7 @@ export const InlineEditableInput = styled.input` height: 100%; box-shadow: 0 0 1px 1px ${Colors.GRAY1}; position: absolute; + outline: none; inset: 0; `; From 64883960ca4d118108823b0289dcc31ae3bf331b Mon Sep 17 00:00:00 2001 From: Sebastien Ahkrin <30870051+Sebastien-Ahkrin@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:08:16 +0100 Subject: [PATCH 18/18] refactor: change stopRendering props to be exit --- .../inline_editable_renderer.tsx | 7 +++++-- stories/components/editable.stories.tsx | 16 ++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/inline_editable_renderer/inline_editable_renderer.tsx b/src/components/inline_editable_renderer/inline_editable_renderer.tsx index 5b0e1698..bc8ad2ef 100644 --- a/src/components/inline_editable_renderer/inline_editable_renderer.tsx +++ b/src/components/inline_editable_renderer/inline_editable_renderer.tsx @@ -5,7 +5,10 @@ import { useCallback, useMemo, useState } from 'react'; export interface InlineRendererEditableProps { ref: (node: T | null) => void; - stopRendering: () => void; + /** + * Function to exit the editable state and display the children content. + */ + exit: () => void; onKeyDown: (event: KeyboardEvent) => void; } @@ -60,7 +63,7 @@ export function InlineEditable( if (!node) return; node.focus(); }, - stopRendering: () => setIsInputRendered(false), + exit: () => setIsInputRendered(false), }; }, [isInputRendered]); diff --git a/stories/components/editable.stories.tsx b/stories/components/editable.stories.tsx index 44e15130..6dbf6c67 100644 --- a/stories/components/editable.stories.tsx +++ b/stories/components/editable.stories.tsx @@ -79,13 +79,13 @@ export function InsideTable() { header: 'Label', cell: ({ getValue, row: { index } }) => ( ( + renderEditable={({ ref, exit, onKeyDown }) => ( { - stopRendering(); + exit(); changeValue(index, 'label', event.currentTarget.value); }} /> @@ -99,13 +99,13 @@ export function InsideTable() { header: 'Field', cell: ({ getValue, row: { index } }) => ( ( + renderEditable={({ ref, exit, onKeyDown }) => ( { - stopRendering(); + exit(); changeValue(index, 'field', event.currentTarget.value); }} /> @@ -119,13 +119,13 @@ export function InsideTable() { header: 'Format', cell: ({ getValue, row: { index } }) => ( ( + renderEditable={({ ref, exit, onKeyDown }) => ( { - stopRendering(); + exit(); changeValue(index, 'format', event.currentTarget.value); }} /> @@ -182,13 +182,13 @@ export const InlineEditable = {
State: {state} ( + renderEditable={({ ref, exit, onKeyDown }) => ( { - stopRendering(); + exit(); setState(event.currentTarget.value); }}