index.stories.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import {Fragment, useCallback, useState} from 'react';
  2. import type {Location} from 'history';
  3. import {Button} from 'sentry/components/button';
  4. import type {GridColumnOrder} from 'sentry/components/gridEditable';
  5. import GridEditable from 'sentry/components/gridEditable';
  6. import useQueryBasedColumnResize from 'sentry/components/replays/useQueryBasedColumnResize';
  7. import JSXNode from 'sentry/components/stories/jsxNode';
  8. import JSXProperty from 'sentry/components/stories/jsxProperty';
  9. import Matrix from 'sentry/components/stories/matrix';
  10. import SideBySide from 'sentry/components/stories/sideBySide';
  11. import {backend, frontend} from 'sentry/data/platformCategories';
  12. import storyBook from 'sentry/stories/storyBook';
  13. import {useLocation} from 'sentry/utils/useLocation';
  14. interface ExampleDataItem {
  15. category: 'frontend' | 'backend';
  16. name: string;
  17. }
  18. export default storyBook(GridEditable, story => {
  19. const columns: GridColumnOrder<keyof ExampleDataItem>[] = [
  20. {key: 'category', name: 'Platform Category'},
  21. {key: 'name', name: 'Platform Name'},
  22. ];
  23. const data: ExampleDataItem[] = [
  24. ...frontend.slice(0, 3).map(name => ({name, category: 'frontend' as const})),
  25. ...backend.slice(0, 3).map(name => ({name, category: 'backend' as const})),
  26. ];
  27. const mockLocation: Location = {
  28. key: '',
  29. search: '',
  30. hash: '',
  31. action: 'PUSH',
  32. state: null,
  33. query: {},
  34. pathname: '/mock-pathname/',
  35. };
  36. story('Minimal', () => {
  37. return (
  38. <GridEditable
  39. data={[]}
  40. columnOrder={columns}
  41. columnSortBy={[]}
  42. grid={{}}
  43. location={mockLocation}
  44. />
  45. );
  46. });
  47. const columnsWithWidth: GridColumnOrder<keyof ExampleDataItem | 'other'>[] =
  48. columns.map(col => {
  49. col.width = 200;
  50. return col;
  51. });
  52. columnsWithWidth.push({key: 'other', name: 'Other', width: 200});
  53. const renderHeadCell = (column: GridColumnOrder, columnIndex: number) =>
  54. `#${columnIndex} ${column.name}`;
  55. const renderBodyCell = (
  56. column: GridColumnOrder<keyof ExampleDataItem | 'other'>,
  57. dataRow: ExampleDataItem,
  58. rowIndex: number,
  59. columnIndex: number
  60. ) =>
  61. column.key in dataRow
  62. ? dataRow[column.key]
  63. : JSON.stringify({column, dataRow, rowIndex, columnIndex});
  64. story('Basic', () => {
  65. return (
  66. <Fragment>
  67. <p>
  68. By default the column widths are resizable, but will reset frequently unless you
  69. persist them somehow.
  70. </p>
  71. <GridEditable
  72. data={data}
  73. columnOrder={columnsWithWidth}
  74. columnSortBy={[]}
  75. grid={{
  76. renderHeadCell,
  77. renderBodyCell,
  78. }}
  79. location={mockLocation}
  80. />
  81. </Fragment>
  82. );
  83. });
  84. story('Props', () => (
  85. <SideBySide>
  86. <div>
  87. <p>
  88. <JSXNode name="GridEditable" props={{error: String}} />
  89. </p>
  90. <GridEditable
  91. error="An error happened"
  92. data={data}
  93. columnOrder={columns}
  94. columnSortBy={[]}
  95. grid={{}}
  96. location={mockLocation}
  97. />
  98. </div>
  99. <div>
  100. <p>
  101. <JSXNode name="GridEditable" props={{isLoading: true}} />
  102. </p>
  103. <GridEditable
  104. isLoading
  105. data={data}
  106. columnOrder={columns}
  107. columnSortBy={[]}
  108. grid={{}}
  109. location={mockLocation}
  110. />
  111. </div>
  112. </SideBySide>
  113. ));
  114. story('Row Mouse Events', () => {
  115. const [activeRowKey, setActiveRowKey] = useState<number | undefined>(undefined);
  116. const activeRow = activeRowKey !== undefined ? data[activeRowKey] : undefined;
  117. return (
  118. <Fragment>
  119. <p>
  120. You can provide a <JSXProperty name="onRowMouseOver" value={Function} /> and a{' '}
  121. <JSXProperty name="onRowMouseOut" value={Function} /> callback. You can also
  122. combine that with the <JSXProperty name="highlightedRowKey" value={Number} />{' '}
  123. prop to highlight a row.
  124. </p>
  125. <p>
  126. Hovered Row: {activeRow?.category} {activeRow?.name}
  127. </p>
  128. <GridEditable
  129. data={data}
  130. columnOrder={columns}
  131. columnSortBy={[]}
  132. grid={{}}
  133. location={mockLocation}
  134. onRowMouseOver={(_dataRow, key) => {
  135. setActiveRowKey(key);
  136. }}
  137. onRowMouseOut={() => {
  138. setActiveRowKey(undefined);
  139. }}
  140. highlightedRowKey={activeRowKey}
  141. />
  142. </Fragment>
  143. );
  144. });
  145. function useStatefulColumnWidths() {
  146. const [columnsWithDynamicWidths, setColumns] =
  147. useState<GridColumnOrder<keyof ExampleDataItem | 'other'>[]>(columnsWithWidth);
  148. const handleResizeColumn = useCallback(
  149. (
  150. columnIndex: number,
  151. nextColumn: GridColumnOrder<keyof ExampleDataItem | 'other'>
  152. ) => {
  153. setColumns(prev => {
  154. const next = [...prev];
  155. next[columnIndex] = nextColumn;
  156. return next;
  157. });
  158. },
  159. []
  160. );
  161. return {
  162. columns: columnsWithDynamicWidths,
  163. handleResizeColumn,
  164. };
  165. }
  166. story('Column Resize', () => {
  167. const statefulColumnResize = useStatefulColumnWidths();
  168. const location = useLocation();
  169. const queryBasedColumnResize = useQueryBasedColumnResize({
  170. columns: columnsWithWidth,
  171. paramName: 'width',
  172. location,
  173. });
  174. return (
  175. <Fragment>
  176. <p>
  177. You can keep track of the column widths by implementing the{' '}
  178. <JSXProperty name="onResizeColumn" value={Function} /> callback.
  179. </p>
  180. <SideBySide>
  181. <div>
  182. <p>In this example we are saving the column widths to state.</p>
  183. <GridEditable
  184. data={data}
  185. columnOrder={statefulColumnResize.columns}
  186. columnSortBy={[]}
  187. grid={{
  188. renderHeadCell,
  189. renderBodyCell,
  190. onResizeColumn: statefulColumnResize.handleResizeColumn,
  191. }}
  192. location={mockLocation}
  193. />
  194. </div>
  195. <div>
  196. <p>
  197. In this example we are using <kbd>useQueryBasedColumnResize</kbd>. Notice
  198. how the url updates after you drag columns.
  199. </p>
  200. <GridEditable
  201. data={data}
  202. columnOrder={queryBasedColumnResize.columns}
  203. columnSortBy={[]}
  204. grid={{
  205. renderHeadCell,
  206. renderBodyCell,
  207. onResizeColumn: queryBasedColumnResize.handleResizeColumn,
  208. }}
  209. location={mockLocation}
  210. />
  211. </div>
  212. </SideBySide>
  213. </Fragment>
  214. );
  215. });
  216. story('Fixed Height', () => (
  217. <GridEditable
  218. data={data}
  219. columnOrder={columns}
  220. columnSortBy={[]}
  221. grid={{
  222. renderHeadCell,
  223. renderBodyCell,
  224. }}
  225. location={mockLocation}
  226. height={200}
  227. stickyHeader
  228. />
  229. ));
  230. story('Header Augmentations', () => (
  231. <Matrix
  232. render={GridEditable}
  233. propMatrix={{
  234. data: [data],
  235. columnOrder: [columns],
  236. columnSortBy: [[]],
  237. grid: [{}],
  238. location: [mockLocation],
  239. headerButtons: [undefined, () => <Button>Take Action</Button>],
  240. title: [undefined, 'GridEditable Title'],
  241. }}
  242. selectedProps={['title', 'headerButtons']}
  243. sizingWindowProps={{display: 'block'}}
  244. />
  245. ));
  246. });