@returnless/focus-ui 0.0.1 → 0.0.3

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 (186) hide show
  1. package/dist/focus-ui.js +10851 -0
  2. package/dist/focus-ui.umd.cjs +26 -0
  3. package/dist/style.css +1 -0
  4. package/package.json +13 -8
  5. package/src/build-utils/generate-component-meta.ts +5 -1
  6. package/src/build-utils/update-component-list.ts +1 -1
  7. package/src/components/Accordion/AccordionContent.vue +34 -5
  8. package/src/components/Accordion/AccordionItem.vue +5 -2
  9. package/src/components/Accordion/AccordionTrigger.vue +5 -2
  10. package/src/components/Accordion/README.md +1 -1
  11. package/src/components/ActionList/ActionList.vue +9 -0
  12. package/src/components/ActionList/ActionListBody.vue +11 -0
  13. package/src/components/ActionList/ActionListItem.vue +37 -0
  14. package/src/components/ActionList/ActionListSection.vue +7 -0
  15. package/src/components/ActionList/ActionListTrigger.vue +9 -0
  16. package/src/components/ActionList/README.md +113 -0
  17. package/src/components/ActionList/index.ts +5 -0
  18. package/src/components/Alert/Alert.vue +23 -10
  19. package/src/components/Alert/AlertDescription.vue +13 -1
  20. package/src/components/Alert/AlertTitle.vue +1 -1
  21. package/src/components/Alert/DismissableAlertButton.vue +6 -4
  22. package/src/components/Alert/README.md +31 -2
  23. package/src/components/Alert/index.ts +2 -0
  24. package/src/components/Alert/types.ts +1 -0
  25. package/src/components/AlertDialog/AlertDialog.vue +10 -1
  26. package/src/components/AlertDialog/AlertDialogActionButton.vue +9 -2
  27. package/src/components/AlertDialog/AlertDialogCancelButton.vue +1 -1
  28. package/src/components/AlertDialog/AlertDialogDescription.vue +7 -1
  29. package/src/components/AlertDialog/AlertDialogTitle.vue +11 -3
  30. package/src/components/AlertDialog/README.md +15 -16
  31. package/src/components/AspectRatio/AspectRatio.vue +19 -0
  32. package/src/components/AspectRatio/README.md +36 -0
  33. package/src/components/AspectRatio/index.ts +1 -0
  34. package/src/components/Avatar/Avatar.vue +57 -13
  35. package/src/components/Avatar/README.md +3 -9
  36. package/src/components/Badge/Badge.vue +1 -1
  37. package/src/components/Badge/README.md +9 -9
  38. package/src/components/BarChart/BarChart.vue +80 -0
  39. package/src/components/{MetricCard/MetricCardHeader.vue → BarChart/BarChartContainer.vue} +1 -1
  40. package/src/components/BarChart/BarChartStacked.vue +93 -0
  41. package/src/components/BarChart/README.md +83 -0
  42. package/src/components/BarChart/index.ts +3 -0
  43. package/src/components/Breadcrumbs/Breadcrumb.vue +7 -0
  44. package/src/components/Breadcrumbs/BreadcrumbEllipsis.vue +12 -0
  45. package/src/components/{MetricCard/MetricCardValue.vue → Breadcrumbs/BreadcrumbItem.vue} +2 -2
  46. package/src/components/Breadcrumbs/BreadcrumbLink.vue +13 -0
  47. package/src/components/Breadcrumbs/BreadcrumbList.vue +8 -0
  48. package/src/components/Breadcrumbs/BreadcrumbPage.vue +13 -0
  49. package/src/components/Breadcrumbs/BreadcrumbSeparator.vue +12 -0
  50. package/src/components/Breadcrumbs/README.md +91 -0
  51. package/src/components/Breadcrumbs/index.ts +7 -0
  52. package/src/components/Button/Button.vue +53 -41
  53. package/src/components/Button/ButtonContent.vue +1 -1
  54. package/src/components/Button/ButtonIcon.vue +28 -3
  55. package/src/components/Button/README.md +32 -29
  56. package/src/components/Button/index.ts +2 -0
  57. package/src/components/Button/types.ts +30 -0
  58. package/src/components/ButtonGroup/README.md +1 -1
  59. package/src/components/Card/CardHelp.vue +23 -0
  60. package/src/components/Card/CardSection.vue +17 -2
  61. package/src/components/Card/CardTitle.vue +6 -3
  62. package/src/components/Card/README.md +97 -10
  63. package/src/components/Card/index.ts +2 -1
  64. package/src/components/Checkbox/Checkbox.vue +29 -5
  65. package/src/components/Checkbox/README.md +34 -5
  66. package/src/components/DatePicker/DatePicker.vue +7 -27
  67. package/src/components/DatePicker/README.md +1 -1
  68. package/src/components/DescriptionList/DescriptionList.vue +1 -1
  69. package/src/components/DescriptionList/DescriptionListItem.vue +1 -1
  70. package/src/components/DescriptionList/README.md +2 -2
  71. package/src/components/Dialog/README.md +2 -0
  72. package/src/components/Dialog/index.ts +0 -0
  73. package/src/components/DropZone/DropZone.vue +105 -0
  74. package/src/components/DropZone/README.md +48 -0
  75. package/src/components/DropZone/index.ts +1 -0
  76. package/src/components/EmptyState/README.md +1 -1
  77. package/src/components/Feed/FeedItem.vue +4 -1
  78. package/src/components/Feed/FeedItemBlock.vue +4 -1
  79. package/src/components/Feed/README.md +1 -1
  80. package/src/components/FileUploadButton/FileUploadButton.vue +62 -0
  81. package/src/components/FileUploadButton/index.ts +1 -0
  82. package/src/components/Form/Form.vue +7 -2
  83. package/src/components/Form/README.md +1 -1
  84. package/src/components/FormLayout/FormLayout.vue +20 -2
  85. package/src/components/FormLayout/README.md +39 -1
  86. package/src/components/Heading/Heading.vue +32 -0
  87. package/src/components/Heading/index.ts +3 -0
  88. package/src/components/Heading/types.ts +3 -0
  89. package/src/components/Image/Image.vue +30 -0
  90. package/src/components/Image/index.ts +1 -0
  91. package/src/components/InertiaLink/InertiaLink.vue +11 -0
  92. package/src/components/InertiaLink/index.ts +1 -0
  93. package/src/components/InlineError/InlineError.vue +21 -0
  94. package/src/components/InlineError/README.md +63 -0
  95. package/src/components/InlineError/index.ts +1 -0
  96. package/src/components/KPICard/KPICard.vue +28 -0
  97. package/src/components/KPICard/KPICardSection.vue +30 -0
  98. package/src/components/KPICard/README.md +124 -0
  99. package/src/components/KPICard/index.ts +2 -0
  100. package/src/components/Legend/Legend.vue +7 -0
  101. package/src/components/Legend/LegendItem.vue +34 -0
  102. package/src/components/Legend/README.md +32 -0
  103. package/src/components/Legend/index.ts +2 -0
  104. package/src/components/Link/Link.vue +4 -4
  105. package/src/components/Link/README.md +1 -1
  106. package/src/components/Navigation/Navigation.vue +2 -2
  107. package/src/components/Navigation/NavigationItem.vue +14 -10
  108. package/src/components/Navigation/NavigationSecondarySection.vue +12 -0
  109. package/src/components/Navigation/NavigationSection.vue +1 -1
  110. package/src/components/Navigation/README.md +10 -15
  111. package/src/components/Navigation/index.ts +1 -0
  112. package/src/components/Page/Page.vue +2 -33
  113. package/src/components/Page/PageBody.vue +36 -0
  114. package/src/components/Page/PageTitle.vue +6 -3
  115. package/src/components/Page/README.md +45 -39
  116. package/src/components/Page/index.ts +1 -0
  117. package/src/components/Pagination/README.md +1 -1
  118. package/src/components/PinInput/README.md +1 -1
  119. package/src/components/Popover/Popover.vue +18 -0
  120. package/src/components/Popover/PopoverBody.vue +11 -0
  121. package/src/components/Popover/PopoverTrigger.vue +9 -0
  122. package/src/components/Popover/README.md +34 -6
  123. package/src/components/Popover/index.ts +3 -0
  124. package/src/components/Popper/Popper.vue +91 -0
  125. package/src/components/Popper/PopperBody.vue +19 -0
  126. package/src/components/Popper/PopperTrigger.vue +14 -0
  127. package/src/components/Popper/README.md +42 -0
  128. package/src/components/Popper/index.ts +3 -0
  129. package/src/components/ProgressBar/ProgressBar.vue +24 -6
  130. package/src/components/RadioButton/README.md +1 -1
  131. package/src/components/RadioButton/RadioButton.vue +3 -2
  132. package/src/components/ResourceList/README.md +160 -0
  133. package/src/components/ResourceList/ResourceList.vue +7 -0
  134. package/src/components/ResourceList/ResourceListItem.vue +7 -0
  135. package/src/components/ResourceList/ResourceListItemContent.vue +7 -0
  136. package/src/components/ResourceList/index.ts +3 -0
  137. package/src/components/Select/README.md +1 -1
  138. package/src/components/Select/Select.vue +1 -1
  139. package/src/components/Separator/README.md +5 -1
  140. package/src/components/Separator/Separator.vue +20 -3
  141. package/src/components/Spinner/README.md +1 -1
  142. package/src/components/Spinner/Spinner.vue +10 -4
  143. package/src/components/StatusIndicator/README.md +2 -2
  144. package/src/components/StatusIndicator/StatusIndicator.vue +11 -5
  145. package/src/components/Stepper/README.md +38 -0
  146. package/src/components/Stepper/Stepper.vue +104 -0
  147. package/src/components/Stepper/index.ts +1 -0
  148. package/src/components/Tabs/README.md +1 -1
  149. package/src/components/Tabs/TabTrigger.vue +5 -4
  150. package/src/components/Tabs/Tabs.vue +4 -1
  151. package/src/components/Tag/Tag.vue +45 -0
  152. package/src/components/Tag/index.ts +1 -0
  153. package/src/components/TextField/README.md +24 -6
  154. package/src/components/TextField/TextField.vue +25 -5
  155. package/src/components/TextField/TextFieldIcon.vue +19 -0
  156. package/src/components/TextStyle/README.md +1 -1
  157. package/src/components/TextStyle/TextStyle.vue +1 -1
  158. package/src/components/Toast/DismissToastAction.vue +1 -1
  159. package/src/components/Toast/README.md +1 -1
  160. package/src/components/Toggle/README.md +1 -1
  161. package/src/components/Toggle/Toggle.vue +8 -5
  162. package/src/components/Tooltip/README.md +1 -1
  163. package/src/components/Tooltip/Tooltip.vue +15 -41
  164. package/src/components/TopBar/TopBarSearch.vue +2 -2
  165. package/src/components/index.ts +68 -12
  166. package/src/components/types.ts +5 -0
  167. package/src/composables/useTheme.ts +13 -1
  168. package/src/composables/useToastNotifications.ts +1 -1
  169. package/src/composables/useUniqueId.ts +4 -3
  170. package/src/index.css +17 -13
  171. package/src/index.ts +0 -11
  172. package/dist/focus-ui.es.js +0 -33
  173. package/dist/types/components/Accordion/Accordion.vue.d.ts +0 -32
  174. package/dist/types/components/Accordion/AccordionItem.vue.d.ts +0 -2
  175. package/dist/types/components/Accordion/index.d.ts +0 -2
  176. package/dist/types/components/index.d.ts +0 -1
  177. package/dist/types/index.d.ts +0 -7
  178. package/src/components/CategoryBar/CategoryBar.vue +0 -25
  179. package/src/components/CategoryBar/CategoryBarItem.vue +0 -34
  180. package/src/components/CategoryBar/README.md +0 -17
  181. package/src/components/CategoryBar/index.ts +0 -2
  182. package/src/components/MetricCard/MetricCard.vue +0 -11
  183. package/src/components/MetricCard/MetricCardLabel.vue +0 -9
  184. package/src/components/MetricCard/MetricCardSection.vue +0 -11
  185. package/src/components/MetricCard/README.md +0 -53
  186. package/src/components/MetricCard/index.ts +0 -5
@@ -1,7 +1,15 @@
1
- <script lang="ts" setup></script>
1
+ <script lang="ts" setup>
2
+ import { Heading } from '../Heading';
3
+ import { inject } from 'vue';
4
+
5
+ const alertDialogLabelledBy = inject<string>('alertDialogLabelledBy')!;
6
+ </script>
2
7
 
3
8
  <template>
4
- <h2 class="text-xl font-semibold leading-none">
9
+ <Heading
10
+ :id="alertDialogLabelledBy"
11
+ level="h2"
12
+ >
5
13
  <slot />
6
- </h2>
14
+ </Heading>
7
15
  </template>
@@ -28,21 +28,20 @@ A modal dialog that interrupts the user with important content and expects a res
28
28
  <Button @click="alertDialogOpen = true">
29
29
  Open Dialog
30
30
  </Button>
31
-
32
- <AlertDialog :open="alertDialogOpen" @cancel="alertDialogOpen = false">
33
- <AlertDialogContent>
34
- <AlertDialogHeader>
35
- <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
36
- <AlertDialogDescription>
37
- This action cannot be undone. This will permanently delete your account and remove your data from our servers.
38
- </AlertDialogDescription>
39
- </AlertDialogHeader>
40
- <AlertDialogFooter>
41
- <AlertDialogCancelButton>Cancel</AlertDialogCancelButton>
42
- <AlertDialogActionButton>Continue</AlertDialogActionButton>
43
- </AlertDialogFooter>
44
- </AlertDialogContent>
45
- </AlertDialog>
31
+ <AlertDialog :open="alertDialogOpen" @cancel="alertDialogOpen = false">
32
+ <AlertDialogContent>
33
+ <AlertDialogHeader>
34
+ <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
35
+ <AlertDialogDescription>
36
+ This action cannot be undone. This will permanently delete your account and remove your data from our servers.
37
+ </AlertDialogDescription>
38
+ </AlertDialogHeader>
39
+ <AlertDialogFooter>
40
+ <AlertDialogCancelButton>Cancel</AlertDialogCancelButton>
41
+ <AlertDialogActionButton>Continue</AlertDialogActionButton>
42
+ </AlertDialogFooter>
43
+ </AlertDialogContent>
44
+ </AlertDialog>
46
45
  </ComponentWrapper>
47
46
 
48
47
  ```js-vue
@@ -57,7 +56,7 @@ import {
57
56
  AlertDialogHeader,
58
57
  AlertDialogTitle,
59
58
  Button,
60
- } from 'focus-ui';
59
+ } from '@returnless/focus-ui';
61
60
 
62
61
  const alertDialogOpen = ref(false);
63
62
  </script>
@@ -0,0 +1,19 @@
1
+ <script lang="ts" setup>
2
+ withDefaults(defineProps<{
3
+ /** The aspect ratio of the element. */
4
+ ratio?: number;
5
+ }>(), {
6
+ ratio: 1,
7
+ });
8
+ </script>
9
+
10
+ <template>
11
+ <div
12
+ :style="{ paddingBottom: `${100 / ratio}%` }"
13
+ class="relative w-full"
14
+ >
15
+ <div class="absolute inset-0 [&>*]:object-cover [&>*]:w-full [&>*]:h-full">
16
+ <slot />
17
+ </div>
18
+ </div>
19
+ </template>
@@ -0,0 +1,36 @@
1
+ <script lang="ts" setup>
2
+ import { AspectRatio } from '../../src/components';
3
+ import api from '../component-meta/AspectRatio.json';
4
+ </script>
5
+
6
+ # Aspect Ratio
7
+
8
+ The AspectRatio component is a layout component that enforces a fixed aspect ratio on its children. This is useful when
9
+ you want to ensure that a child component maintains a specific aspect ratio, regardless of the size of the parent
10
+ component.
11
+
12
+ <ComponentApi :api="api" />
13
+
14
+ ## Usage
15
+
16
+ <ComponentWrapper>
17
+ <div style="width: 400px;">
18
+ <AspectRatio :ratio="16 / 9">
19
+ <img src="https://www.retourneren.nl/media/catalog/product/cache/8d4d2075b1a30681853bef5bdc41b164/r/e/returnless_afbeelding.jpg">
20
+ </AspectRatio>
21
+ </div>
22
+ </ComponentWrapper>
23
+
24
+ ```js-vue
25
+ <script lang="ts" setup>
26
+ import { AspectRatio } from '@returnless/focus-ui';
27
+ </script>
28
+
29
+ <template>
30
+ <div style="width: 400px;">
31
+ <AspectRatio :ratio="16 / 9">
32
+ <img src="https://www.retourneren.nl/media/catalog/product/cache/8d4d2075b1a30681853bef5bdc41b164/r/e/returnless_afbeelding.jpg">
33
+ </AspectRatio>
34
+ </div>
35
+ </template>
36
+ ```
@@ -0,0 +1 @@
1
+ export { default as AspectRatio } from './AspectRatio.vue';
@@ -1,47 +1,59 @@
1
1
  <script lang="ts" setup>
2
2
  import { UserIcon } from '@heroicons/vue/16/solid';
3
- import { computed } from 'vue';
3
+ import { computed, ref } from 'vue';
4
+ import { AspectRatio } from '../AspectRatio';
5
+ import { Image } from '../Image';
4
6
 
5
7
  const props = withDefaults(defineProps<{
6
8
  /** The size of the avatar */
7
9
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
8
10
 
9
11
  /** The name of the person, company or entity */
10
- name?: string | null;
12
+ name?: string | undefined;
11
13
 
12
14
  /** Initials of person to display. */
13
15
  initials?: string | null;
14
16
 
15
17
  /** URL of the avatar image which falls back to initials if the image fails to load. */
16
- source?: string | null;
18
+ source?: string | undefined;
17
19
 
18
20
  /** Accessible label for the avatar image. */
19
21
  accessibilityLabel?: string | null;
20
22
  }>(), {
21
23
  size: 'md',
22
- name: null,
24
+ name: undefined,
23
25
  initials: null,
24
- source: null,
26
+ source: undefined,
25
27
  accessibilityLabel: null,
26
28
  });
27
29
 
28
- const classList = computed(() => {
30
+ enum ImageLoadingState {
31
+ Pending = 'PENDING',
32
+ Loaded = 'LOADED',
33
+ Errored = 'ERRORED',
34
+ }
35
+
36
+ const imageLoadingState = ref<ImageLoadingState>(ImageLoadingState.Pending);
37
+
38
+ const classList = computed((): Record<string, boolean>[] => {
29
39
  return [
30
40
  { 'w-4 h-4': props.size === 'xs' },
31
41
  { 'w-6 h-6': props.size === 'sm' },
32
42
  { 'w-8 h-8': props.size === 'md' },
33
43
  { 'w-10 h-10': props.size === 'lg' },
34
44
  { 'w-12 h-12': props.size === 'xl' },
45
+
46
+ { 'bg-slate-200': imageLoadingState.value !== ImageLoadingState.Loaded },
35
47
  ];
36
48
  });
37
49
 
38
- const iconSize = computed(() => {
50
+ const iconSize = computed((): Record<string, boolean>[] => {
39
51
  return [
40
52
  { 'w-3 h-3': props.size === 'xs' },
41
53
  ];
42
54
  });
43
55
 
44
- const textSize = computed(() => {
56
+ const textSize = computed((): Record<string, boolean>[] => {
45
57
  return [
46
58
  { 'text-xs': props.size === 'xs' },
47
59
  { 'text-xs': props.size === 'sm' },
@@ -56,23 +68,55 @@ function renderInitials(initials: string): string {
56
68
  ? initials?.slice(0, 1)
57
69
  : initials;
58
70
  }
71
+
72
+ function handleImageError(): void {
73
+ imageLoadingState.value = ImageLoadingState.Errored;
74
+ }
75
+
76
+ function handleImageLoad(): void {
77
+ imageLoadingState.value = ImageLoadingState.Loaded;
78
+ }
79
+
80
+ const accessibilityLabelValue = computed((): string | undefined => {
81
+ const accessibilityLabel = props.accessibilityLabel || props.name;
82
+
83
+ return accessibilityLabel
84
+ ? accessibilityLabel
85
+ : undefined;
86
+ });
59
87
  </script>
60
88
 
61
89
  <template>
62
- <div
63
- class="relative flex items-center justify-center rounded bg-slate-200"
90
+ <span
91
+ :aria-label="accessibilityLabelValue"
64
92
  :class="classList"
93
+ class="relative flex items-center justify-center overflow-hidden rounded"
65
94
  >
66
95
  <UserIcon
67
96
  v-if="!initials"
68
- role="img"
69
97
  :class="iconSize"
98
+ role="img"
70
99
  />
71
100
  <span
72
- class="font-semibold"
101
+ v-if="source"
102
+ class="absolute h-full w-full"
103
+ >
104
+ <AspectRatio>
105
+ <Image
106
+ :alt="name!"
107
+ :class="{ 'hidden': imageLoadingState !== ImageLoadingState.Loaded}"
108
+ :source="source"
109
+ @error="handleImageError"
110
+ @load="handleImageLoad"
111
+ />
112
+ </AspectRatio>
113
+ </span>
114
+ <span
115
+ v-if="initials && imageLoadingState !== ImageLoadingState.Loaded"
73
116
  :class="textSize"
117
+ class="font-semibold"
74
118
  >
75
119
  {{ renderInitials(initials) }}
76
120
  </span>
77
- </div>
121
+ </span>
78
122
  </template>
@@ -17,7 +17,7 @@ Avatars are used to show a thumbnail representation of an individual or business
17
17
 
18
18
  ```js-vue
19
19
  <script lang="ts" setup>
20
- import { Avatar } from 'focus-ui';
20
+ import { Avatar } from '@returnless/focus-ui';
21
21
  </script>
22
22
 
23
23
  <template>
@@ -35,7 +35,7 @@ import { Avatar } from 'focus-ui';
35
35
 
36
36
  ```js-vue
37
37
  <script lang="ts" setup>
38
- import { Avatar } from 'focus-ui';
38
+ import { Avatar } from '@returnless/focus-ui';
39
39
  </script>
40
40
 
41
41
  <template>
@@ -66,7 +66,7 @@ import { Avatar } from 'focus-ui';
66
66
 
67
67
  ```js-vue
68
68
  <script lang="ts" setup>
69
- import { Avatar } from 'focus-ui';
69
+ import { Avatar } from '@returnless/focus-ui';
70
70
  </script>
71
71
 
72
72
  <template>
@@ -109,12 +109,6 @@ For avatars, we recommend using a format that describes what will show in the im
109
109
 
110
110
  ## Accessibility
111
111
 
112
- ### Structure
113
-
114
- The avatar component uses a generated scalable vector graphics (SVG) file, which can cause challenges for users that
115
- use assistive technologies. To create a standard experience, the `<img>` is hidden from assistive technologies by
116
- using an empty `alt` attribute, and replaced with a `<span>` that has `role="img"`.
117
-
118
112
  ### Labeling
119
113
 
120
114
  The avatar component represents content, and should have a text equivalent for users using assistive technologies.
@@ -25,7 +25,7 @@ const colorValues = computed((): CSSProperties => {
25
25
  };
26
26
  });
27
27
 
28
- const classList = computed(() => {
28
+ const classList = computed((): Record<string, any> => {
29
29
  return [
30
30
  { 'text-xs px-2 py-1': props.size === 'sm' },
31
31
  { 'text-sm px-3 py-1.5': props.size === 'md' },
@@ -24,7 +24,7 @@ Badges are used to inform users of the tone of an object or of an action that's
24
24
 
25
25
  ```js-vue
26
26
  <script lang="ts" setup>
27
- import { Badge } from 'focus-ui';
27
+ import { Badge } from '@returnless/focus-ui';
28
28
  </script>
29
29
 
30
30
  <template>
@@ -43,7 +43,7 @@ Badges can include icons to help users quickly identify the tone of an object or
43
43
  <ComponentWrapper>
44
44
  <ComponentGrid>
45
45
  <Badge color="green">
46
- <BadgetContent>+ 25%</BadgetContent>
46
+ <BadgeContent>+ 25%</BadgeContent>
47
47
  <BadgeIcon>
48
48
  <ArrowTrendingUpIcon />
49
49
  </BadgeIcon>
@@ -52,20 +52,20 @@ Badges can include icons to help users quickly identify the tone of an object or
52
52
  <BadgeIcon>
53
53
  <ArrowTrendingDownIcon />
54
54
  </BadgeIcon>
55
- <BadgetContent>- 25%</BadgetContent>
55
+ <BadgeContent>- 25%</BadgeContent>
56
56
  </Badge>
57
57
  </ComponentGrid>
58
58
  </ComponentWrapper>
59
59
 
60
60
  ```js-vue
61
61
  <script lang="ts" setup>
62
- import { Badge, BadgetContent, BadgeIcon } from 'focus-ui';
62
+ import { Badge, BadgeContent, BadgeIcon } from '@returnless/focus-ui';
63
63
  import { ArrowTrendingUpIcon, ArrowTrendingDownIcon } from '@heroicons/vue/16/solid';
64
64
  </script>
65
65
 
66
66
  <template>
67
67
  <Badge color="green">
68
- <BadgetContent>+ 25%</BadgetContent>
68
+ <BadgeContent>+ 25%</BadgeContent>
69
69
  <BadgeIcon>
70
70
  <ArrowTrendingUpIcon />
71
71
  </BadgeIcon>
@@ -74,7 +74,7 @@ import { ArrowTrendingUpIcon, ArrowTrendingDownIcon } from '@heroicons/vue/16/so
74
74
  <BadgeIcon>
75
75
  <ArrowTrendingDownIcon />
76
76
  </BadgeIcon>
77
- <BadgetContent>- 25%</BadgetContent>
77
+ <BadgeContent>- 25%</BadgeContent>
78
78
  </Badge>
79
79
  </template>
80
80
  ```
@@ -92,19 +92,19 @@ Badges come in three sizes: small, medium, and large.
92
92
  <BadgeIcon>
93
93
  <ArrowTrendingUpIcon />
94
94
  </BadgeIcon>
95
- <BadgetContent>sm with icon</BadgetContent>
95
+ <BadgeContent>sm with icon</BadgeContent>
96
96
  </Badge>
97
97
  <Badge color="blue" size="md">
98
98
  <BadgeIcon>
99
99
  <ArrowTrendingUpIcon />
100
100
  </BadgeIcon>
101
- <BadgetContent>md with icon</BadgetContent>
101
+ <BadgeContent>md with icon</BadgeContent>
102
102
  </Badge>
103
103
  <Badge color="red" size="lg">
104
104
  <BadgeIcon>
105
105
  <ArrowTrendingDownIcon />
106
106
  </BadgeIcon>
107
- <BadgetContent>lg with icon</BadgetContent>
107
+ <BadgeContent>lg with icon</BadgeContent>
108
108
  </Badge>
109
109
  </ComponentGrid>
110
110
  </ComponentWrapper>
@@ -0,0 +1,80 @@
1
+ <script lang="ts" setup>
2
+ import collect, { Collection } from 'collect.js';
3
+ import { computed } from 'vue';
4
+ import { TailwindColor, useTailwindColor } from '../../composables';
5
+ import { BarChartContainer } from './';
6
+ import { Legend, LegendItem } from '../Legend';
7
+
8
+ type DataPoint = {
9
+ label: string;
10
+ value: number;
11
+ color: TailwindColor;
12
+ };
13
+
14
+ const props = defineProps<{
15
+ /** The data points to display */
16
+ dataPoints: DataPoint[];
17
+
18
+ /** The height of the chart */
19
+ height: number;
20
+ }>();
21
+
22
+ const dataPointCollection = computed((): Collection<DataPoint> => {
23
+ return collect(props.dataPoints);
24
+ });
25
+
26
+ const max = computed((): number => {
27
+ return dataPointCollection.value.max('value');
28
+ });
29
+
30
+ function dataPointHeight(dataPoint: DataPoint): number {
31
+ return dataPoint.value / max.value * 100;
32
+ }
33
+
34
+ function getColorValue(dataPoint: DataPoint): string {
35
+ return useTailwindColor(dataPoint.color, '400');
36
+ }
37
+
38
+ const axisLabels = computed(() => {
39
+ return [
40
+ props.dataPoints[0].label,
41
+ props.dataPoints[props.dataPoints.length - 1].label,
42
+ ];
43
+ });
44
+ </script>
45
+
46
+ <template>
47
+ <BarChartContainer>
48
+ <Legend>
49
+ <LegendItem color="blue">
50
+ En-route
51
+ </LegendItem>
52
+ </Legend>
53
+
54
+ <div class="space-y-2">
55
+ <div
56
+ class="flex space-x-1"
57
+ :style="{ height: `${height}px` }"
58
+ >
59
+ <div
60
+ v-for="(dataPoint, dataPointIndex) in dataPoints"
61
+ :key="dataPointIndex"
62
+ :style="{ width: `${100 / dataPoints.length}%` }"
63
+ class="flex h-full items-end rounded-sm bg-slate-100"
64
+ >
65
+ <div
66
+ class="w-full rounded-sm"
67
+ :style="{
68
+ height: `${dataPointHeight(dataPoint)}%`,
69
+ backgroundColor: getColorValue(dataPoint),
70
+ }"
71
+ />
72
+ </div>
73
+ </div>
74
+ <div class="flex justify-between">
75
+ <div>{{ axisLabels[0] }}</div>
76
+ <div>{{ axisLabels[1] }}</div>
77
+ </div>
78
+ </div>
79
+ </BarChartContainer>
80
+ </template>
@@ -1,7 +1,7 @@
1
1
  <script lang="ts" setup></script>
2
2
 
3
3
  <template>
4
- <div class="relative space-y-2">
4
+ <div class="space-y-4">
5
5
  <slot />
6
6
  </div>
7
7
  </template>
@@ -0,0 +1,93 @@
1
+ <script lang="ts" setup>
2
+ import collect from 'collect.js';
3
+ import { computed } from 'vue';
4
+ import { TailwindColor, useTailwindColor } from '../../composables';
5
+ import { Legend, LegendItem } from '../Legend';
6
+ import { BarChartContainer } from './index';
7
+
8
+ type DataPointStack = {
9
+ label: string;
10
+ dataPoints: DataPoint[];
11
+ };
12
+
13
+ type DataPoint = {
14
+ label: string;
15
+ value: number;
16
+ color: TailwindColor;
17
+ };
18
+
19
+ const props = defineProps<{
20
+ dataPoints: DataPointStack[];
21
+ height: number;
22
+ }>();
23
+
24
+ const max = computed((): number => {
25
+ return collect(props.dataPoints)
26
+ .map((dataPointStack: DataPointStack) => collect(dataPointStack.dataPoints).sum('value'))
27
+ .max();
28
+ });
29
+
30
+ function getDataPointHeight(dataPoint: DataPoint): number {
31
+ return dataPoint.value / max.value * 100;
32
+ }
33
+
34
+ function getColorValue(dataPoint: DataPoint): string {
35
+ return useTailwindColor(dataPoint.color, '400');
36
+ }
37
+
38
+ const axisLabels = computed((): string[] => {
39
+ return [
40
+ props.dataPoints[0].label,
41
+ props.dataPoints[props.dataPoints.length - 1].label,
42
+ ];
43
+ });
44
+ </script>
45
+
46
+ <template>
47
+ <BarChartContainer>
48
+ <Legend>
49
+ <LegendItem color="teal">
50
+ Announced
51
+ </LegendItem>
52
+ <LegendItem color="green">
53
+ Sent
54
+ </LegendItem>
55
+ <LegendItem color="lime">
56
+ En-route
57
+ </LegendItem>
58
+ <LegendItem color="yellow">
59
+ Delivered
60
+ </LegendItem>
61
+ <LegendItem color="amber">
62
+ En-route
63
+ </LegendItem>
64
+ </Legend>
65
+ <div class="space-y-2">
66
+ <div
67
+ class="flex space-x-1"
68
+ :style="{ height: `${height}px` }"
69
+ >
70
+ <div
71
+ v-for="(dataStack, dataStackIndex) in dataPoints"
72
+ :key="dataStackIndex"
73
+ :style="{ width: `${100 / dataPoints.length}%` }"
74
+ class="flex flex-col-reverse h-full items-end rounded-sm bg-slate-100 overflow-hidden"
75
+ >
76
+ <div
77
+ v-for="(dataPoint, dataPointIndex) in dataStack.dataPoints"
78
+ :key="dataPointIndex"
79
+ class="w-full"
80
+ :style="{
81
+ height: `${getDataPointHeight(dataPoint)}%`,
82
+ backgroundColor: getColorValue(dataPoint),
83
+ }"
84
+ />
85
+ </div>
86
+ </div>
87
+ <div class="flex justify-between">
88
+ <div>{{ axisLabels[0] }}</div>
89
+ <div>{{ axisLabels[1] }}</div>
90
+ </div>
91
+ </div>
92
+ </BarChartContainer>
93
+ </template>
@@ -0,0 +1,83 @@
1
+ <script lang="ts" setup>
2
+ import { BarChart, BarChartStacked, Card, CardSection, Legend, LegendItem } from '../../src/components';
3
+ import api from '../component-meta/BarChart.json';
4
+
5
+ const dataPoints = Array.from({ length: 30 }).map((_, index) => {
6
+ return {
7
+ value: Math.floor(Math.random() * 200),
8
+ label: `data-point ${index}`,
9
+ color: 'blue',
10
+ };
11
+ });
12
+
13
+ const stackedDataPoints = Array.from({ length: 30 }).map((_, index) => {
14
+ return {
15
+ label: `data-point ${index}`,
16
+ dataPoints: Array.from({ length: 5 }).map((_, index) => {
17
+ return {
18
+ value: Math.floor(Math.random() * 200),
19
+ label: `data-point ${index}`,
20
+ color: ['teal', 'green', 'lime', 'yellow', 'amber'][index],
21
+ };
22
+ }),
23
+ };
24
+ });
25
+ </script>
26
+
27
+ # Bar Chart
28
+
29
+ <ComponentApi :api="api" />
30
+
31
+ ## Usage
32
+
33
+ Use the `BarChart` component to display a single data point in a bar.
34
+
35
+ <ComponentWrapper>
36
+ <Card>
37
+ <CardSection>
38
+ <BarChart :data-points="dataPoints" height="150" />
39
+ </CardSection>
40
+ </Card>
41
+ </ComponentWrapper>
42
+
43
+ ```js-vue
44
+ <script lang="ts" setup>
45
+ import { BarChart, Card, CardSection } from '@returnless/focus-ui';
46
+ </script>
47
+
48
+ <template>
49
+ <Card>
50
+ <CardSection>
51
+ <BarChart :data-points="dataPoints" height="200" />
52
+ </CardSection>
53
+ </Card>
54
+ </template>
55
+ ```
56
+
57
+ ### Stacked Bar Chart
58
+
59
+ Use the `BarChartStacked` component to display multiple data points in a single bar.
60
+
61
+ <ComponentWrapper>
62
+ <Card>
63
+ <CardSection>
64
+ <BarChartStacked :data-points="stackedDataPoints" height="150">
65
+ test
66
+ </BarChartStacked>
67
+ </CardSection>
68
+ </Card>
69
+ </ComponentWrapper>
70
+
71
+ ```js-vue
72
+ <script lang="ts" setup>
73
+ import { BarChartStacked, Card, CardSection } from '@returnless/focus-ui';
74
+ </script>
75
+
76
+ <template>
77
+ <Card>
78
+ <CardSection>
79
+ <BarChartStacked :data-points="dataPoints" height="200" />
80
+ </CardSection>
81
+ </Card>
82
+ </template>
83
+ ```
@@ -0,0 +1,3 @@
1
+ export { default as BarChart } from './BarChart.vue';
2
+ export { default as BarChartContainer } from './BarChartContainer.vue';
3
+ export { default as BarChartStacked } from './BarChartStacked.vue';
@@ -0,0 +1,7 @@
1
+ <script lang="ts" setup></script>
2
+
3
+ <template>
4
+ <nav aria-label="breadcrumb">
5
+ <slot />
6
+ </nav>
7
+ </template>
@@ -0,0 +1,12 @@
1
+ <script lang="ts" setup>
2
+ import { EllipsisHorizontalIcon } from '@heroicons/vue/16/solid';
3
+ </script>
4
+
5
+ <template>
6
+ <span
7
+ role="presentation"
8
+ aria-hidden="true"
9
+ >
10
+ <EllipsisHorizontalIcon class="w-4 h-4" />
11
+ </span>
12
+ </template>
@@ -2,7 +2,7 @@
2
2
  </script>
3
3
 
4
4
  <template>
5
- <div class="text-xl font-bold leading-none">
5
+ <li class="inline-flex items-center text-slate-500">
6
6
  <slot />
7
- </div>
7
+ </li>
8
8
  </template>