@bundle-stats/ui 4.16.0-beta.0 → 4.16.0-beta.10

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 (174) hide show
  1. package/lib/assets/icons.svg +6 -1
  2. package/lib/assets/icons.svg.js +3 -0
  3. package/lib/assets/icons.svg.js.map +1 -1
  4. package/lib/components/bundle-assets/bundle-assets.js +10 -3
  5. package/lib/components/bundle-assets/bundle-assets.js.map +1 -1
  6. package/lib/components/bundle-assets-totals/bundle-assets-totals.js +21 -9
  7. package/lib/components/bundle-assets-totals/bundle-assets-totals.js.map +1 -1
  8. package/lib/components/bundle-modules/bundle-modules.js +9 -2
  9. package/lib/components/bundle-modules/bundle-modules.js.map +1 -1
  10. package/lib/components/bundle-packages/bundle-packages.js +9 -2
  11. package/lib/components/bundle-packages/bundle-packages.js.map +1 -1
  12. package/lib/components/entry-info/entry-info.js +2 -2
  13. package/lib/components/entry-info/entry-info.js.map +1 -1
  14. package/lib/components/insights/insights.module.css +1 -0
  15. package/lib/components/metrics-table-export/index.js +18 -0
  16. package/lib/components/metrics-table-export/index.js.map +1 -0
  17. package/lib/components/metrics-table-export/metrics-table-export.js +82 -0
  18. package/lib/components/metrics-table-export/metrics-table-export.js.map +1 -0
  19. package/lib/components/metrics-table-options/index.js.map +1 -1
  20. package/lib/components/metrics-table-options/metrics-table-options.js +7 -15
  21. package/lib/components/metrics-table-options/metrics-table-options.js.map +1 -1
  22. package/lib/components/preview-source/index.js +18 -0
  23. package/lib/components/preview-source/index.js.map +1 -0
  24. package/lib/components/preview-source/preview-source.js +28 -0
  25. package/lib/components/preview-source/preview-source.js.map +1 -0
  26. package/lib/components/preview-source/preview-source.module.css +40 -0
  27. package/lib/css/variables.css +5 -0
  28. package/lib/i18n.js +3 -0
  29. package/lib/i18n.js.map +1 -1
  30. package/lib/ui/button/button.js +2 -1
  31. package/lib/ui/button/button.js.map +1 -1
  32. package/lib/ui/copy-to-clipboard/copy-to-clipboard.js +2 -6
  33. package/lib/ui/copy-to-clipboard/copy-to-clipboard.js.map +1 -1
  34. package/lib/ui/copy-to-clipboard/copy-to-clipboard.module.css +5 -48
  35. package/lib/ui/dialog/dialog.js +27 -0
  36. package/lib/ui/dialog/dialog.js.map +1 -0
  37. package/lib/ui/dialog/dialog.module.css +89 -0
  38. package/lib/ui/dialog/index.js +18 -0
  39. package/lib/ui/dialog/index.js.map +1 -0
  40. package/lib/ui/icon/icon.js +1 -0
  41. package/lib/ui/icon/icon.js.map +1 -1
  42. package/lib/ui/index.js +7 -1
  43. package/lib/ui/index.js.map +1 -1
  44. package/lib/ui/markdown/index.js +18 -0
  45. package/lib/ui/markdown/index.js.map +1 -0
  46. package/lib/ui/markdown/markdown.js +40 -0
  47. package/lib/ui/markdown/markdown.js.map +1 -0
  48. package/lib/ui/markdown/markdown.module.css +7 -0
  49. package/lib/ui/skeleton/skeleton.module.css +3 -3
  50. package/lib/ui/tabs/tabs.js +2 -2
  51. package/lib/ui/tabs/tabs.js.map +1 -1
  52. package/lib/ui/tabs/tabs.module.css +1 -0
  53. package/lib/ui/textarea/index.js +18 -0
  54. package/lib/ui/textarea/index.js.map +1 -0
  55. package/lib/ui/textarea/textarea.js +16 -0
  56. package/lib/ui/textarea/textarea.js.map +1 -0
  57. package/lib/ui/textarea/textarea.module.css +57 -0
  58. package/lib-esm/assets/icons.svg +6 -1
  59. package/lib-esm/assets/icons.svg.js +3 -0
  60. package/lib-esm/assets/icons.svg.js.map +1 -1
  61. package/lib-esm/components/bundle-assets/bundle-assets.js +10 -3
  62. package/lib-esm/components/bundle-assets/bundle-assets.js.map +1 -1
  63. package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.js +21 -9
  64. package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.js.map +1 -1
  65. package/lib-esm/components/bundle-modules/bundle-modules.js +9 -2
  66. package/lib-esm/components/bundle-modules/bundle-modules.js.map +1 -1
  67. package/lib-esm/components/bundle-packages/bundle-packages.js +9 -2
  68. package/lib-esm/components/bundle-packages/bundle-packages.js.map +1 -1
  69. package/lib-esm/components/entry-info/entry-info.js +2 -2
  70. package/lib-esm/components/entry-info/entry-info.js.map +1 -1
  71. package/lib-esm/components/insights/insights.module.css +1 -0
  72. package/lib-esm/components/metrics-table-export/index.js +2 -0
  73. package/lib-esm/components/metrics-table-export/index.js.map +1 -0
  74. package/lib-esm/components/metrics-table-export/metrics-table-export.js +60 -0
  75. package/lib-esm/components/metrics-table-export/metrics-table-export.js.map +1 -0
  76. package/lib-esm/components/metrics-table-options/index.js.map +1 -1
  77. package/lib-esm/components/metrics-table-options/metrics-table-options.js +7 -15
  78. package/lib-esm/components/metrics-table-options/metrics-table-options.js.map +1 -1
  79. package/lib-esm/components/preview-source/index.js +2 -0
  80. package/lib-esm/components/preview-source/index.js.map +1 -0
  81. package/lib-esm/components/preview-source/preview-source.js +21 -0
  82. package/lib-esm/components/preview-source/preview-source.js.map +1 -0
  83. package/lib-esm/components/preview-source/preview-source.module.css +40 -0
  84. package/lib-esm/css/variables.css +5 -0
  85. package/lib-esm/i18n.js +3 -0
  86. package/lib-esm/i18n.js.map +1 -1
  87. package/lib-esm/ui/button/button.js +1 -0
  88. package/lib-esm/ui/button/button.js.map +1 -1
  89. package/lib-esm/ui/copy-to-clipboard/copy-to-clipboard.js +2 -6
  90. package/lib-esm/ui/copy-to-clipboard/copy-to-clipboard.js.map +1 -1
  91. package/lib-esm/ui/copy-to-clipboard/copy-to-clipboard.module.css +5 -48
  92. package/lib-esm/ui/dialog/dialog.js +19 -0
  93. package/lib-esm/ui/dialog/dialog.js.map +1 -0
  94. package/lib-esm/ui/dialog/dialog.module.css +89 -0
  95. package/lib-esm/ui/dialog/index.js +2 -0
  96. package/lib-esm/ui/dialog/index.js.map +1 -0
  97. package/lib-esm/ui/icon/icon.js +1 -0
  98. package/lib-esm/ui/icon/icon.js.map +1 -1
  99. package/lib-esm/ui/index.js +3 -0
  100. package/lib-esm/ui/index.js.map +1 -1
  101. package/lib-esm/ui/markdown/index.js +2 -0
  102. package/lib-esm/ui/markdown/index.js.map +1 -0
  103. package/lib-esm/ui/markdown/markdown.js +10 -0
  104. package/lib-esm/ui/markdown/markdown.js.map +1 -0
  105. package/lib-esm/ui/markdown/markdown.module.css +7 -0
  106. package/lib-esm/ui/skeleton/skeleton.module.css +3 -3
  107. package/lib-esm/ui/tabs/tabs.js +2 -2
  108. package/lib-esm/ui/tabs/tabs.js.map +1 -1
  109. package/lib-esm/ui/tabs/tabs.module.css +1 -0
  110. package/lib-esm/ui/textarea/index.js +2 -0
  111. package/lib-esm/ui/textarea/index.js.map +1 -0
  112. package/lib-esm/ui/textarea/textarea.js +9 -0
  113. package/lib-esm/ui/textarea/textarea.js.map +1 -0
  114. package/lib-esm/ui/textarea/textarea.module.css +57 -0
  115. package/package.json +14 -13
  116. package/src/assets/icons/download.svg +4 -0
  117. package/src/assets/icons.svg +6 -1
  118. package/src/assets/icons.svg.jsx +9 -0
  119. package/src/components/bundle-assets/bundle-assets.jsx +20 -7
  120. package/src/components/bundle-assets-totals/bundle-assets-totals.jsx +54 -35
  121. package/src/components/bundle-modules/bundle-modules.tsx +15 -2
  122. package/src/components/bundle-packages/bundle-packages.jsx +15 -2
  123. package/src/components/entry-info/entry-info.tsx +2 -2
  124. package/src/components/insights/insights.module.css +1 -0
  125. package/src/components/metrics-table-export/index.ts +1 -0
  126. package/src/components/metrics-table-export/metrics-table-export.stories.tsx +30 -0
  127. package/src/components/metrics-table-export/metrics-table-export.tsx +102 -0
  128. package/src/components/metrics-table-options/metrics-table-options.stories.tsx +22 -0
  129. package/src/components/metrics-table-options/metrics-table-options.tsx +25 -0
  130. package/src/components/preview-source/index.ts +1 -0
  131. package/src/components/preview-source/preview-source.module.css +40 -0
  132. package/src/components/preview-source/preview-source.stories.tsx +53 -0
  133. package/src/components/preview-source/preview-source.tsx +56 -0
  134. package/src/css/variables.css +5 -0
  135. package/src/i18n.js +4 -0
  136. package/src/ui/button/button.tsx +2 -1
  137. package/src/ui/copy-to-clipboard/copy-to-clipboard.module.css +5 -48
  138. package/src/ui/copy-to-clipboard/copy-to-clipboard.tsx +4 -8
  139. package/src/ui/dialog/dialog.module.css +89 -0
  140. package/src/ui/dialog/dialog.stories.tsx +67 -0
  141. package/src/ui/dialog/dialog.tsx +62 -0
  142. package/src/ui/dialog/index.ts +1 -0
  143. package/src/ui/icon/icon.tsx +1 -0
  144. package/src/ui/index.js +3 -0
  145. package/src/ui/markdown/index.ts +1 -0
  146. package/src/ui/markdown/markdown.module.css +7 -0
  147. package/src/ui/markdown/markdown.stories.tsx +29 -0
  148. package/src/ui/markdown/markdown.tsx +24 -0
  149. package/src/ui/skeleton/skeleton.module.css +3 -3
  150. package/src/ui/tabs/tabs.jsx +2 -2
  151. package/src/ui/tabs/tabs.module.css +1 -0
  152. package/src/ui/textarea/index.ts +1 -0
  153. package/src/ui/textarea/textarea.module.css +57 -0
  154. package/src/ui/textarea/textarea.stories.tsx +37 -0
  155. package/src/ui/textarea/textarea.tsx +17 -0
  156. package/types/components/metrics-table-export/index.d.ts +1 -0
  157. package/types/components/metrics-table-export/metrics-table-export.d.ts +10 -0
  158. package/types/components/metrics-table-options/index.d.ts +1 -1
  159. package/types/components/metrics-table-options/metrics-table-options.d.ts +9 -17
  160. package/types/components/preview-source/index.d.ts +1 -0
  161. package/types/components/preview-source/preview-source.d.ts +9 -0
  162. package/types/i18n.d.ts +3 -0
  163. package/types/ui/button/button.d.ts +26 -0
  164. package/types/ui/dialog/dialog.d.ts +13 -0
  165. package/types/ui/dialog/index.d.ts +1 -0
  166. package/types/ui/icon/icon.d.ts +2 -0
  167. package/types/ui/index.d.ts +3 -0
  168. package/types/ui/markdown/index.d.ts +1 -0
  169. package/types/ui/markdown/markdown.d.ts +7 -0
  170. package/types/ui/tabs/tabs.d.ts +3 -3
  171. package/types/ui/textarea/index.d.ts +1 -0
  172. package/types/ui/textarea/textarea.d.ts +6 -0
  173. package/src/components/metrics-table-options/metrics-table-options.jsx +0 -30
  174. /package/src/components/metrics-table-options/{index.js → index.ts} +0 -0
@@ -0,0 +1,25 @@
1
+ import type { ComponentProps } from 'react';
2
+ import React from 'react';
3
+
4
+ import I18N from '../../i18n';
5
+ import { Dropdown, DropdownItem } from '../../ui/dropdown';
6
+ import { Separator } from '../../layout/separator';
7
+
8
+ type MetricsTableOptionsProps = {
9
+ onViewAllClick?: () => void;
10
+ onResetClick?: () => void;
11
+ onExportClick?: () => void;
12
+ } & ComponentProps<typeof Dropdown>;
13
+
14
+ export const MetricsTableOptions = (props: MetricsTableOptionsProps) => {
15
+ const { onViewAllClick, onResetClick, onExportClick, ...restProps } = props;
16
+
17
+ return (
18
+ <Dropdown glyph="more-vertical" {...restProps}>
19
+ {onResetClick && <DropdownItem onClick={onResetClick}>{I18N.RESET_FILTERS}</DropdownItem>}
20
+ {onViewAllClick && <DropdownItem onClick={onViewAllClick}>{I18N.VIEW_ALL}</DropdownItem>}
21
+ {((onResetClick || onViewAllClick) && onExportClick) && <Separator />}
22
+ {onExportClick && <DropdownItem onClick={onExportClick}>{I18N.EXPORT}</DropdownItem>}
23
+ </Dropdown>
24
+ );
25
+ };
@@ -0,0 +1 @@
1
+ export * from './preview-source';
@@ -0,0 +1,40 @@
1
+ .root {
2
+ position: relative;
3
+ }
4
+
5
+ .toolbar {
6
+ position: absolute;
7
+ z-index: 2;
8
+ top: var(--space-xsmall);
9
+ right: var(--space-xsmall);
10
+ background: var(--color-background);
11
+ border-radius: var(--radius-medium);
12
+ }
13
+
14
+ /** fix control group with nested components */
15
+ .downloadTooltip > a {
16
+ border-top-left-radius: inherit;
17
+ border-bottom-left-radius: inherit;
18
+ }
19
+
20
+ .downloadTooltip > a:hover,
21
+ .downloadTooltip > a:active,
22
+ .downloadTooltip > a:focus {
23
+ box-shadow: none;
24
+ }
25
+
26
+ /** Source content */
27
+ .content {
28
+ position: relative;
29
+ z-index: 1;
30
+ height: 100%;
31
+ background: var(--color-highlight);
32
+ color: var(--color-text);
33
+ font-family: var(--font-family-fixed);
34
+ }
35
+
36
+ /** Empty / loading state */
37
+ .isEmpty .content {
38
+ pointer-events: none;
39
+ background: var(--color-highlight);
40
+ }
@@ -0,0 +1,53 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import { getWrapperDecorator } from '../../stories';
4
+ import { PreviewSource } from '.';
5
+
6
+ const meta: Meta<typeof PreviewSource> = {
7
+ title: 'Components/PreviewSource',
8
+ component: PreviewSource,
9
+ decorators: [getWrapperDecorator()],
10
+ };
11
+
12
+ export default meta;
13
+
14
+ type Story = StoryObj<typeof meta>;
15
+
16
+ const SOURCE = JSON.stringify(
17
+ [
18
+ {
19
+ label: 'Hey',
20
+ runs: [
21
+ { value: 1, name: 'a' },
22
+ { value: 2, name: 'b' },
23
+ ],
24
+ },
25
+ ],
26
+ null,
27
+ 2,
28
+ );
29
+
30
+ export const Default: Story = {
31
+ args: {
32
+ source: JSON.stringify(
33
+ [
34
+ {
35
+ label: 'Hey',
36
+ runs: [
37
+ { value: 1, name: 'a' },
38
+ { value: 2, name: 'b' },
39
+ ],
40
+ },
41
+ ],
42
+ null,
43
+ 2,
44
+ ),
45
+ download: 'bundle-stats--assets.json',
46
+ },
47
+ };
48
+
49
+ export const Empty: Story = {
50
+ args: {
51
+ download: 'bundle-stats--assets.json',
52
+ },
53
+ };
@@ -0,0 +1,56 @@
1
+ import type { ComponentProps } from 'react';
2
+ import React from 'react';
3
+ import cx from 'classnames';
4
+
5
+ import { Button, BUTTON_GLYPHS } from '../../ui/button';
6
+ import { CopyToClipboard } from '../../ui/copy-to-clipboard';
7
+ import { ControlGroup } from '../../ui/control-group';
8
+ import { Tooltip } from '../../ui/tooltip';
9
+ import { Textarea } from '../../ui/textarea';
10
+ import css from './preview-source.module.css';
11
+
12
+ const DEFAULT_ROWS = 10;
13
+
14
+ type PreviewSourceProps = {
15
+ source?: string;
16
+ /**
17
+ * Textarea rows
18
+ */
19
+ rows?: number;
20
+ /**
21
+ * Link download attribute
22
+ * @example
23
+ * bundle-stats--assets.json
24
+ */
25
+ download?: string;
26
+ } & ComponentProps<'div'>;
27
+
28
+ export const PreviewSource = (props: PreviewSourceProps) => {
29
+ const { className = '', source, rows = DEFAULT_ROWS, download = false, ...restProps } = props;
30
+
31
+ const isEmpty = !source;
32
+ const rootClassName = cx(css.root, className, isEmpty && css.isEmpty);
33
+
34
+ return (
35
+ <div className={rootClassName} {...restProps}>
36
+ {source && (
37
+ <ControlGroup className={css.toolbar}>
38
+ <CopyToClipboard outline size="small" text={source} />
39
+ {download && (
40
+ <Tooltip title={`Download ${download}`} className={css.downloadTooltip}>
41
+ <Button
42
+ outline
43
+ rightGlyph={BUTTON_GLYPHS.DOWNLOAD}
44
+ size="small"
45
+ as="a"
46
+ href={`data:application/json,${encodeURIComponent(source)}`}
47
+ download={download}
48
+ />
49
+ </Tooltip>
50
+ )}
51
+ </ControlGroup>
52
+ )}
53
+ <Textarea readOnly rows={rows} value={source} className={css.content} />
54
+ </div>
55
+ );
56
+ };
@@ -176,6 +176,7 @@
176
176
  --color-outline-warning: var(--color-yellow-200);
177
177
  --color-outline-danger: var(--color-red-100);
178
178
 
179
+ --color-backdrop: rgba(0, 0, 0, 0.05);
179
180
  --color-shadow: rgba(0, 0, 0, 0.2);
180
181
 
181
182
  --transition-duration-in: 0.2s;
@@ -200,6 +201,10 @@
200
201
 
201
202
  --outline-hover: 0 0 0 var(--space-xxxsmall) var(--color-highlight);
202
203
 
204
+ --skeleton-inline-background: linear-gradient(transparent 10%, currentColor 10.1%, currentColor 89.9%, transparent 90%);
205
+ --skeleton-block-background: currentColor;
206
+ --skeleton-opacity: 0.06;
207
+
203
208
  /** Layout */
204
209
  --max-width: 88rem;
205
210
  --entry-info-width: 64rem;
package/src/i18n.js CHANGED
@@ -21,6 +21,10 @@ export default {
21
21
 
22
22
  RESET_FILTERS: 'Reset filters',
23
23
  VIEW_ALL: 'View all entries',
24
+ EXPORT: 'Export',
25
+
26
+ COPY_TO_CLIPBOARD: 'Copy to clipboard',
27
+ COPY_TO_CLIPBOARD_DONE: 'Copied to clipboard',
24
28
 
25
29
  GITHUB_LINK_TITLE: 'View BundleStats project on GitHub',
26
30
  REPORT_ISSUE: 'Report issue',
@@ -1,5 +1,4 @@
1
1
  import React from 'react';
2
-
3
2
  import cx from 'classnames';
4
3
  import { Button as ButtonBaseComponent } from 'ariakit/button';
5
4
 
@@ -21,6 +20,8 @@ export const BUTTON_KIND = {
21
20
  SUCCES: 'success',
22
21
  } as const;
23
22
 
23
+ export const BUTTON_GLYPHS = BaseIcon.ICONS;
24
+
24
25
  type Size = (typeof BUTTON_SIZE)[keyof typeof BUTTON_SIZE];
25
26
  type Kind = (typeof BUTTON_KIND)[keyof typeof BUTTON_KIND];
26
27
 
@@ -1,58 +1,15 @@
1
1
  .root {
2
- padding: var(--space-xxxsmall);
3
- }
4
-
5
- .wrapper {
6
- display: flex;
7
- gap: var(--space-xxsmall);
8
- align-items: center;
9
- }
10
-
11
- .content {
12
- flex: 1 1 auto;
13
2
  white-space: nowrap;
14
- line-height: 1.1;
15
- }
16
-
17
- .icon {
18
- flex: 0 0 auto;
19
- position: relative;
20
- width: var(--space-small);
21
- height: var(--space-small);
22
- }
23
-
24
- .iconCopy,
25
- .iconDone {
26
- position: absolute;
27
- top: 0;
28
- left: 0;
29
- }
30
-
31
- .iconCopy {
32
- color: var(--color-text-ultra-light);
33
- }
34
-
35
- .iconDone {
36
- color: var(--color-success);
3
+ /** override tooltip styles */
4
+ color: var(--color-text-ultra-light) !important;
37
5
  }
38
6
 
39
7
  /** done state */
40
- .iconCopy {
41
- opacity: 1;
42
- transition: var(--ui-transition-out);
43
- }
44
-
45
- .iconDone {
46
- opacity: 0;
8
+ .root svg {
47
9
  transition: var(--ui-transition-out);
48
10
  }
49
11
 
50
- .done .iconCopy {
51
- opacity: 0;
52
- transition: var(--ui-transition-in);
53
- }
54
-
55
- .done .iconDone {
56
- opacity: 1;
12
+ .done svg {
13
+ color: var(--color-success);
57
14
  transition: var(--ui-transition-in);
58
15
  }
@@ -3,6 +3,7 @@ import React, { useCallback, useState } from 'react';
3
3
  import cx from 'classnames';
4
4
  import { useCopyToClipboard } from 'react-use';
5
5
 
6
+ import I18N from '../../i18n';
6
7
  import { Button } from '../button';
7
8
  import { Icon } from '../icon';
8
9
  import { Tooltip } from '../tooltip';
@@ -38,20 +39,15 @@ export const CopyToClipboard = (props: CopyToClipboardProps) => {
38
39
 
39
40
  return (
40
41
  <Tooltip
41
- title={doneTimeout ? 'Copied' : 'Copy to clipboard'}
42
+ title={doneTimeout ? I18N.COPY_TO_CLIPBOARD_DONE : I18N.COPY_TO_CLIPBOARD}
42
43
  as={Button}
43
44
  type="button"
45
+ rightGlyph={doneTimeout ? Icon.ICONS.CLIPBOARD_CHECK : Icon.ICONS.CLIPBOARD}
44
46
  onClick={handleOnClick}
45
47
  className={rootClassName}
46
48
  {...restProps}
47
49
  >
48
- <span className={css.wrapper}>
49
- {children && <span className={css.content}>{children}</span>}
50
- <span className={css.icon}>
51
- <Icon className={css.iconCopy} glyph={Icon.ICONS.CLIPBOARD} />
52
- <Icon className={css.iconDone} glyph={Icon.ICONS.CLIPBOARD_CHECK} />
53
- </span>
54
- </span>
50
+ {children}
55
51
  </Tooltip>
56
52
  );
57
53
  };
@@ -0,0 +1,89 @@
1
+ .backdrop {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ z-index: 99999 !important;
6
+ }
7
+
8
+ .backdrop::before {
9
+ content: ' ';
10
+ position: absolute;
11
+ left: 0;
12
+ top: 0;
13
+ width: 100%;
14
+ height: 100%;
15
+ background: var(--color-backdrop);
16
+ filter:
17
+ }
18
+
19
+ .dialog {
20
+ --dialog-header-height: var(--space-medium);
21
+ --dialog-vspace: var(--space-xsmall);
22
+ --dialog-padding: var(--space-xsmall);
23
+
24
+ width: calc(100vw - 2 * var(--dialog-padding));
25
+ position: relative;
26
+ border-radius: var(--radius-medium);
27
+ padding: var(--dialog-padding);
28
+ background: var(--color-background);
29
+ box-shadow: 0 0 var(--space-medium) var(--color-shadow);
30
+ }
31
+
32
+ .header {
33
+ min-height: var(--dialog-header-height);
34
+ }
35
+
36
+ .header:empty {
37
+ display: none;
38
+ }
39
+
40
+ .headerClose {
41
+ position: absolute;
42
+ right: var(--space-xxsmall);
43
+ top: var(--space-xxsmall);
44
+ padding: var(--space-xxxsmall);
45
+ flex: 0 0 auto;
46
+ color: var(--color-outline-dark);
47
+ }
48
+
49
+ .headerClose:hover,
50
+ .headerClose:focus,
51
+ .headerClose:active {
52
+ color: var(--color-text-ultra-light);
53
+ }
54
+
55
+ .content {
56
+ width: 100%;
57
+ max-height: calc(100vh - 2 * var(--space-medium) - 2 * var(--dialog-padding) - var(--dialog-header-height) - var(--dialog-vspace));
58
+ padding-bottom: 0.3em; /* extra space to compensate for the title line-height and allow to show focus state correctly on buttons */
59
+ overflow: auto;
60
+ }
61
+
62
+ /** Width variation */
63
+ .width--narrow {
64
+ max-width: 360px;
65
+ }
66
+
67
+ .width--medium {
68
+ max-width: 640px;
69
+ }
70
+
71
+ .width--wide {
72
+ max-width: 1024px;
73
+ }
74
+
75
+ .width--extra-wide {
76
+ max-width: calc(var(--max-width) + 2 * var(--space-medium));
77
+ }
78
+
79
+ /** Center variation */
80
+ .center {
81
+ text-align: center;
82
+ }
83
+
84
+ @media (min-width: 768px) {
85
+ .dialog {
86
+ --dialog-vspace: var(--space-medium);
87
+ --dialog-padding: var(--space-large);
88
+ }
89
+ }
@@ -0,0 +1,67 @@
1
+ import React, { Fragment } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+
4
+ import { Dropdown, DropdownItem } from '../dropdown';
5
+ import { Button } from '../button';
6
+ import { Dialog, useDialogState } from '.';
7
+
8
+ const meta: Meta<typeof Dialog> = {
9
+ title: 'UI/Dialog',
10
+ component: Dialog,
11
+ };
12
+
13
+ export default meta;
14
+
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Default: Story = {
18
+ render: function DefaultDialog() {
19
+ const dialog = useDialogState();
20
+
21
+ return (
22
+ <>
23
+ <Button type="button" onClick={dialog.toggle}>
24
+ Open
25
+ </Button>
26
+ <Dialog title="Title" state={dialog}>
27
+ <div style={{ maxWidth: 480 }}>
28
+ <Dropdown label="Select option">
29
+ <DropdownItem>Option 1</DropdownItem>
30
+ <DropdownItem>Option 2</DropdownItem>
31
+ <DropdownItem>Option 3</DropdownItem>
32
+ </Dropdown>
33
+ <br />
34
+ <br />
35
+ {[1, 2, 3].map((item) => (
36
+ <Fragment key={item}>
37
+ <p>
38
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate, lectus
39
+ vitae tristique facilisis, quam ex vulputate risus, vitae mattis eros diam quis
40
+ velit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames
41
+ ac turpis egestas. Sed et velit tortor. Suspendisse risus ipsum, convallis
42
+ ultricies justo vel, auctor semper est. Sed viverra pulvinar odio eget pharetra.
43
+ In eleifend felis nec odio convallis, eleifend rhoncus diam maximus. Cras diam
44
+ libero, egestas blandit vestibulum nec, volutpat a mi. Etiam blandit turpis ac
45
+ nulla molestie vehicula. Duis bibendum a nisl sollicitudin lobortis. Vestibulum
46
+ lobortis eget justo ac elementum. Vestibulum vel lorem arcu. Etiam sagittis luctus
47
+ justo eu interdum. Ut lobortis tempus turpis, vitae auctor elit congue id.
48
+ </p>
49
+ <br />
50
+ <p>
51
+ Nulla eu ullamcorper quam. Mauris rhoncus tristique dui in rutrum. Ut eget justo a
52
+ lorem efficitur consequat. Nunc sed erat at ipsum convallis iaculis at ac lacus.
53
+ Etiam pellentesque felis quis maximus ultrices. Vivamus non pretium quam. Aenean
54
+ vel nunc fringilla, lacinia felis a, vehicula purus. Vestibulum in varius purus.
55
+ Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos
56
+ himenaeos. Quisque laoreet ultrices ligula eget iaculis. Vivamus lacinia ante
57
+ mattis lorem ultricies consequat. Integer dui odio, semper ac neque vitae,
58
+ elementum hendrerit ante. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
59
+ </p>
60
+ </Fragment>
61
+ ))}
62
+ </div>
63
+ </Dialog>
64
+ </>
65
+ );
66
+ },
67
+ };
@@ -0,0 +1,62 @@
1
+ import type { ComponentProps, ReactNode } from 'react';
2
+ import React from 'react';
3
+ import cx from 'classnames';
4
+ import { Dialog as AriakitDialog, DialogDismiss as AriakitDialogDismiss } from 'ariakit/dialog';
5
+
6
+ import { Stack } from '../../layout/stack';
7
+ import { BUTTON_GLYPHS, Button } from '../button';
8
+ import css from './dialog.module.css';
9
+
10
+ export { useDialogState } from 'ariakit/dialog';
11
+
12
+ type AriakitDialogProps = ComponentProps<typeof AriakitDialog>;
13
+
14
+ type DialogProps = {
15
+ title?: ReactNode;
16
+ width?: 'narrow' | 'medium' | 'wide' | 'extra-wide';
17
+ center?: boolean;
18
+ modal?: AriakitDialogProps['modal'];
19
+ state: AriakitDialogProps['state'];
20
+ } & Omit<ComponentProps<'div'>, 'title'>;
21
+
22
+ export const Dialog = (props: DialogProps) => {
23
+ const {
24
+ state,
25
+ className = '',
26
+ title = '',
27
+ width = 'medium',
28
+ center = false,
29
+ modal = false,
30
+ children,
31
+ } = props;
32
+
33
+ const rootClassName = cx(css.dialog, width && css[`width--${width}`], center && css.center);
34
+
35
+ return (
36
+ <AriakitDialog
37
+ state={state}
38
+ modal={modal}
39
+ backdrop
40
+ backdropProps={{
41
+ className: css.backdrop,
42
+ }}
43
+ portal
44
+ className={rootClassName}
45
+ >
46
+ {!modal && (
47
+ <AriakitDialogDismiss as={Button} glyph={BUTTON_GLYPHS.CLOSE} className={css.headerClose}>
48
+ {/* null content to prevent DialogDismiss to render the default "x" */}
49
+ {null}
50
+ </AriakitDialogDismiss>
51
+ )}
52
+ <Stack space="medium">
53
+ {title && (
54
+ <header className={css.header}>
55
+ {typeof title === 'string' ? <h2>{title}</h2> : title}
56
+ </header>
57
+ )}
58
+ <div className={cx(css.content, className)}>{children}</div>
59
+ </Stack>
60
+ </AriakitDialog>
61
+ );
62
+ };
@@ -0,0 +1 @@
1
+ export * from './dialog';
@@ -15,6 +15,7 @@ const ICONS = {
15
15
  COMMIT: 'commit',
16
16
  CLIPBOARD: 'clipboard',
17
17
  CLIPBOARD_CHECK: 'clipboard-check',
18
+ DOWNLOAD: 'download',
18
19
  ERROR: 'error',
19
20
  EXTERNAL_LINK: 'external-link',
20
21
  FILTER: 'filter',
package/src/ui/index.js CHANGED
@@ -3,6 +3,7 @@ export { Button } from './button';
3
3
  export { CopyToClipboard } from './copy-to-clipboard';
4
4
  export { Container } from './container';
5
5
  export { ControlGroup } from './control-group';
6
+ export { Dialog } from './dialog';
6
7
  export { Dropdown } from './dropdown';
7
8
  export { EmptySet } from './empty-set';
8
9
  export { FileName } from './file-name';
@@ -12,8 +13,10 @@ export { Icon } from './icon';
12
13
  export { Input } from './input';
13
14
  export { InputSearch } from './input-search';
14
15
  export { Loader } from './loader';
16
+ export { Markdown } from './markdown';
15
17
  export { Skeleton } from './skeleton';
16
18
  export { Table } from './table';
17
19
  export { Tabs } from './tabs';
18
20
  export { Tag } from './tag';
21
+ export { Textarea } from './textarea';
19
22
  export { Tooltip } from './tooltip';
@@ -0,0 +1 @@
1
+ export * from './markdown';
@@ -0,0 +1,7 @@
1
+ .root code {
2
+ padding: 2px var(--space-xxxsmall);
3
+ background: var(--color-highlight);
4
+ outline: 1px solid var(--color-outline);
5
+ border-radius: var(--radius-small);
6
+ color: var(--color-text);
7
+ }
@@ -0,0 +1,29 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { Markdown } from '.';
3
+
4
+ const meta: Meta<typeof Markdown> = {
5
+ title: 'UI/Markdown',
6
+ component: Markdown,
7
+ };
8
+
9
+ export default meta;
10
+
11
+ type Story = StoryObj<typeof meta>;
12
+
13
+ export const Default: Story = {
14
+ args: {
15
+ content: '**One line** _markdown_ content',
16
+ },
17
+ };
18
+
19
+ const MULTI_LINE_MARKDOWN = `
20
+ **Lorem ipsum** dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Habitant morbi tristique senectus et netus et malesuada.
21
+
22
+ Eget nunc scelerisque viverra mauris in. Ipsum dolor sit amet consectetur adipiscing.
23
+ `;
24
+
25
+ export const MultiLine: Story = {
26
+ args: {
27
+ content: MULTI_LINE_MARKDOWN,
28
+ },
29
+ };
@@ -0,0 +1,24 @@
1
+ import React, { ComponentProps, ElementType, useMemo } from 'react';
2
+ import cx from 'classnames';
3
+ import snarkdown from 'snarkdown';
4
+
5
+ import css from './markdown.module.css';
6
+
7
+ type MarkdownProps = {
8
+ content?: string;
9
+ as?: ElementType;
10
+ } & ComponentProps<'div'>;
11
+
12
+ export const Markdown = (props: MarkdownProps) => {
13
+ const { className = '', content = '', as: Component = 'div', ...restProps } = props;
14
+
15
+ const html = useMemo(() => snarkdown(content), [content]);
16
+
17
+ return (
18
+ <Component
19
+ className={cx(css.root, className)}
20
+ {...restProps}
21
+ dangerouslySetInnerHTML={{ __html: html }}
22
+ />
23
+ );
24
+ };
@@ -1,6 +1,6 @@
1
1
  .root {
2
- background: linear-gradient(transparent 10%, currentColor 10.1%, currentColor 89.9%, transparent 90%);
3
- opacity: 0.05;
2
+ background: var(--skeleton-inline-background);
3
+ opacity: var(--skeleton-opacity);
4
4
  pointer-events: none;
5
5
  min-width: 6em;
6
6
  border-radius: var(--radius-medium);
@@ -14,5 +14,5 @@
14
14
  }
15
15
 
16
16
  .block {
17
- background: currentColor;
17
+ background: var(--skeleton-block-background);
18
18
  }
@@ -4,7 +4,7 @@ import cx from 'classnames';
4
4
 
5
5
  import css from './tabs.module.css';
6
6
 
7
- const Item = ({ isTabActive, as: Component, className,...restProps }) => (
7
+ const Item = ({ isTabActive, as: Component = 'span', className = '', ...restProps }) => (
8
8
  <Component className={cx(css.item, className, isTabActive && css.itemActive)} {...restProps} />
9
9
  );
10
10
 
@@ -20,7 +20,7 @@ Item.propTypes = {
20
20
  as: PropTypes.elementType,
21
21
  };
22
22
 
23
- export const Tabs = ({ className, ...restProps }) => (
23
+ export const Tabs = ({ className = '', ...restProps }) => (
24
24
  <nav className={cx(css.root, className)} {...restProps} />
25
25
  );
26
26
 
@@ -10,6 +10,7 @@
10
10
  color: var(--color-text-ultra-light);
11
11
  font-weight: bold;
12
12
  text-decoration: none;
13
+ cursor: default;
13
14
  }
14
15
 
15
16
  .item::after {