|
1 | | -import React from 'react' |
2 | 1 | import {DiffAddedIcon, DiffModifiedIcon, DiffRemovedIcon, DiffRenamedIcon, FileIcon} from '@primer/octicons-react' |
3 | 2 | import {Meta, Story} from '@storybook/react' |
| 3 | +import React from 'react' |
| 4 | +import {ActionList} from '../ActionList' |
| 5 | +import {ActionMenu} from '../ActionMenu' |
4 | 6 | import Box from '../Box' |
| 7 | +import {Button} from '../Button' |
| 8 | +import {ConfirmationDialog} from '../Dialog/ConfirmationDialog' |
5 | 9 | import StyledOcticon from '../StyledOcticon' |
6 | 10 | import {TreeView} from './TreeView' |
7 | | -import {Button} from '../Button' |
8 | | -import {ActionMenu} from '../ActionMenu' |
9 | | -import {ActionList} from '../ActionList' |
10 | 11 |
|
11 | 12 | const meta: Meta = { |
12 | 13 | title: 'Components/TreeView', |
@@ -404,4 +405,148 @@ function TreeItem({ |
404 | 405 | ) |
405 | 406 | } |
406 | 407 |
|
| 408 | +async function wait(ms: number) { |
| 409 | + return new Promise(resolve => setTimeout(resolve, ms)) |
| 410 | +} |
| 411 | + |
| 412 | +async function loadItems(responseTime: number) { |
| 413 | + await wait(responseTime) |
| 414 | + return ['Avatar.tsx', 'Button.tsx', 'Checkbox.tsx'] |
| 415 | +} |
| 416 | + |
| 417 | +export const AsyncSuccess: Story = args => { |
| 418 | + const [isLoading, setIsLoading] = React.useState(false) |
| 419 | + const [asyncItems, setAsyncItems] = React.useState<string[]>([]) |
| 420 | + |
| 421 | + return ( |
| 422 | + <Box sx={{p: 3}}> |
| 423 | + <nav aria-label="File navigation"> |
| 424 | + <TreeView aria-label="File navigation"> |
| 425 | + <TreeView.Item |
| 426 | + onExpandedChange={async isExpanded => { |
| 427 | + if (asyncItems.length === 0 && isExpanded) { |
| 428 | + // Show loading indicator after a short delay |
| 429 | + const timeout = setTimeout(() => setIsLoading(true), 300) |
| 430 | + |
| 431 | + // Load items |
| 432 | + const items = await loadItems(args.responseTime) |
| 433 | + |
| 434 | + clearTimeout(timeout) |
| 435 | + setIsLoading(false) |
| 436 | + setAsyncItems(items) |
| 437 | + } |
| 438 | + }} |
| 439 | + > |
| 440 | + <TreeView.LeadingVisual> |
| 441 | + <TreeView.DirectoryIcon /> |
| 442 | + </TreeView.LeadingVisual> |
| 443 | + Directory with async items |
| 444 | + <TreeView.SubTree> |
| 445 | + {isLoading ? <TreeView.LoadingItem /> : null} |
| 446 | + {asyncItems.map(item => ( |
| 447 | + <TreeView.Item key={item}> |
| 448 | + <TreeView.LeadingVisual> |
| 449 | + <FileIcon /> |
| 450 | + </TreeView.LeadingVisual> |
| 451 | + {item} |
| 452 | + </TreeView.Item> |
| 453 | + ))} |
| 454 | + </TreeView.SubTree> |
| 455 | + </TreeView.Item> |
| 456 | + </TreeView> |
| 457 | + </nav> |
| 458 | + </Box> |
| 459 | + ) |
| 460 | +} |
| 461 | + |
| 462 | +AsyncSuccess.args = { |
| 463 | + responseTime: 2000 |
| 464 | +} |
| 465 | + |
| 466 | +async function alwaysFails(responseTime: number) { |
| 467 | + await wait(responseTime) |
| 468 | + throw new Error('Failed to load items') |
| 469 | + return [] |
| 470 | +} |
| 471 | + |
| 472 | +export const AsyncError: Story = args => { |
| 473 | + const [isLoading, setIsLoading] = React.useState(false) |
| 474 | + const [isExpanded, setIsExpanded] = React.useState(false) |
| 475 | + const [asyncItems, setAsyncItems] = React.useState<string[]>([]) |
| 476 | + const [error, setError] = React.useState<Error | null>(null) |
| 477 | + |
| 478 | + async function loadItems() { |
| 479 | + if (asyncItems.length === 0) { |
| 480 | + // Show loading indicator after a short delay |
| 481 | + const timeout = setTimeout(() => setIsLoading(true), 300) |
| 482 | + try { |
| 483 | + // Try to load items |
| 484 | + const items = await alwaysFails(args.responseTime) |
| 485 | + setAsyncItems(items) |
| 486 | + } catch (error) { |
| 487 | + setError(error as Error) |
| 488 | + } finally { |
| 489 | + clearTimeout(timeout) |
| 490 | + setIsLoading(false) |
| 491 | + } |
| 492 | + } |
| 493 | + } |
| 494 | + |
| 495 | + return ( |
| 496 | + <Box sx={{p: 3}}> |
| 497 | + <nav aria-label="File navigation"> |
| 498 | + <TreeView aria-label="File navigation"> |
| 499 | + <TreeView.Item |
| 500 | + expanded={isExpanded} |
| 501 | + onExpandedChange={isExpanded => { |
| 502 | + setIsExpanded(isExpanded) |
| 503 | + |
| 504 | + if (isExpanded) { |
| 505 | + loadItems() |
| 506 | + } |
| 507 | + }} |
| 508 | + > |
| 509 | + <TreeView.LeadingVisual> |
| 510 | + <TreeView.DirectoryIcon /> |
| 511 | + </TreeView.LeadingVisual> |
| 512 | + Directory with async items |
| 513 | + <TreeView.SubTree> |
| 514 | + {isLoading ? <TreeView.LoadingItem /> : null} |
| 515 | + {error ? ( |
| 516 | + <ConfirmationDialog |
| 517 | + title="Error" |
| 518 | + onClose={gesture => { |
| 519 | + setError(null) |
| 520 | + |
| 521 | + if (gesture === 'confirm') { |
| 522 | + loadItems() |
| 523 | + } else { |
| 524 | + setIsExpanded(false) |
| 525 | + } |
| 526 | + }} |
| 527 | + confirmButtonContent="Retry" |
| 528 | + > |
| 529 | + {error.message} |
| 530 | + </ConfirmationDialog> |
| 531 | + ) : null} |
| 532 | + {asyncItems.map(item => ( |
| 533 | + <TreeView.Item key={item}> |
| 534 | + <TreeView.LeadingVisual> |
| 535 | + <FileIcon /> |
| 536 | + </TreeView.LeadingVisual> |
| 537 | + {item} |
| 538 | + </TreeView.Item> |
| 539 | + ))} |
| 540 | + </TreeView.SubTree> |
| 541 | + </TreeView.Item> |
| 542 | + </TreeView> |
| 543 | + </nav> |
| 544 | + </Box> |
| 545 | + ) |
| 546 | +} |
| 547 | + |
| 548 | +AsyncError.args = { |
| 549 | + responseTime: 2000 |
| 550 | +} |
| 551 | + |
407 | 552 | export default meta |
0 commit comments