-
Notifications
You must be signed in to change notification settings - Fork 7
docs: add editable table text #846
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
2302519
docs: add editable table text
Sebastien-Ahkrin 1ec8f42
feat: create switchable input
Sebastien-Ahkrin 7540622
wip: bug with input size that take the entire place (and more) when c…
Sebastien-Ahkrin 2615a6b
Merge remote-tracking branch 'origin/main' into feat-editable-table
Sebastien-Ahkrin 0eb593f
feat: correctly fix the table width size
Sebastien-Ahkrin a7f40b6
feat: change inline editable renderer
Sebastien-Ahkrin 7ab1fe8
refactor: make a poc
Sebastien-Ahkrin 45a70b6
refactor: remove hardcoded values
Sebastien-Ahkrin 076a31b
refactor: change folder names
Sebastien-Ahkrin f29353b
refactor: change component name
Sebastien-Ahkrin 25f31c9
fix: run stylelint fix
Sebastien-Ahkrin 98f1c2b
refactor: change component name
Sebastien-Ahkrin f61769d
refactor: add min height to empty value render
Sebastien-Ahkrin 1210df9
refactor: remove docs for stylelint
Sebastien-Ahkrin 6c4d4e7
refactor: make some changes according to reviewers
Sebastien-Ahkrin fd7ef58
refactor: change docs
Sebastien-Ahkrin 4d3f9c7
feat: press enter will save the input
Sebastien-Ahkrin 51510ab
fix: remove outline from input
stropitek 6488396
refactor: change stopRendering props to be exit
Sebastien-Ahkrin 3b8c7cc
Merge remote-tracking branch 'origin/feat-editable-table' into feat-e…
Sebastien-Ahkrin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from './inline_editable_renderer.js'; |
75 changes: 75 additions & 0 deletions
75
src/components/inline_editable_renderer/inline_editable_renderer.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import styled from '@emotion/styled'; | ||
| import type { ReactNode } from 'react'; | ||
| import { useCallback, useMemo, useState } from 'react'; | ||
|
|
||
| export interface InlineRendererEditableProps<T extends HTMLElement> { | ||
| ref: (node: T | null) => void; | ||
| toggle: () => void; | ||
| } | ||
|
|
||
| export interface InlineEditableProps<T extends HTMLElement> { | ||
| renderEditable: (props: InlineRendererEditableProps<T>) => ReactNode; | ||
| children: ReactNode; | ||
| } | ||
|
|
||
| export const InlineEditableInput = styled.input` | ||
| width: 100%; | ||
| height: 100%; | ||
| box-shadow: 0 0 1px 1px #595959; | ||
| position: absolute; | ||
| inset: 0; | ||
|
|
||
| :focus, | ||
| :hover { | ||
| box-shadow: 0 0 1px 1px #595959; | ||
Sebastien-Ahkrin marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| `; | ||
|
|
||
| const Container = styled.div` | ||
| min-width: 100%; | ||
| width: 100%; | ||
| min-height: 21px; | ||
|
|
||
| :focus, | ||
| :hover { | ||
| box-shadow: 0 0 1px 1px #595959; | ||
| } | ||
Sebastien-Ahkrin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| `; | ||
|
|
||
| export function InlineEditable<T extends HTMLElement>( | ||
Sebastien-Ahkrin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| props: InlineEditableProps<T>, | ||
| ) { | ||
| const { children, renderEditable } = props; | ||
| const [isInputRendered, setIsInputRendered] = useState(false); | ||
|
|
||
| const toggle = useCallback(() => { | ||
| return setIsInputRendered((old) => !old); | ||
| }, []); | ||
|
|
||
| const renderEditableProps = useMemo<InlineRendererEditableProps<T>>(() => { | ||
| return { | ||
| isRendered: isInputRendered, | ||
| ref: (node) => { | ||
| if (!node) return; | ||
| node.focus(); | ||
| }, | ||
| toggle, | ||
| }; | ||
| }, [isInputRendered, toggle]); | ||
|
|
||
| return ( | ||
| <div style={{ position: 'relative' }}> | ||
| <div style={{ visibility: isInputRendered ? 'visible' : 'hidden' }}> | ||
| {renderEditable(renderEditableProps)} | ||
| </div> | ||
|
|
||
| <Container | ||
| tabIndex={isInputRendered ? -1 : 0} | ||
| onFocus={() => setIsInputRendered(true)} | ||
| onClick={toggle} | ||
| > | ||
| {children} | ||
| </Container> | ||
| </div> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| 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 { | ||
| InlineEditable as InlineEditableComponent, | ||
| InlineEditableInput, | ||
| } from '../../src/components/inline_editable_renderer/index.js'; | ||
|
|
||
| export default { | ||
| title: 'Components / InlineEditableComponent', | ||
| }; | ||
|
|
||
| interface TableData { | ||
| id: number; | ||
| label: string; | ||
| field: string; | ||
| format: string; | ||
| visible: boolean; | ||
| } | ||
|
|
||
| const helper = createTableColumnHelper<TableData>(); | ||
|
|
||
| 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 deleteIndex = useCallback((index: number) => { | ||
| return setState((prev) => prev.filter((_, i) => i !== index)); | ||
| }, []); | ||
|
|
||
| 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 } }) => ( | ||
| <InlineEditableComponent | ||
| renderEditable={({ ref, toggle }) => ( | ||
| <InlineEditableInput | ||
| ref={ref} | ||
| defaultValue={getValue()} | ||
| onBlur={(event) => { | ||
| toggle(); | ||
| changeValue(index, 'label', event.currentTarget.value); | ||
| }} | ||
| /> | ||
| )} | ||
| > | ||
| {getValue()} | ||
| </InlineEditableComponent> | ||
| ), | ||
| }), | ||
| helper.accessor('field', { | ||
| header: 'Field', | ||
| cell: ({ getValue, row: { index } }) => ( | ||
| <InlineEditableComponent | ||
| renderEditable={({ ref, toggle }) => ( | ||
| <InlineEditableInput | ||
| ref={ref} | ||
| defaultValue={getValue()} | ||
| onBlur={(event) => { | ||
| toggle(); | ||
| changeValue(index, 'field', event.currentTarget.value); | ||
| }} | ||
| /> | ||
| )} | ||
| > | ||
| {getValue()} | ||
| </InlineEditableComponent> | ||
| ), | ||
| }), | ||
| helper.accessor('format', { | ||
| header: 'Format', | ||
| cell: ({ getValue, row: { index } }) => ( | ||
| <InlineEditableComponent | ||
| renderEditable={({ ref, toggle }) => ( | ||
| <InlineEditableInput | ||
| ref={ref} | ||
| defaultValue={getValue()} | ||
| onBlur={(event) => { | ||
| toggle(); | ||
| changeValue(index, 'format', event.currentTarget.value); | ||
| }} | ||
| /> | ||
| )} | ||
| > | ||
| {getValue()} | ||
| </InlineEditableComponent> | ||
| ), | ||
| }), | ||
| helper.accessor('visible', { header: 'Visible' }), | ||
| helper.display({ | ||
| header: ' ', | ||
| cell: ({ row: { index } }) => { | ||
| return ( | ||
| <Button | ||
| type="button" | ||
| icon="trash" | ||
| tabIndex={-1} | ||
| onClick={() => deleteIndex(index)} | ||
| /> | ||
| ); | ||
| }, | ||
| }), | ||
| ]; | ||
| }, [changeValue, deleteIndex]); | ||
|
|
||
| return ( | ||
| <div | ||
| style={{ | ||
| width: '100%', | ||
| display: 'flex', | ||
| flexDirection: 'row', | ||
| gap: 5, | ||
| }} | ||
| > | ||
| <div> | ||
| <Table<TableData> | ||
| data={state} | ||
| columns={columns} | ||
| estimatedRowHeight={() => 50} | ||
| /> | ||
| </div> | ||
|
|
||
| <pre>{JSON.stringify(state, null, 2)}</pre> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export const BorderlessEditableInputExample = { | ||
Sebastien-Ahkrin marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| render: () => <InlineEditableInput defaultValue="Hello, World!" />, | ||
| decorators: (Story) => ( | ||
| <div | ||
| style={{ | ||
| backgroundColor: 'lightblue', | ||
| height: 25, | ||
| width: 100, | ||
| position: 'relative', | ||
| }} | ||
| > | ||
| <Story /> | ||
| </div> | ||
| ), | ||
| } satisfies StoryObj; | ||
|
|
||
| export const InlineEditable = { | ||
| render: () => { | ||
| const [state, setState] = useState('Hello, World!'); | ||
|
|
||
| return ( | ||
| <div style={{ width: 100 }}> | ||
| <span>State: {state}</span> | ||
| <InlineEditableComponent | ||
| renderEditable={({ ref, toggle }) => ( | ||
| <InlineEditableInput | ||
| ref={ref} | ||
| defaultValue={state} | ||
| onBlur={(event) => { | ||
| toggle(); | ||
|
|
||
| setState(event.currentTarget.value); | ||
| }} | ||
| /> | ||
| )} | ||
| > | ||
| {state} | ||
| </InlineEditableComponent> | ||
| </div> | ||
| ); | ||
| }, | ||
| } satisfies StoryObj; | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.