@workflow/web-shared 5.0.0-beta.10 → 5.0.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/components/index.d.ts +1 -0
  2. package/dist/components/index.d.ts.map +1 -1
  3. package/dist/components/index.js +2 -1
  4. package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -1
  5. package/dist/components/new-trace-viewer/components/event-list.js +9 -4
  6. package/dist/components/new-trace-viewer/components/split-pane.d.ts +3 -2
  7. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -1
  8. package/dist/components/new-trace-viewer/components/split-pane.js +10 -4
  9. package/dist/components/new-trace-viewer/components/timeline.d.ts +1 -1
  10. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -1
  11. package/dist/components/new-trace-viewer/components/timeline.js +6 -8
  12. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts +2 -0
  13. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts.map +1 -0
  14. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.js +19 -0
  15. package/dist/components/new-trace-viewer/components/use-row-window.d.ts +20 -0
  16. package/dist/components/new-trace-viewer/components/use-row-window.d.ts.map +1 -0
  17. package/dist/components/new-trace-viewer/components/use-row-window.js +69 -0
  18. package/dist/components/new-trace-viewer/context.d.ts.map +1 -1
  19. package/dist/components/new-trace-viewer/context.js +2 -2
  20. package/dist/components/new-trace-viewer/trace-viewer.d.ts +4 -1
  21. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -1
  22. package/dist/components/new-trace-viewer/trace-viewer.js +19 -6
  23. package/dist/components/trace-viewer-new.d.ts +5 -1
  24. package/dist/components/trace-viewer-new.d.ts.map +1 -1
  25. package/dist/components/trace-viewer-new.js +7 -8
  26. package/dist/components/ui/icon-button.d.ts +8 -2
  27. package/dist/components/ui/icon-button.d.ts.map +1 -1
  28. package/dist/components/ui/icon-button.js +4 -4
  29. package/dist/components/ui/kbd.d.ts +6 -0
  30. package/dist/components/ui/kbd.d.ts.map +1 -0
  31. package/dist/components/ui/kbd.js +6 -0
  32. package/dist/components/ui/tooltip.d.ts +10 -0
  33. package/dist/components/ui/tooltip.d.ts.map +1 -0
  34. package/dist/components/ui/tooltip.js +12 -0
  35. package/dist/hooks/use-intersection-observer.d.ts +17 -0
  36. package/dist/hooks/use-intersection-observer.d.ts.map +1 -0
  37. package/dist/hooks/use-intersection-observer.js +29 -0
  38. package/dist/hooks/use-load-more-on-scroll.d.ts +21 -0
  39. package/dist/hooks/use-load-more-on-scroll.d.ts.map +1 -0
  40. package/dist/hooks/use-load-more-on-scroll.js +28 -0
  41. package/package.json +3 -3
  42. package/src/components/index.ts +1 -0
  43. package/src/components/new-trace-viewer/components/event-list.tsx +19 -11
  44. package/src/components/new-trace-viewer/components/split-pane.tsx +15 -2
  45. package/src/components/new-trace-viewer/components/timeline.tsx +27 -19
  46. package/src/components/new-trace-viewer/components/trace-viewer-skeleton.tsx +94 -0
  47. package/src/components/new-trace-viewer/components/use-row-window.ts +93 -0
  48. package/src/components/new-trace-viewer/context.tsx +1 -3
  49. package/src/components/new-trace-viewer/trace-viewer.tsx +89 -21
  50. package/src/components/trace-viewer-new.tsx +20 -12
  51. package/src/components/ui/icon-button.tsx +7 -11
  52. package/src/components/ui/kbd.tsx +21 -0
  53. package/src/components/ui/tooltip.tsx +47 -0
  54. package/src/hooks/use-intersection-observer.ts +57 -0
  55. package/src/hooks/use-load-more-on-scroll.ts +49 -0
@@ -1,5 +1,5 @@
1
1
  import { type VariantProps } from 'class-variance-authority';
2
- import type * as React from 'react';
2
+ import * as React from 'react';
3
3
  declare const iconButtonVariants: (props?: ({
4
4
  variant?: "muted" | "tertiary" | null | undefined;
5
5
  size?: "small" | "tiny" | null | undefined;
@@ -8,6 +8,12 @@ export type IconButtonProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>
8
8
  'aria-label': string;
9
9
  type?: 'button' | 'submit' | 'reset';
10
10
  };
11
- export declare function IconButton({ className, variant, size, type, ...props }: IconButtonProps): import("react/jsx-runtime").JSX.Element;
11
+ export declare const IconButton: React.ForwardRefExoticComponent<Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "type"> & VariantProps<(props?: ({
12
+ variant?: "muted" | "tertiary" | null | undefined;
13
+ size?: "small" | "tiny" | null | undefined;
14
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string> & {
15
+ 'aria-label': string;
16
+ type?: "button" | "submit" | "reset";
17
+ } & React.RefAttributes<HTMLButtonElement>>;
12
18
  export { iconButtonVariants };
13
19
  //# sourceMappingURL=icon-button.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"icon-button.d.ts","sourceRoot":"","sources":["../../../src/components/ui/icon-button.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAGpC,QAAA,MAAM,kBAAkB;;;8EAyBvB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,CAChC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAC7C,MAAM,CACP,GACC,YAAY,CAAC,OAAO,kBAAkB,CAAC,GAAG;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;CACtC,CAAC;AAEJ,wBAAgB,UAAU,CAAC,EACzB,SAAS,EACT,OAAO,EACP,IAAI,EACJ,IAAe,EACf,GAAG,KAAK,EACT,EAAE,eAAe,2CAQjB;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"icon-button.d.ts","sourceRoot":"","sources":["../../../src/components/ui/icon-button.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,QAAA,MAAM,kBAAkB;;;8EAyBvB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,CAChC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAC7C,MAAM,CACP,GACC,YAAY,CAAC,OAAO,kBAAkB,CAAC,GAAG;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;CACtC,CAAC;AAEJ,eAAO,MAAM,UAAU;;;;kBAJL,MAAM;WACb,QAAQ,GAAG,QAAQ,GAAG,OAAO;2CAYvC,CAAC;AAGF,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cva } from 'class-variance-authority';
3
+ import * as React from 'react';
3
4
  import { cn } from '../../lib/utils';
4
5
  const iconButtonVariants = cva([
5
6
  'm-0 inline-flex shrink-0 appearance-none items-center justify-center border-0 bg-transparent p-0 align-baseline font-inherit no-underline [background:none] [-webkit-appearance:none] [-webkit-tap-highlight-color:transparent]',
@@ -22,8 +23,7 @@ const iconButtonVariants = cva([
22
23
  size: 'tiny',
23
24
  },
24
25
  });
25
- export function IconButton({ className, variant, size, type = 'button', ...props }) {
26
- return (_jsx("button", { type: type, className: cn(iconButtonVariants({ variant, size, className })), ...props }));
27
- }
26
+ export const IconButton = React.forwardRef(({ className, variant, size, type = 'button', ...props }, ref) => (_jsx("button", { ref: ref, type: type, className: cn(iconButtonVariants({ variant, size, className })), ...props })));
27
+ IconButton.displayName = 'IconButton';
28
28
  export { iconButtonVariants };
29
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaWNvbi1idXR0b24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29tcG9uZW50cy91aS9pY29uLWJ1dHRvbi50c3giXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU8sRUFBRSxHQUFHLEVBQXFCLE1BQU0sMEJBQTBCLENBQUM7QUFFbEUsT0FBTyxFQUFFLEVBQUUsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRXJDLE1BQU0sa0JBQWtCLEdBQUcsR0FBRyxDQUM1QjtJQUNFLGlPQUFpTztJQUNqTyw0Q0FBNEM7SUFDNUMsd0VBQXdFO0lBQ3hFLDRIQUE0SDtDQUM3SCxFQUNEO0lBQ0UsUUFBUSxFQUFFO1FBQ1IsT0FBTyxFQUFFO1lBQ1AsUUFBUSxFQUNOLDBJQUEwSTtZQUM1SSxLQUFLLEVBQ0gsMElBQTBJO1NBQzdJO1FBQ0QsSUFBSSxFQUFFO1lBQ0osSUFBSSxFQUFFLCtDQUErQztZQUNyRCxLQUFLLEVBQUUsNENBQTRDO1NBQ3BEO0tBQ0Y7SUFDRCxlQUFlLEVBQUU7UUFDZixPQUFPLEVBQUUsVUFBVTtRQUNuQixJQUFJLEVBQUUsTUFBTTtLQUNiO0NBQ0YsQ0FDRixDQUFDO0FBV0YsTUFBTSxVQUFVLFVBQVUsQ0FBQyxFQUN6QixTQUFTLEVBQ1QsT0FBTyxFQUNQLElBQUksRUFDSixJQUFJLEdBQUcsUUFBUSxFQUNmLEdBQUcsS0FBSyxFQUNRO0lBQ2hCLE9BQU8sQ0FDTCxpQkFDRSxJQUFJLEVBQUUsSUFBSSxFQUNWLFNBQVMsRUFBRSxFQUFFLENBQUMsa0JBQWtCLENBQUMsRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxDQUFDLENBQUMsS0FDM0QsS0FBSyxHQUNULENBQ0gsQ0FBQztBQUNKLENBQUM7QUFFRCxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGN2YSwgdHlwZSBWYXJpYW50UHJvcHMgfSBmcm9tICdjbGFzcy12YXJpYW5jZS1hdXRob3JpdHknO1xuaW1wb3J0IHR5cGUgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCc7XG5pbXBvcnQgeyBjbiB9IGZyb20gJy4uLy4uL2xpYi91dGlscyc7XG5cbmNvbnN0IGljb25CdXR0b25WYXJpYW50cyA9IGN2YShcbiAgW1xuICAgICdtLTAgaW5saW5lLWZsZXggc2hyaW5rLTAgYXBwZWFyYW5jZS1ub25lIGl0ZW1zLWNlbnRlciBqdXN0aWZ5LWNlbnRlciBib3JkZXItMCBiZy10cmFuc3BhcmVudCBwLTAgYWxpZ24tYmFzZWxpbmUgZm9udC1pbmhlcml0IG5vLXVuZGVybGluZSBbYmFja2dyb3VuZDpub25lXSBbLXdlYmtpdC1hcHBlYXJhbmNlOm5vbmVdIFstd2Via2l0LXRhcC1oaWdobGlnaHQtY29sb3I6dHJhbnNwYXJlbnRdJyxcbiAgICAndHJhbnNpdGlvbi1jb2xvcnMgZHVyYXRpb24tMTUwIGVhc2UtaW4tb3V0JyxcbiAgICAnZW5hYmxlZDpjdXJzb3ItcG9pbnRlciBkaXNhYmxlZDpjdXJzb3Itbm90LWFsbG93ZWQgZGlzYWJsZWQ6b3BhY2l0eS00MCcsXG4gICAgJ2ZvY3VzLXZpc2libGU6b3V0bGluZSBmb2N1cy12aXNpYmxlOm91dGxpbmUtMiBmb2N1cy12aXNpYmxlOm91dGxpbmUtW3ZhcigtLWRzLWZvY3VzLWNvbG9yKV0gZm9jdXMtdmlzaWJsZTpvdXRsaW5lLW9mZnNldC0yJyxcbiAgXSxcbiAge1xuICAgIHZhcmlhbnRzOiB7XG4gICAgICB2YXJpYW50OiB7XG4gICAgICAgIHRlcnRpYXJ5OlxuICAgICAgICAgICd0ZXh0LWdyYXktOTAwIGVuYWJsZWQ6aG92ZXI6YmctZ3JheS1hbHBoYS0yMDAgZW5hYmxlZDpob3Zlcjp0ZXh0LWdyYXktMTAwMCBlbmFibGVkOmFjdGl2ZTpiZy1ncmF5LWFscGhhLTIwMCBmb2N1cy12aXNpYmxlOnRleHQtZ3JheS0xMDAwJyxcbiAgICAgICAgbXV0ZWQ6XG4gICAgICAgICAgJ3RleHQtZ3JheS05MDAgZW5hYmxlZDpob3ZlcjpiZy1ncmF5LWFscGhhLTIwMCBlbmFibGVkOmhvdmVyOnRleHQtZ3JheS0xMDAwIGVuYWJsZWQ6YWN0aXZlOmJnLWdyYXktYWxwaGEtMjAwIGZvY3VzLXZpc2libGU6dGV4dC1ncmF5LTEwMDAnLFxuICAgICAgfSxcbiAgICAgIHNpemU6IHtcbiAgICAgICAgdGlueTogJ2gtNiB3LTYgcm91bmRlZC1bNHB4XSBbJl9zdmddOmgtNCBbJl9zdmddOnctNCcsXG4gICAgICAgIHNtYWxsOiAnaC04IHctOCByb3VuZGVkLW1kIFsmX3N2Z106aC00IFsmX3N2Z106dy00JyxcbiAgICAgIH0sXG4gICAgfSxcbiAgICBkZWZhdWx0VmFyaWFudHM6IHtcbiAgICAgIHZhcmlhbnQ6ICd0ZXJ0aWFyeScsXG4gICAgICBzaXplOiAndGlueScsXG4gICAgfSxcbiAgfVxuKTtcblxuZXhwb3J0IHR5cGUgSWNvbkJ1dHRvblByb3BzID0gT21pdDxcbiAgUmVhY3QuQnV0dG9uSFRNTEF0dHJpYnV0ZXM8SFRNTEJ1dHRvbkVsZW1lbnQ+LFxuICAndHlwZSdcbj4gJlxuICBWYXJpYW50UHJvcHM8dHlwZW9mIGljb25CdXR0b25WYXJpYW50cz4gJiB7XG4gICAgJ2FyaWEtbGFiZWwnOiBzdHJpbmc7XG4gICAgdHlwZT86ICdidXR0b24nIHwgJ3N1Ym1pdCcgfCAncmVzZXQnO1xuICB9O1xuXG5leHBvcnQgZnVuY3Rpb24gSWNvbkJ1dHRvbih7XG4gIGNsYXNzTmFtZSxcbiAgdmFyaWFudCxcbiAgc2l6ZSxcbiAgdHlwZSA9ICdidXR0b24nLFxuICAuLi5wcm9wc1xufTogSWNvbkJ1dHRvblByb3BzKSB7XG4gIHJldHVybiAoXG4gICAgPGJ1dHRvblxuICAgICAgdHlwZT17dHlwZX1cbiAgICAgIGNsYXNzTmFtZT17Y24oaWNvbkJ1dHRvblZhcmlhbnRzKHsgdmFyaWFudCwgc2l6ZSwgY2xhc3NOYW1lIH0pKX1cbiAgICAgIHsuLi5wcm9wc31cbiAgICAvPlxuICApO1xufVxuXG5leHBvcnQgeyBpY29uQnV0dG9uVmFyaWFudHMgfTtcbiJdfQ==
29
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaWNvbi1idXR0b24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29tcG9uZW50cy91aS9pY29uLWJ1dHRvbi50c3giXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU8sRUFBRSxHQUFHLEVBQXFCLE1BQU0sMEJBQTBCLENBQUM7QUFDbEUsT0FBTyxLQUFLLEtBQUssTUFBTSxPQUFPLENBQUM7QUFDL0IsT0FBTyxFQUFFLEVBQUUsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRXJDLE1BQU0sa0JBQWtCLEdBQUcsR0FBRyxDQUM1QjtJQUNFLGlPQUFpTztJQUNqTyw0Q0FBNEM7SUFDNUMsd0VBQXdFO0lBQ3hFLDRIQUE0SDtDQUM3SCxFQUNEO0lBQ0UsUUFBUSxFQUFFO1FBQ1IsT0FBTyxFQUFFO1lBQ1AsUUFBUSxFQUNOLDBJQUEwSTtZQUM1SSxLQUFLLEVBQ0gsMElBQTBJO1NBQzdJO1FBQ0QsSUFBSSxFQUFFO1lBQ0osSUFBSSxFQUFFLCtDQUErQztZQUNyRCxLQUFLLEVBQUUsNENBQTRDO1NBQ3BEO0tBQ0Y7SUFDRCxlQUFlLEVBQUU7UUFDZixPQUFPLEVBQUUsVUFBVTtRQUNuQixJQUFJLEVBQUUsTUFBTTtLQUNiO0NBQ0YsQ0FDRixDQUFDO0FBV0YsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHLEtBQUssQ0FBQyxVQUFVLENBQ3hDLENBQUMsRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxJQUFJLEdBQUcsUUFBUSxFQUFFLEdBQUcsS0FBSyxFQUFFLEVBQUUsR0FBRyxFQUFFLEVBQUUsQ0FBQyxDQUNoRSxpQkFDRSxHQUFHLEVBQUUsR0FBRyxFQUNSLElBQUksRUFBRSxJQUFJLEVBQ1YsU0FBUyxFQUFFLEVBQUUsQ0FBQyxrQkFBa0IsQ0FBQyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxLQUMzRCxLQUFLLEdBQ1QsQ0FDSCxDQUNGLENBQUM7QUFDRixVQUFVLENBQUMsV0FBVyxHQUFHLFlBQVksQ0FBQztBQUV0QyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGN2YSwgdHlwZSBWYXJpYW50UHJvcHMgfSBmcm9tICdjbGFzcy12YXJpYW5jZS1hdXRob3JpdHknO1xuaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnO1xuaW1wb3J0IHsgY24gfSBmcm9tICcuLi8uLi9saWIvdXRpbHMnO1xuXG5jb25zdCBpY29uQnV0dG9uVmFyaWFudHMgPSBjdmEoXG4gIFtcbiAgICAnbS0wIGlubGluZS1mbGV4IHNocmluay0wIGFwcGVhcmFuY2Utbm9uZSBpdGVtcy1jZW50ZXIganVzdGlmeS1jZW50ZXIgYm9yZGVyLTAgYmctdHJhbnNwYXJlbnQgcC0wIGFsaWduLWJhc2VsaW5lIGZvbnQtaW5oZXJpdCBuby11bmRlcmxpbmUgW2JhY2tncm91bmQ6bm9uZV0gWy13ZWJraXQtYXBwZWFyYW5jZTpub25lXSBbLXdlYmtpdC10YXAtaGlnaGxpZ2h0LWNvbG9yOnRyYW5zcGFyZW50XScsXG4gICAgJ3RyYW5zaXRpb24tY29sb3JzIGR1cmF0aW9uLTE1MCBlYXNlLWluLW91dCcsXG4gICAgJ2VuYWJsZWQ6Y3Vyc29yLXBvaW50ZXIgZGlzYWJsZWQ6Y3Vyc29yLW5vdC1hbGxvd2VkIGRpc2FibGVkOm9wYWNpdHktNDAnLFxuICAgICdmb2N1cy12aXNpYmxlOm91dGxpbmUgZm9jdXMtdmlzaWJsZTpvdXRsaW5lLTIgZm9jdXMtdmlzaWJsZTpvdXRsaW5lLVt2YXIoLS1kcy1mb2N1cy1jb2xvcildIGZvY3VzLXZpc2libGU6b3V0bGluZS1vZmZzZXQtMicsXG4gIF0sXG4gIHtcbiAgICB2YXJpYW50czoge1xuICAgICAgdmFyaWFudDoge1xuICAgICAgICB0ZXJ0aWFyeTpcbiAgICAgICAgICAndGV4dC1ncmF5LTkwMCBlbmFibGVkOmhvdmVyOmJnLWdyYXktYWxwaGEtMjAwIGVuYWJsZWQ6aG92ZXI6dGV4dC1ncmF5LTEwMDAgZW5hYmxlZDphY3RpdmU6YmctZ3JheS1hbHBoYS0yMDAgZm9jdXMtdmlzaWJsZTp0ZXh0LWdyYXktMTAwMCcsXG4gICAgICAgIG11dGVkOlxuICAgICAgICAgICd0ZXh0LWdyYXktOTAwIGVuYWJsZWQ6aG92ZXI6YmctZ3JheS1hbHBoYS0yMDAgZW5hYmxlZDpob3Zlcjp0ZXh0LWdyYXktMTAwMCBlbmFibGVkOmFjdGl2ZTpiZy1ncmF5LWFscGhhLTIwMCBmb2N1cy12aXNpYmxlOnRleHQtZ3JheS0xMDAwJyxcbiAgICAgIH0sXG4gICAgICBzaXplOiB7XG4gICAgICAgIHRpbnk6ICdoLTYgdy02IHJvdW5kZWQtWzRweF0gWyZfc3ZnXTpoLTQgWyZfc3ZnXTp3LTQnLFxuICAgICAgICBzbWFsbDogJ2gtOCB3LTggcm91bmRlZC1tZCBbJl9zdmddOmgtNCBbJl9zdmddOnctNCcsXG4gICAgICB9LFxuICAgIH0sXG4gICAgZGVmYXVsdFZhcmlhbnRzOiB7XG4gICAgICB2YXJpYW50OiAndGVydGlhcnknLFxuICAgICAgc2l6ZTogJ3RpbnknLFxuICAgIH0sXG4gIH1cbik7XG5cbmV4cG9ydCB0eXBlIEljb25CdXR0b25Qcm9wcyA9IE9taXQ8XG4gIFJlYWN0LkJ1dHRvbkhUTUxBdHRyaWJ1dGVzPEhUTUxCdXR0b25FbGVtZW50PixcbiAgJ3R5cGUnXG4+ICZcbiAgVmFyaWFudFByb3BzPHR5cGVvZiBpY29uQnV0dG9uVmFyaWFudHM+ICYge1xuICAgICdhcmlhLWxhYmVsJzogc3RyaW5nO1xuICAgIHR5cGU/OiAnYnV0dG9uJyB8ICdzdWJtaXQnIHwgJ3Jlc2V0JztcbiAgfTtcblxuZXhwb3J0IGNvbnN0IEljb25CdXR0b24gPSBSZWFjdC5mb3J3YXJkUmVmPEhUTUxCdXR0b25FbGVtZW50LCBJY29uQnV0dG9uUHJvcHM+KFxuICAoeyBjbGFzc05hbWUsIHZhcmlhbnQsIHNpemUsIHR5cGUgPSAnYnV0dG9uJywgLi4ucHJvcHMgfSwgcmVmKSA9PiAoXG4gICAgPGJ1dHRvblxuICAgICAgcmVmPXtyZWZ9XG4gICAgICB0eXBlPXt0eXBlfVxuICAgICAgY2xhc3NOYW1lPXtjbihpY29uQnV0dG9uVmFyaWFudHMoeyB2YXJpYW50LCBzaXplLCBjbGFzc05hbWUgfSkpfVxuICAgICAgey4uLnByb3BzfVxuICAgIC8+XG4gIClcbik7XG5JY29uQnV0dG9uLmRpc3BsYXlOYW1lID0gJ0ljb25CdXR0b24nO1xuXG5leHBvcnQgeyBpY29uQnV0dG9uVmFyaWFudHMgfTtcbiJdfQ==
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ export declare function Kbd({ children, className, }: {
3
+ children: ReactNode;
4
+ className?: string;
5
+ }): ReactNode;
6
+ //# sourceMappingURL=kbd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kbd.d.ts","sourceRoot":"","sources":["../../../src/components/ui/kbd.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,wBAAgB,GAAG,CAAC,EAClB,QAAQ,EACR,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,SAAS,CAWZ"}
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from '../../lib/utils';
3
+ export function Kbd({ children, className, }) {
4
+ return (_jsx("kbd", { className: cn('inline-flex h-5 min-w-5 items-center justify-center rounded bg-background-100/20 px-1 text-center font-sans text-xs leading-none', className), children: children }));
5
+ }
6
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoia2JkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvdWkva2JkLnRzeCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQ0EsT0FBTyxFQUFFLEVBQUUsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRXJDLE1BQU0sVUFBVSxHQUFHLENBQUMsRUFDbEIsUUFBUSxFQUNSLFNBQVMsR0FJVjtJQUNDLE9BQU8sQ0FDTCxjQUNFLFNBQVMsRUFBRSxFQUFFLENBQ1gsa0lBQWtJLEVBQ2xJLFNBQVMsQ0FDVixZQUVBLFFBQVEsR0FDTCxDQUNQLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHR5cGUgeyBSZWFjdE5vZGUgfSBmcm9tICdyZWFjdCc7XG5pbXBvcnQgeyBjbiB9IGZyb20gJy4uLy4uL2xpYi91dGlscyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBLYmQoe1xuICBjaGlsZHJlbixcbiAgY2xhc3NOYW1lLFxufToge1xuICBjaGlsZHJlbjogUmVhY3ROb2RlO1xuICBjbGFzc05hbWU/OiBzdHJpbmc7XG59KTogUmVhY3ROb2RlIHtcbiAgcmV0dXJuIChcbiAgICA8a2JkXG4gICAgICBjbGFzc05hbWU9e2NuKFxuICAgICAgICAnaW5saW5lLWZsZXggaC01IG1pbi13LTUgaXRlbXMtY2VudGVyIGp1c3RpZnktY2VudGVyIHJvdW5kZWQgYmctYmFja2dyb3VuZC0xMDAvMjAgcHgtMSB0ZXh0LWNlbnRlciBmb250LXNhbnMgdGV4dC14cyBsZWFkaW5nLW5vbmUnLFxuICAgICAgICBjbGFzc05hbWVcbiAgICAgICl9XG4gICAgPlxuICAgICAge2NoaWxkcmVufVxuICAgIDwva2JkPlxuICApO1xufVxuIl19
@@ -0,0 +1,10 @@
1
+ import * as TooltipPrimitive from '@radix-ui/react-tooltip';
2
+ import * as React from 'react';
3
+ declare const TooltipProvider: React.FC<TooltipPrimitive.TooltipProviderProps>;
4
+ declare const Tooltip: React.FC<TooltipPrimitive.TooltipProps>;
5
+ declare const TooltipTrigger: React.ForwardRefExoticComponent<TooltipPrimitive.TooltipTriggerProps & React.RefAttributes<HTMLButtonElement>>;
6
+ declare const TooltipContent: React.ForwardRefExoticComponent<Omit<TooltipPrimitive.TooltipContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
7
+ showArrow?: boolean;
8
+ } & React.RefAttributes<HTMLDivElement>>;
9
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
10
+ //# sourceMappingURL=tooltip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../../src/components/ui/tooltip.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,gBAAgB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,QAAA,MAAM,eAAe,iDAA4B,CAAC;AAElD,QAAA,MAAM,OAAO,yCAAwB,CAAC;AAEtC,QAAA,MAAM,cAAc,gHAA2B,CAAC;AAEhD,QAAA,MAAM,cAAc;gBAGJ,OAAO;wCA4BtB,CAAC;AAGF,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,12 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as TooltipPrimitive from '@radix-ui/react-tooltip';
4
+ import * as React from 'react';
5
+ import { cn } from '../../lib/utils';
6
+ const TooltipProvider = TooltipPrimitive.Provider;
7
+ const Tooltip = TooltipPrimitive.Root;
8
+ const TooltipTrigger = TooltipPrimitive.Trigger;
9
+ const TooltipContent = React.forwardRef(({ className, sideOffset = 8, showArrow = true, children, ...props }, ref) => (_jsx(TooltipPrimitive.Portal, { children: _jsxs(TooltipPrimitive.Content, { ref: ref, sideOffset: sideOffset, className: cn('z-[99999] flex items-center gap-1 rounded-[10px] bg-gray-1000 px-2 py-1.5 !text-label-13 text-background-100 shadow-md select-none', className), ...props, children: [children, showArrow ? (_jsx(TooltipPrimitive.Arrow, { width: 11, height: 5, className: "fill-gray-1000" })) : null] }) })));
10
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
11
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
12
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9vbHRpcC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9jb21wb25lbnRzL3VpL3Rvb2x0aXAudHN4Il0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVksQ0FBQzs7QUFFYixPQUFPLEtBQUssZ0JBQWdCLE1BQU0seUJBQXlCLENBQUM7QUFDNUQsT0FBTyxLQUFLLEtBQUssTUFBTSxPQUFPLENBQUM7QUFDL0IsT0FBTyxFQUFFLEVBQUUsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRXJDLE1BQU0sZUFBZSxHQUFHLGdCQUFnQixDQUFDLFFBQVEsQ0FBQztBQUVsRCxNQUFNLE9BQU8sR0FBRyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUM7QUFFdEMsTUFBTSxjQUFjLEdBQUcsZ0JBQWdCLENBQUMsT0FBTyxDQUFDO0FBRWhELE1BQU0sY0FBYyxHQUFHLEtBQUssQ0FBQyxVQUFVLENBTXJDLENBQ0UsRUFBRSxTQUFTLEVBQUUsVUFBVSxHQUFHLENBQUMsRUFBRSxTQUFTLEdBQUcsSUFBSSxFQUFFLFFBQVEsRUFBRSxHQUFHLEtBQUssRUFBRSxFQUNuRSxHQUFHLEVBQ0gsRUFBRSxDQUFDLENBQ0gsS0FBQyxnQkFBZ0IsQ0FBQyxNQUFNLGNBQ3RCLE1BQUMsZ0JBQWdCLENBQUMsT0FBTyxJQUN2QixHQUFHLEVBQUUsR0FBRyxFQUNSLFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFNBQVMsRUFBRSxFQUFFLENBQ1gsb0lBQW9JLEVBQ3BJLFNBQVMsQ0FDVixLQUNHLEtBQUssYUFFUixRQUFRLEVBQ1IsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUNYLEtBQUMsZ0JBQWdCLENBQUMsS0FBSyxJQUNyQixLQUFLLEVBQUUsRUFBRSxFQUNULE1BQU0sRUFBRSxDQUFDLEVBQ1QsU0FBUyxFQUFDLGdCQUFnQixHQUMxQixDQUNILENBQUMsQ0FBQyxDQUFDLElBQUksSUFDaUIsR0FDSCxDQUMzQixDQUNGLENBQUM7QUFDRixjQUFjLENBQUMsV0FBVyxHQUFHLGdCQUFnQixDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUM7QUFFbEUsT0FBTyxFQUFFLE9BQU8sRUFBRSxjQUFjLEVBQUUsY0FBYyxFQUFFLGVBQWUsRUFBRSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiJ3VzZSBjbGllbnQnO1xuXG5pbXBvcnQgKiBhcyBUb29sdGlwUHJpbWl0aXZlIGZyb20gJ0ByYWRpeC11aS9yZWFjdC10b29sdGlwJztcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0JztcbmltcG9ydCB7IGNuIH0gZnJvbSAnLi4vLi4vbGliL3V0aWxzJztcblxuY29uc3QgVG9vbHRpcFByb3ZpZGVyID0gVG9vbHRpcFByaW1pdGl2ZS5Qcm92aWRlcjtcblxuY29uc3QgVG9vbHRpcCA9IFRvb2x0aXBQcmltaXRpdmUuUm9vdDtcblxuY29uc3QgVG9vbHRpcFRyaWdnZXIgPSBUb29sdGlwUHJpbWl0aXZlLlRyaWdnZXI7XG5cbmNvbnN0IFRvb2x0aXBDb250ZW50ID0gUmVhY3QuZm9yd2FyZFJlZjxcbiAgUmVhY3QuRWxlbWVudFJlZjx0eXBlb2YgVG9vbHRpcFByaW1pdGl2ZS5Db250ZW50PixcbiAgUmVhY3QuQ29tcG9uZW50UHJvcHNXaXRob3V0UmVmPHR5cGVvZiBUb29sdGlwUHJpbWl0aXZlLkNvbnRlbnQ+ICYge1xuICAgIHNob3dBcnJvdz86IGJvb2xlYW47XG4gIH1cbj4oXG4gIChcbiAgICB7IGNsYXNzTmFtZSwgc2lkZU9mZnNldCA9IDgsIHNob3dBcnJvdyA9IHRydWUsIGNoaWxkcmVuLCAuLi5wcm9wcyB9LFxuICAgIHJlZlxuICApID0+IChcbiAgICA8VG9vbHRpcFByaW1pdGl2ZS5Qb3J0YWw+XG4gICAgICA8VG9vbHRpcFByaW1pdGl2ZS5Db250ZW50XG4gICAgICAgIHJlZj17cmVmfVxuICAgICAgICBzaWRlT2Zmc2V0PXtzaWRlT2Zmc2V0fVxuICAgICAgICBjbGFzc05hbWU9e2NuKFxuICAgICAgICAgICd6LVs5OTk5OV0gZmxleCBpdGVtcy1jZW50ZXIgZ2FwLTEgcm91bmRlZC1bMTBweF0gYmctZ3JheS0xMDAwIHB4LTIgcHktMS41ICF0ZXh0LWxhYmVsLTEzIHRleHQtYmFja2dyb3VuZC0xMDAgc2hhZG93LW1kIHNlbGVjdC1ub25lJyxcbiAgICAgICAgICBjbGFzc05hbWVcbiAgICAgICAgKX1cbiAgICAgICAgey4uLnByb3BzfVxuICAgICAgPlxuICAgICAgICB7Y2hpbGRyZW59XG4gICAgICAgIHtzaG93QXJyb3cgPyAoXG4gICAgICAgICAgPFRvb2x0aXBQcmltaXRpdmUuQXJyb3dcbiAgICAgICAgICAgIHdpZHRoPXsxMX1cbiAgICAgICAgICAgIGhlaWdodD17NX1cbiAgICAgICAgICAgIGNsYXNzTmFtZT1cImZpbGwtZ3JheS0xMDAwXCJcbiAgICAgICAgICAvPlxuICAgICAgICApIDogbnVsbH1cbiAgICAgIDwvVG9vbHRpcFByaW1pdGl2ZS5Db250ZW50PlxuICAgIDwvVG9vbHRpcFByaW1pdGl2ZS5Qb3J0YWw+XG4gIClcbik7XG5Ub29sdGlwQ29udGVudC5kaXNwbGF5TmFtZSA9IFRvb2x0aXBQcmltaXRpdmUuQ29udGVudC5kaXNwbGF5TmFtZTtcblxuZXhwb3J0IHsgVG9vbHRpcCwgVG9vbHRpcFRyaWdnZXIsIFRvb2x0aXBDb250ZW50LCBUb29sdGlwUHJvdmlkZXIgfTtcbiJdfQ==
@@ -0,0 +1,17 @@
1
+ import type { RefObject } from 'react';
2
+ interface UseIntersectionObserverArgs extends Omit<IntersectionObserverInit, 'root'> {
3
+ /**
4
+ * Scroll container the target is observed within. Defaults to the viewport.
5
+ * Passed as a ref so it resolves after commit (when the node exists).
6
+ */
7
+ rootRef?: RefObject<Element | null>;
8
+ freezeOnceVisible?: boolean;
9
+ }
10
+ /**
11
+ * Returns the latest IntersectionObserverEntry for `elementRef`.
12
+ * Ported from vercel/front's `@vercel/hooks` `useIntersectionObserver`, with
13
+ * `root` threaded as a ref so we can observe inside a custom scroll container.
14
+ */
15
+ export declare function useIntersectionObserver(elementRef: RefObject<Element | null>, { threshold, rootRef, rootMargin, freezeOnceVisible, }?: UseIntersectionObserverArgs): IntersectionObserverEntry | undefined;
16
+ export {};
17
+ //# sourceMappingURL=use-intersection-observer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-intersection-observer.d.ts","sourceRoot":"","sources":["../../src/hooks/use-intersection-observer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,UAAU,2BACR,SAAQ,IAAI,CAAC,wBAAwB,EAAE,MAAM,CAAC;IAC9C;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACpC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC,EACrC,EACE,SAAa,EACb,OAAO,EACP,UAAiB,EACjB,iBAAyB,GAC1B,GAAE,2BAAgC,GAClC,yBAAyB,GAAG,SAAS,CA4BvC"}
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+ import { useEffect, useState } from 'react';
3
+ /**
4
+ * Returns the latest IntersectionObserverEntry for `elementRef`.
5
+ * Ported from vercel/front's `@vercel/hooks` `useIntersectionObserver`, with
6
+ * `root` threaded as a ref so we can observe inside a custom scroll container.
7
+ */
8
+ export function useIntersectionObserver(elementRef, { threshold = 0, rootRef, rootMargin = '0%', freezeOnceVisible = false, } = {}) {
9
+ const [entry, setEntry] = useState();
10
+ const frozen = entry?.isIntersecting && freezeOnceVisible;
11
+ useEffect(() => {
12
+ const node = elementRef?.current;
13
+ const hasIOSupport = !!window.IntersectionObserver;
14
+ if (!hasIOSupport || frozen || !node)
15
+ return;
16
+ const updateEntry = ([entry]) => {
17
+ setEntry(entry);
18
+ };
19
+ const observer = new IntersectionObserver(updateEntry, {
20
+ threshold,
21
+ root: rootRef?.current ?? null,
22
+ rootMargin,
23
+ });
24
+ observer.observe(node);
25
+ return () => observer.disconnect();
26
+ }, [elementRef, threshold, rootRef, rootMargin, frozen]);
27
+ return entry;
28
+ }
29
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXNlLWludGVyc2VjdGlvbi1vYnNlcnZlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9ob29rcy91c2UtaW50ZXJzZWN0aW9uLW9ic2VydmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVksQ0FBQztBQUdiLE9BQU8sRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLE1BQU0sT0FBTyxDQUFDO0FBWTVDOzs7O0dBSUc7QUFDSCxNQUFNLFVBQVUsdUJBQXVCLENBQ3JDLFVBQXFDLEVBQ3JDLEVBQ0UsU0FBUyxHQUFHLENBQUMsRUFDYixPQUFPLEVBQ1AsVUFBVSxHQUFHLElBQUksRUFDakIsaUJBQWlCLEdBQUcsS0FBSyxNQUNNLEVBQUU7SUFFbkMsTUFBTSxDQUFDLEtBQUssRUFBRSxRQUFRLENBQUMsR0FBRyxRQUFRLEVBQTZCLENBQUM7SUFFaEUsTUFBTSxNQUFNLEdBQUcsS0FBSyxFQUFFLGNBQWMsSUFBSSxpQkFBaUIsQ0FBQztJQUUxRCxTQUFTLENBQUMsR0FBRyxFQUFFO1FBQ2IsTUFBTSxJQUFJLEdBQUcsVUFBVSxFQUFFLE9BQU8sQ0FBQztRQUVqQyxNQUFNLFlBQVksR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDLG9CQUFvQixDQUFDO1FBRW5ELElBQUksQ0FBQyxZQUFZLElBQUksTUFBTSxJQUFJLENBQUMsSUFBSTtZQUFFLE9BQU87UUFFN0MsTUFBTSxXQUFXLEdBQUcsQ0FBQyxDQUFDLEtBQUssQ0FBOEIsRUFBUSxFQUFFO1lBQ2pFLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUNsQixDQUFDLENBQUM7UUFFRixNQUFNLFFBQVEsR0FBRyxJQUFJLG9CQUFvQixDQUFDLFdBQVcsRUFBRTtZQUNyRCxTQUFTO1lBQ1QsSUFBSSxFQUFFLE9BQU8sRUFBRSxPQUFPLElBQUksSUFBSTtZQUM5QixVQUFVO1NBQ1gsQ0FBQyxDQUFDO1FBRUgsUUFBUSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUV2QixPQUFPLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxVQUFVLEVBQUUsQ0FBQztJQUNyQyxDQUFDLEVBQUUsQ0FBQyxVQUFVLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztJQUV6RCxPQUFPLEtBQUssQ0FBQztBQUNmLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIGNsaWVudCc7XG5cbmltcG9ydCB0eXBlIHsgUmVmT2JqZWN0IH0gZnJvbSAncmVhY3QnO1xuaW1wb3J0IHsgdXNlRWZmZWN0LCB1c2VTdGF0ZSB9IGZyb20gJ3JlYWN0JztcblxuaW50ZXJmYWNlIFVzZUludGVyc2VjdGlvbk9ic2VydmVyQXJnc1xuICBleHRlbmRzIE9taXQ8SW50ZXJzZWN0aW9uT2JzZXJ2ZXJJbml0LCAncm9vdCc+IHtcbiAgLyoqXG4gICAqIFNjcm9sbCBjb250YWluZXIgdGhlIHRhcmdldCBpcyBvYnNlcnZlZCB3aXRoaW4uIERlZmF1bHRzIHRvIHRoZSB2aWV3cG9ydC5cbiAgICogUGFzc2VkIGFzIGEgcmVmIHNvIGl0IHJlc29sdmVzIGFmdGVyIGNvbW1pdCAod2hlbiB0aGUgbm9kZSBleGlzdHMpLlxuICAgKi9cbiAgcm9vdFJlZj86IFJlZk9iamVjdDxFbGVtZW50IHwgbnVsbD47XG4gIGZyZWV6ZU9uY2VWaXNpYmxlPzogYm9vbGVhbjtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBsYXRlc3QgSW50ZXJzZWN0aW9uT2JzZXJ2ZXJFbnRyeSBmb3IgYGVsZW1lbnRSZWZgLlxuICogUG9ydGVkIGZyb20gdmVyY2VsL2Zyb250J3MgYEB2ZXJjZWwvaG9va3NgIGB1c2VJbnRlcnNlY3Rpb25PYnNlcnZlcmAsIHdpdGhcbiAqIGByb290YCB0aHJlYWRlZCBhcyBhIHJlZiBzbyB3ZSBjYW4gb2JzZXJ2ZSBpbnNpZGUgYSBjdXN0b20gc2Nyb2xsIGNvbnRhaW5lci5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHVzZUludGVyc2VjdGlvbk9ic2VydmVyKFxuICBlbGVtZW50UmVmOiBSZWZPYmplY3Q8RWxlbWVudCB8IG51bGw+LFxuICB7XG4gICAgdGhyZXNob2xkID0gMCxcbiAgICByb290UmVmLFxuICAgIHJvb3RNYXJnaW4gPSAnMCUnLFxuICAgIGZyZWV6ZU9uY2VWaXNpYmxlID0gZmFsc2UsXG4gIH06IFVzZUludGVyc2VjdGlvbk9ic2VydmVyQXJncyA9IHt9XG4pOiBJbnRlcnNlY3Rpb25PYnNlcnZlckVudHJ5IHwgdW5kZWZpbmVkIHtcbiAgY29uc3QgW2VudHJ5LCBzZXRFbnRyeV0gPSB1c2VTdGF0ZTxJbnRlcnNlY3Rpb25PYnNlcnZlckVudHJ5PigpO1xuXG4gIGNvbnN0IGZyb3plbiA9IGVudHJ5Py5pc0ludGVyc2VjdGluZyAmJiBmcmVlemVPbmNlVmlzaWJsZTtcblxuICB1c2VFZmZlY3QoKCkgPT4ge1xuICAgIGNvbnN0IG5vZGUgPSBlbGVtZW50UmVmPy5jdXJyZW50O1xuXG4gICAgY29uc3QgaGFzSU9TdXBwb3J0ID0gISF3aW5kb3cuSW50ZXJzZWN0aW9uT2JzZXJ2ZXI7XG5cbiAgICBpZiAoIWhhc0lPU3VwcG9ydCB8fCBmcm96ZW4gfHwgIW5vZGUpIHJldHVybjtcblxuICAgIGNvbnN0IHVwZGF0ZUVudHJ5ID0gKFtlbnRyeV06IEludGVyc2VjdGlvbk9ic2VydmVyRW50cnlbXSk6IHZvaWQgPT4ge1xuICAgICAgc2V0RW50cnkoZW50cnkpO1xuICAgIH07XG5cbiAgICBjb25zdCBvYnNlcnZlciA9IG5ldyBJbnRlcnNlY3Rpb25PYnNlcnZlcih1cGRhdGVFbnRyeSwge1xuICAgICAgdGhyZXNob2xkLFxuICAgICAgcm9vdDogcm9vdFJlZj8uY3VycmVudCA/PyBudWxsLFxuICAgICAgcm9vdE1hcmdpbixcbiAgICB9KTtcblxuICAgIG9ic2VydmVyLm9ic2VydmUobm9kZSk7XG5cbiAgICByZXR1cm4gKCkgPT4gb2JzZXJ2ZXIuZGlzY29ubmVjdCgpO1xuICB9LCBbZWxlbWVudFJlZiwgdGhyZXNob2xkLCByb290UmVmLCByb290TWFyZ2luLCBmcm96ZW5dKTtcblxuICByZXR1cm4gZW50cnk7XG59XG4iXX0=
@@ -0,0 +1,21 @@
1
+ import type { RefObject } from 'react';
2
+ interface UseLoadMoreOnScrollOptions {
3
+ hasMore: boolean;
4
+ isLoadingMore: boolean;
5
+ /** Scroll container the sentinel lives in. Defaults to the viewport. */
6
+ rootRef?: RefObject<Element | null>;
7
+ rootMargin?: string;
8
+ }
9
+ /**
10
+ * Triggers `loadMore` when a sentinel element scrolls into view.
11
+ * Returns a ref to attach to an invisible sentinel `<div>` placed after the
12
+ * list content. Ported from vercel/front's `@vercel/hooks`
13
+ * `useLoadMoreOnScroll`.
14
+ *
15
+ * Because it keys off `isIntersecting`/`isLoadingMore`, it self-continues:
16
+ * if the sentinel is still in view once a page finishes loading, the next
17
+ * page is requested automatically.
18
+ */
19
+ export declare function useLoadMoreOnScroll(loadMore: () => void, { hasMore, isLoadingMore, rootRef, rootMargin, }: UseLoadMoreOnScrollOptions): RefObject<HTMLDivElement | null>;
20
+ export {};
21
+ //# sourceMappingURL=use-load-more-on-scroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-load-more-on-scroll.d.ts","sourceRoot":"","sources":["../../src/hooks/use-load-more-on-scroll.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,UAAU,0BAA0B;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,wEAAwE;IACxE,OAAO,CAAC,EAAE,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,IAAI,EACpB,EACE,OAAO,EACP,aAAa,EACb,OAAO,EACP,UAAoB,GACrB,EAAE,0BAA0B,oCAiB9B"}
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+ import { useEffect, useRef } from 'react';
3
+ import { useIntersectionObserver } from './use-intersection-observer';
4
+ /**
5
+ * Triggers `loadMore` when a sentinel element scrolls into view.
6
+ * Returns a ref to attach to an invisible sentinel `<div>` placed after the
7
+ * list content. Ported from vercel/front's `@vercel/hooks`
8
+ * `useLoadMoreOnScroll`.
9
+ *
10
+ * Because it keys off `isIntersecting`/`isLoadingMore`, it self-continues:
11
+ * if the sentinel is still in view once a page finishes loading, the next
12
+ * page is requested automatically.
13
+ */
14
+ export function useLoadMoreOnScroll(loadMore, { hasMore, isLoadingMore, rootRef, rootMargin = '400px', }) {
15
+ const sentinelRef = useRef(null);
16
+ const entry = useIntersectionObserver(sentinelRef, { rootRef, rootMargin });
17
+ const loadMoreRef = useRef(loadMore);
18
+ useEffect(() => {
19
+ loadMoreRef.current = loadMore;
20
+ }, [loadMore]);
21
+ useEffect(() => {
22
+ if (entry?.isIntersecting && hasMore && !isLoadingMore) {
23
+ loadMoreRef.current();
24
+ }
25
+ }, [entry?.isIntersecting, hasMore, isLoadingMore]);
26
+ return sentinelRef;
27
+ }
28
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXNlLWxvYWQtbW9yZS1vbi1zY3JvbGwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaG9va3MvdXNlLWxvYWQtbW9yZS1vbi1zY3JvbGwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWSxDQUFDO0FBR2IsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsTUFBTSxPQUFPLENBQUM7QUFDMUMsT0FBTyxFQUFFLHVCQUF1QixFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFVdEU7Ozs7Ozs7OztHQVNHO0FBQ0gsTUFBTSxVQUFVLG1CQUFtQixDQUNqQyxRQUFvQixFQUNwQixFQUNFLE9BQU8sRUFDUCxhQUFhLEVBQ2IsT0FBTyxFQUNQLFVBQVUsR0FBRyxPQUFPLEdBQ087SUFFN0IsTUFBTSxXQUFXLEdBQUcsTUFBTSxDQUFpQixJQUFJLENBQUMsQ0FBQztJQUNqRCxNQUFNLEtBQUssR0FBRyx1QkFBdUIsQ0FBQyxXQUFXLEVBQUUsRUFBRSxPQUFPLEVBQUUsVUFBVSxFQUFFLENBQUMsQ0FBQztJQUU1RSxNQUFNLFdBQVcsR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDckMsU0FBUyxDQUFDLEdBQUcsRUFBRTtRQUNiLFdBQVcsQ0FBQyxPQUFPLEdBQUcsUUFBUSxDQUFDO0lBQ2pDLENBQUMsRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUM7SUFFZixTQUFTLENBQUMsR0FBRyxFQUFFO1FBQ2IsSUFBSSxLQUFLLEVBQUUsY0FBYyxJQUFJLE9BQU8sSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO1lBQ3ZELFdBQVcsQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUN4QixDQUFDO0lBQ0gsQ0FBQyxFQUFFLENBQUMsS0FBSyxFQUFFLGNBQWMsRUFBRSxPQUFPLEVBQUUsYUFBYSxDQUFDLENBQUMsQ0FBQztJQUVwRCxPQUFPLFdBQVcsQ0FBQztBQUNyQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiJ3VzZSBjbGllbnQnO1xuXG5pbXBvcnQgdHlwZSB7IFJlZk9iamVjdCB9IGZyb20gJ3JlYWN0JztcbmltcG9ydCB7IHVzZUVmZmVjdCwgdXNlUmVmIH0gZnJvbSAncmVhY3QnO1xuaW1wb3J0IHsgdXNlSW50ZXJzZWN0aW9uT2JzZXJ2ZXIgfSBmcm9tICcuL3VzZS1pbnRlcnNlY3Rpb24tb2JzZXJ2ZXInO1xuXG5pbnRlcmZhY2UgVXNlTG9hZE1vcmVPblNjcm9sbE9wdGlvbnMge1xuICBoYXNNb3JlOiBib29sZWFuO1xuICBpc0xvYWRpbmdNb3JlOiBib29sZWFuO1xuICAvKiogU2Nyb2xsIGNvbnRhaW5lciB0aGUgc2VudGluZWwgbGl2ZXMgaW4uIERlZmF1bHRzIHRvIHRoZSB2aWV3cG9ydC4gKi9cbiAgcm9vdFJlZj86IFJlZk9iamVjdDxFbGVtZW50IHwgbnVsbD47XG4gIHJvb3RNYXJnaW4/OiBzdHJpbmc7XG59XG5cbi8qKlxuICogVHJpZ2dlcnMgYGxvYWRNb3JlYCB3aGVuIGEgc2VudGluZWwgZWxlbWVudCBzY3JvbGxzIGludG8gdmlldy5cbiAqIFJldHVybnMgYSByZWYgdG8gYXR0YWNoIHRvIGFuIGludmlzaWJsZSBzZW50aW5lbCBgPGRpdj5gIHBsYWNlZCBhZnRlciB0aGVcbiAqIGxpc3QgY29udGVudC4gUG9ydGVkIGZyb20gdmVyY2VsL2Zyb250J3MgYEB2ZXJjZWwvaG9va3NgXG4gKiBgdXNlTG9hZE1vcmVPblNjcm9sbGAuXG4gKlxuICogQmVjYXVzZSBpdCBrZXlzIG9mZiBgaXNJbnRlcnNlY3RpbmdgL2Bpc0xvYWRpbmdNb3JlYCwgaXQgc2VsZi1jb250aW51ZXM6XG4gKiBpZiB0aGUgc2VudGluZWwgaXMgc3RpbGwgaW4gdmlldyBvbmNlIGEgcGFnZSBmaW5pc2hlcyBsb2FkaW5nLCB0aGUgbmV4dFxuICogcGFnZSBpcyByZXF1ZXN0ZWQgYXV0b21hdGljYWxseS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHVzZUxvYWRNb3JlT25TY3JvbGwoXG4gIGxvYWRNb3JlOiAoKSA9PiB2b2lkLFxuICB7XG4gICAgaGFzTW9yZSxcbiAgICBpc0xvYWRpbmdNb3JlLFxuICAgIHJvb3RSZWYsXG4gICAgcm9vdE1hcmdpbiA9ICc0MDBweCcsXG4gIH06IFVzZUxvYWRNb3JlT25TY3JvbGxPcHRpb25zXG4pIHtcbiAgY29uc3Qgc2VudGluZWxSZWYgPSB1c2VSZWY8SFRNTERpdkVsZW1lbnQ+KG51bGwpO1xuICBjb25zdCBlbnRyeSA9IHVzZUludGVyc2VjdGlvbk9ic2VydmVyKHNlbnRpbmVsUmVmLCB7IHJvb3RSZWYsIHJvb3RNYXJnaW4gfSk7XG5cbiAgY29uc3QgbG9hZE1vcmVSZWYgPSB1c2VSZWYobG9hZE1vcmUpO1xuICB1c2VFZmZlY3QoKCkgPT4ge1xuICAgIGxvYWRNb3JlUmVmLmN1cnJlbnQgPSBsb2FkTW9yZTtcbiAgfSwgW2xvYWRNb3JlXSk7XG5cbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICBpZiAoZW50cnk/LmlzSW50ZXJzZWN0aW5nICYmIGhhc01vcmUgJiYgIWlzTG9hZGluZ01vcmUpIHtcbiAgICAgIGxvYWRNb3JlUmVmLmN1cnJlbnQoKTtcbiAgICB9XG4gIH0sIFtlbnRyeT8uaXNJbnRlcnNlY3RpbmcsIGhhc01vcmUsIGlzTG9hZGluZ01vcmVdKTtcblxuICByZXR1cm4gc2VudGluZWxSZWY7XG59XG4iXX0=
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@workflow/web-shared",
3
3
  "description": "Shared components for Workflow Observability UI",
4
- "version": "5.0.0-beta.10",
4
+ "version": "5.0.0-beta.11",
5
5
  "private": false,
6
6
  "files": [
7
7
  "dist",
@@ -53,9 +53,9 @@
53
53
  "streamdown": "2.3.0",
54
54
  "tailwind-merge": "3.5.0",
55
55
  "tailwindcss": "4",
56
- "@workflow/core": "5.0.0-beta.10",
56
+ "@workflow/core": "5.0.0-beta.11",
57
57
  "@workflow/utils": "5.0.0-beta.3",
58
- "@workflow/world": "5.0.0-beta.5"
58
+ "@workflow/world": "5.0.0-beta.6"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@biomejs/biome": "^2.4.4",
@@ -38,3 +38,4 @@ export { MenuDropdown, type MenuDropdownOption } from './ui/menu-dropdown';
38
38
  export { Spinner } from './ui/spinner';
39
39
  export { WorkflowTraceViewer } from './workflow-trace-view';
40
40
  export { NewTraceViewer } from './trace-viewer-new';
41
+ export { TraceViewerSkeleton } from './new-trace-viewer/components/trace-viewer-skeleton';
@@ -1,4 +1,5 @@
1
1
  import { Circle } from 'lucide-react';
2
+ import { useRef } from 'react';
2
3
  import { cn } from '../../../lib/utils';
3
4
  import type { Span } from '../../trace-viewer/types';
4
5
  import { formatDuration } from '../../trace-viewer/util/timing';
@@ -11,6 +12,7 @@ import {
11
12
  import { isSpanDimmedBySearch, type SpanSearchResult } from '../search';
12
13
  import { getSpanDurationMs } from '../utils';
13
14
  import { MiddleTruncate } from './middle-truncate/middle-truncate';
15
+ import { ROW_HEIGHT_PX, useRowWindow } from './use-row-window';
14
16
 
15
17
  interface EventStyle {
16
18
  icon: React.ComponentType<{ className?: string }>;
@@ -103,23 +105,29 @@ const EventList = ({
103
105
  searchResult: SpanSearchResult;
104
106
  onSelectSpan: (spanId: string) => void;
105
107
  }) => {
108
+ const listRef = useRef<HTMLUListElement>(null);
109
+ const { start, end } = useRowWindow(listRef, spans.length, ROW_HEIGHT_PX);
110
+
106
111
  return (
107
112
  <ul
113
+ ref={listRef}
108
114
  id="event-list"
109
115
  role="tree"
110
116
  className="block min-h-0 overflow-visible divide-y divide-gray-alpha-400 border-b border-gray-alpha-400"
117
+ style={{
118
+ paddingTop: start * ROW_HEIGHT_PX,
119
+ paddingBottom: (spans.length - end) * ROW_HEIGHT_PX,
120
+ }}
111
121
  >
112
- {spans.map((span) => {
113
- return (
114
- <EventRow
115
- key={span.spanId}
116
- span={span}
117
- isSelected={span.spanId === activeSpanId}
118
- isDimmed={isSpanDimmedBySearch(span.spanId, searchResult)}
119
- onSelectSpan={onSelectSpan}
120
- />
121
- );
122
- })}
122
+ {spans.slice(start, end).map((span) => (
123
+ <EventRow
124
+ key={span.spanId}
125
+ span={span}
126
+ isSelected={span.spanId === activeSpanId}
127
+ isDimmed={isSpanDimmedBySearch(span.spanId, searchResult)}
128
+ onSelectSpan={onSelectSpan}
129
+ />
130
+ ))}
123
131
  </ul>
124
132
  );
125
133
  };
@@ -4,6 +4,7 @@ import {
4
4
  Children,
5
5
  type ReactNode,
6
6
  type PointerEvent as ReactPointerEvent,
7
+ type RefObject,
7
8
  useCallback,
8
9
  useEffect,
9
10
  useRef,
@@ -28,6 +29,7 @@ export interface SplitPaneProps {
28
29
  startHeader?: ReactNode;
29
30
  /** Fixed (non-scrolling) header rendered above the end pane. */
30
31
  endHeader?: ReactNode;
32
+ scrollContainerRef?: RefObject<HTMLDivElement | null>;
31
33
  }
32
34
 
33
35
  export function SplitPane({
@@ -36,6 +38,7 @@ export function SplitPane({
36
38
  defaultStartWidth = DEFAULT_START_PX,
37
39
  startHeader,
38
40
  endHeader,
41
+ scrollContainerRef,
39
42
  }: SplitPaneProps) {
40
43
  const parts = Children.toArray(children);
41
44
  if (parts.length !== 2) {
@@ -57,6 +60,16 @@ export function SplitPane({
57
60
  };
58
61
  }, []);
59
62
 
63
+ const setContainerRef = useCallback(
64
+ (node: HTMLDivElement | null) => {
65
+ containerRef.current = node;
66
+ if (scrollContainerRef) {
67
+ scrollContainerRef.current = node;
68
+ }
69
+ },
70
+ [scrollContainerRef]
71
+ );
72
+
60
73
  const clampPx = useCallback((px: number) => {
61
74
  const el = containerRef.current;
62
75
  if (!el) return px;
@@ -152,7 +165,7 @@ export function SplitPane({
152
165
  <div>{endHeader}</div>
153
166
  </div>
154
167
  <div
155
- ref={containerRef}
168
+ ref={setContainerRef}
156
169
  className={cn(
157
170
  'grid flex-1 min-h-0 overflow-x-hidden overflow-y-auto',
158
171
  isDragging && 'select-none'
@@ -169,7 +182,7 @@ export function SplitPane({
169
182
 
170
183
  return (
171
184
  <div
172
- ref={containerRef}
185
+ ref={setContainerRef}
173
186
  className={cn(
174
187
  'grid h-full min-h-0 content-start overflow-x-hidden overflow-y-auto',
175
188
  isDragging && 'select-none',
@@ -6,14 +6,15 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
6
6
  import { cn } from '../../../lib/utils';
7
7
  import type { Span } from '../../trace-viewer/types';
8
8
  import { formatDuration, getHighResInMs } from '../../trace-viewer/util/timing';
9
- import type { Segment, SegmentStatus, TimeMarker } from '../utils';
10
9
  import { isSpanDimmedBySearch, type SpanSearchResult } from '../search';
10
+ import type { Segment, SegmentStatus, TimeMarker } from '../utils';
11
11
  import {
12
12
  computeSpanGaps,
13
13
  computeSpanSegments,
14
14
  getResourceColor,
15
15
  getSpanDurationMs,
16
16
  } from '../utils';
17
+ import { ROW_HEIGHT_PX, useRowWindow } from './use-row-window';
17
18
 
18
19
  // ---------------------------------------------------------------------------
19
20
  // Constants
@@ -365,10 +366,6 @@ export { TimelineBar };
365
366
  // DeltaIndicator (Alt-key gap overlay)
366
367
  // ---------------------------------------------------------------------------
367
368
 
368
- // Row and indicator sizes are local to DeltaIndicator since it's the only
369
- // place that needs to compute Y positions from a row index. ROW_HEIGHT must
370
- // match the `h-10` (40px) row used in TimelineBar.
371
- const DELTA_ROW_HEIGHT_PX = 40;
372
369
  const DELTA_CAP_HEIGHT_PX = 8;
373
370
  // Vertical offset to sit the indicator inside the gap between row N and N+1,
374
371
  // aligned with where the bar starts in the next row (rows center a 24px bar
@@ -386,7 +383,7 @@ const DeltaIndicator = memo(function DeltaIndicator({
386
383
  label: string;
387
384
  rowIndex: number;
388
385
  }) {
389
- const centerY = DELTA_ROW_OFFSET_PX + (rowIndex + 1) * DELTA_ROW_HEIGHT_PX;
386
+ const centerY = DELTA_ROW_OFFSET_PX + (rowIndex + 1) * ROW_HEIGHT_PX;
390
387
 
391
388
  return (
392
389
  <div
@@ -473,6 +470,11 @@ export function Timeline({
473
470
  const [containerWidth, setContainerWidth] = useState(0);
474
471
  const viewDuration = viewEnd - viewStart;
475
472
  const timelineWidth = Math.max(0, containerWidth - TIMELINE_PADDING_PX * 2);
473
+ const { start, end } = useRowWindow(
474
+ containerRef,
475
+ spans.length,
476
+ ROW_HEIGHT_PX
477
+ );
476
478
 
477
479
  useEffect(() => {
478
480
  const el = containerRef.current;
@@ -490,7 +492,11 @@ export function Timeline({
490
492
  );
491
493
 
492
494
  return (
493
- <div ref={containerRef} className="relative h-full overflow-hidden">
495
+ <div
496
+ ref={containerRef}
497
+ className="relative h-full overflow-hidden"
498
+ style={{ minHeight: spans.length * ROW_HEIGHT_PX }}
499
+ >
494
500
  <div
495
501
  aria-hidden
496
502
  className="absolute inset-y-0 pointer-events-none"
@@ -518,18 +524,20 @@ export function Timeline({
518
524
  />
519
525
  </div>
520
526
  )}
521
- {spans.map((span) => (
522
- <TimelineBar
523
- key={span.spanId}
524
- span={span}
525
- viewStart={viewStart}
526
- viewDuration={viewDuration}
527
- containerWidth={timelineWidth}
528
- isSelected={selectedId === span.spanId}
529
- isDimmed={isSpanDimmedBySearch(span.spanId, searchResult)}
530
- onSelect={onSelect}
531
- />
532
- ))}
527
+ <div style={{ transform: `translateY(${start * ROW_HEIGHT_PX}px)` }}>
528
+ {spans.slice(start, end).map((span) => (
529
+ <TimelineBar
530
+ key={span.spanId}
531
+ span={span}
532
+ viewStart={viewStart}
533
+ viewDuration={viewDuration}
534
+ containerWidth={timelineWidth}
535
+ isSelected={selectedId === span.spanId}
536
+ isDimmed={isSpanDimmedBySearch(span.spanId, searchResult)}
537
+ onSelect={onSelect}
538
+ />
539
+ ))}
540
+ </div>
533
541
  {altHeld && (
534
542
  <div
535
543
  aria-hidden
@@ -0,0 +1,94 @@
1
+ import { Skeleton } from '../../ui/skeleton';
2
+
3
+ const COL_TEMPLATE = '340px 1px minmax(50px, 1fr)';
4
+
5
+ const ROWS: { id: string; name: number; off: number; bar: number }[] = [
6
+ { id: 'r0', name: 62, off: 0, bar: 72 },
7
+ { id: 'r1', name: 78, off: 6, bar: 48 },
8
+ { id: 'r2', name: 50, off: 10, bar: 55 },
9
+ { id: 'r3', name: 84, off: 18, bar: 30 },
10
+ { id: 'r4', name: 45, off: 18, bar: 42 },
11
+ { id: 'r5', name: 66, off: 34, bar: 38 },
12
+ { id: 'r6', name: 55, off: 41, bar: 25 },
13
+ { id: 'r7', name: 40, off: 50, bar: 30 },
14
+ ];
15
+
16
+ const HEADER_MARKERS = ['m0', 'm1', 'm2', 'm3'];
17
+
18
+ const HeaderDivider = () => (
19
+ <div className="flex justify-center">
20
+ <span aria-hidden className="h-full w-px bg-gray-alpha-400" />
21
+ </div>
22
+ );
23
+
24
+ export function TraceViewerSkeleton() {
25
+ return (
26
+ <output
27
+ aria-busy="true"
28
+ aria-label="Loading trace"
29
+ className="flex flex-col w-full h-full min-h-0 bg-background-100"
30
+ >
31
+ <span className="sr-only">Loading trace…</span>
32
+
33
+ {/* Header row: search header | divider | timeline header */}
34
+ <div
35
+ className="shrink-0 grid"
36
+ style={{ gridTemplateColumns: COL_TEMPLATE }}
37
+ >
38
+ <div className="h-10 min-h-10 flex items-center border-b border-gray-alpha-400 pl-4 pr-2 gap-1.5">
39
+ <Skeleton className="w-3.5 h-3.5 shrink-0 rounded-sm" />
40
+ <Skeleton className="h-3.5 w-40" />
41
+ </div>
42
+ <HeaderDivider />
43
+ <div className="h-10 min-h-10 flex items-end border-b border-gray-alpha-400 px-4 pb-1 gap-2">
44
+ <div className="relative flex-1 flex items-end justify-between">
45
+ {HEADER_MARKERS.map((id) => (
46
+ <Skeleton key={id} className="h-3.5 w-9" />
47
+ ))}
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ {/* Content row: event list | gutter | timeline */}
53
+ <div
54
+ className="grid flex-1 min-h-0 overflow-hidden"
55
+ style={{ gridTemplateColumns: COL_TEMPLATE }}
56
+ >
57
+ {/* Sidebar event rows */}
58
+ <div className="block overflow-visible">
59
+ <ul className="block divide-y divide-gray-alpha-400 border-b border-gray-alpha-400">
60
+ {ROWS.map((row) => (
61
+ <li key={row.id} className="h-10 flex items-center pl-4 pr-2">
62
+ <div className="flex min-w-0 flex-1 items-center gap-2">
63
+ <Skeleton className="w-4 h-4 shrink-0 rounded-sm" />
64
+ <Skeleton
65
+ className="h-3.5"
66
+ style={{ width: `${row.name}%` }}
67
+ />
68
+ </div>
69
+ <Skeleton className="ml-2 h-3.5 w-10 shrink-0" />
70
+ </li>
71
+ ))}
72
+ </ul>
73
+ </div>
74
+
75
+ {/* Gutter */}
76
+ <span aria-hidden className="h-full w-px bg-gray-alpha-400" />
77
+
78
+ {/* Timeline bars */}
79
+ <div className="relative min-h-0">
80
+ {ROWS.map((row) => (
81
+ <div key={row.id} className="relative h-10">
82
+ <div className="absolute inset-x-4 inset-y-0">
83
+ <Skeleton
84
+ className="absolute top-1/2 -translate-y-1/2 h-6 rounded-[0.25rem]"
85
+ style={{ left: `${row.off}%`, width: `${row.bar}%` }}
86
+ />
87
+ </div>
88
+ </div>
89
+ ))}
90
+ </div>
91
+ </div>
92
+ </output>
93
+ );
94
+ }