@returnless/focus-ui 0.0.2 → 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.
- package/dist/focus-ui.js +10851 -0
- package/dist/focus-ui.umd.cjs +26 -0
- package/dist/style.css +1 -0
- package/package.json +11 -7
- package/src/build-utils/generate-component-meta.ts +5 -1
- package/src/build-utils/update-component-list.ts +1 -1
- package/src/components/Accordion/AccordionContent.vue +34 -5
- package/src/components/Accordion/AccordionItem.vue +5 -2
- package/src/components/Accordion/AccordionTrigger.vue +5 -2
- package/src/components/Accordion/README.md +1 -1
- package/src/components/ActionList/ActionList.vue +9 -0
- package/src/components/ActionList/ActionListBody.vue +11 -0
- package/src/components/ActionList/ActionListItem.vue +37 -0
- package/src/components/ActionList/ActionListSection.vue +7 -0
- package/src/components/ActionList/ActionListTrigger.vue +9 -0
- package/src/components/ActionList/README.md +113 -0
- package/src/components/ActionList/index.ts +5 -0
- package/src/components/Alert/Alert.vue +23 -10
- package/src/components/Alert/AlertDescription.vue +13 -1
- package/src/components/Alert/AlertTitle.vue +1 -1
- package/src/components/Alert/DismissableAlertButton.vue +6 -4
- package/src/components/Alert/README.md +31 -2
- package/src/components/Alert/index.ts +2 -0
- package/src/components/Alert/types.ts +1 -0
- package/src/components/AlertDialog/AlertDialog.vue +10 -1
- package/src/components/AlertDialog/AlertDialogActionButton.vue +9 -2
- package/src/components/AlertDialog/AlertDialogCancelButton.vue +1 -1
- package/src/components/AlertDialog/AlertDialogDescription.vue +7 -1
- package/src/components/AlertDialog/AlertDialogTitle.vue +11 -3
- package/src/components/AlertDialog/README.md +15 -16
- package/src/components/AspectRatio/AspectRatio.vue +19 -0
- package/src/components/AspectRatio/README.md +36 -0
- package/src/components/AspectRatio/index.ts +1 -0
- package/src/components/Avatar/Avatar.vue +57 -13
- package/src/components/Avatar/README.md +3 -9
- package/src/components/Badge/Badge.vue +1 -1
- package/src/components/Badge/README.md +9 -9
- package/src/components/BarChart/BarChart.vue +80 -0
- package/src/components/{MetricCard/MetricCardHeader.vue → BarChart/BarChartContainer.vue} +1 -1
- package/src/components/BarChart/BarChartStacked.vue +93 -0
- package/src/components/BarChart/README.md +83 -0
- package/src/components/BarChart/index.ts +3 -0
- package/src/components/Breadcrumbs/Breadcrumb.vue +7 -0
- package/src/components/Breadcrumbs/BreadcrumbEllipsis.vue +12 -0
- package/src/components/{MetricCard/MetricCardValue.vue → Breadcrumbs/BreadcrumbItem.vue} +2 -2
- package/src/components/Breadcrumbs/BreadcrumbLink.vue +13 -0
- package/src/components/Breadcrumbs/BreadcrumbList.vue +8 -0
- package/src/components/Breadcrumbs/BreadcrumbPage.vue +13 -0
- package/src/components/Breadcrumbs/BreadcrumbSeparator.vue +12 -0
- package/src/components/Breadcrumbs/README.md +91 -0
- package/src/components/Breadcrumbs/index.ts +7 -0
- package/src/components/Button/Button.vue +53 -41
- package/src/components/Button/ButtonContent.vue +1 -1
- package/src/components/Button/ButtonIcon.vue +28 -3
- package/src/components/Button/README.md +32 -29
- package/src/components/Button/index.ts +2 -0
- package/src/components/Button/types.ts +30 -0
- package/src/components/ButtonGroup/README.md +1 -1
- package/src/components/Card/CardHelp.vue +23 -0
- package/src/components/Card/CardSection.vue +17 -2
- package/src/components/Card/CardTitle.vue +6 -3
- package/src/components/Card/README.md +97 -10
- package/src/components/Card/index.ts +2 -1
- package/src/components/Checkbox/Checkbox.vue +29 -5
- package/src/components/Checkbox/README.md +34 -5
- package/src/components/DatePicker/DatePicker.vue +7 -27
- package/src/components/DatePicker/README.md +1 -1
- package/src/components/DescriptionList/DescriptionList.vue +1 -1
- package/src/components/DescriptionList/DescriptionListItem.vue +1 -1
- package/src/components/DescriptionList/README.md +2 -2
- package/src/components/Dialog/README.md +2 -0
- package/src/components/Dialog/index.ts +0 -0
- package/src/components/DropZone/DropZone.vue +105 -0
- package/src/components/DropZone/README.md +48 -0
- package/src/components/DropZone/index.ts +1 -0
- package/src/components/EmptyState/README.md +1 -1
- package/src/components/Feed/FeedItem.vue +4 -1
- package/src/components/Feed/FeedItemBlock.vue +4 -1
- package/src/components/Feed/README.md +1 -1
- package/src/components/FileUploadButton/FileUploadButton.vue +62 -0
- package/src/components/FileUploadButton/index.ts +1 -0
- package/src/components/Form/Form.vue +7 -2
- package/src/components/Form/README.md +1 -1
- package/src/components/FormLayout/FormLayout.vue +20 -2
- package/src/components/FormLayout/README.md +39 -1
- package/src/components/Heading/Heading.vue +32 -0
- package/src/components/Heading/index.ts +3 -0
- package/src/components/Heading/types.ts +3 -0
- package/src/components/Image/Image.vue +30 -0
- package/src/components/Image/index.ts +1 -0
- package/src/components/InertiaLink/InertiaLink.vue +11 -0
- package/src/components/InertiaLink/index.ts +1 -0
- package/src/components/InlineError/InlineError.vue +21 -0
- package/src/components/InlineError/README.md +63 -0
- package/src/components/InlineError/index.ts +1 -0
- package/src/components/KPICard/KPICard.vue +28 -0
- package/src/components/KPICard/KPICardSection.vue +30 -0
- package/src/components/KPICard/README.md +124 -0
- package/src/components/KPICard/index.ts +2 -0
- package/src/components/Legend/Legend.vue +7 -0
- package/src/components/Legend/LegendItem.vue +34 -0
- package/src/components/Legend/README.md +32 -0
- package/src/components/Legend/index.ts +2 -0
- package/src/components/Link/Link.vue +4 -4
- package/src/components/Link/README.md +1 -1
- package/src/components/Navigation/Navigation.vue +2 -2
- package/src/components/Navigation/NavigationItem.vue +14 -10
- package/src/components/Navigation/NavigationSecondarySection.vue +12 -0
- package/src/components/Navigation/NavigationSection.vue +1 -1
- package/src/components/Navigation/README.md +10 -15
- package/src/components/Navigation/index.ts +1 -0
- package/src/components/Page/Page.vue +2 -33
- package/src/components/Page/PageBody.vue +36 -0
- package/src/components/Page/PageTitle.vue +6 -3
- package/src/components/Page/README.md +45 -39
- package/src/components/Page/index.ts +1 -0
- package/src/components/Pagination/README.md +1 -1
- package/src/components/PinInput/README.md +1 -1
- package/src/components/Popover/Popover.vue +18 -0
- package/src/components/Popover/PopoverBody.vue +11 -0
- package/src/components/Popover/PopoverTrigger.vue +9 -0
- package/src/components/Popover/README.md +34 -6
- package/src/components/Popover/index.ts +3 -0
- package/src/components/Popper/Popper.vue +91 -0
- package/src/components/Popper/PopperBody.vue +19 -0
- package/src/components/Popper/PopperTrigger.vue +14 -0
- package/src/components/Popper/README.md +42 -0
- package/src/components/Popper/index.ts +3 -0
- package/src/components/ProgressBar/ProgressBar.vue +24 -6
- package/src/components/RadioButton/README.md +1 -1
- package/src/components/RadioButton/RadioButton.vue +3 -2
- package/src/components/ResourceList/README.md +160 -0
- package/src/components/ResourceList/ResourceList.vue +7 -0
- package/src/components/ResourceList/ResourceListItem.vue +7 -0
- package/src/components/ResourceList/ResourceListItemContent.vue +7 -0
- package/src/components/ResourceList/index.ts +3 -0
- package/src/components/Select/README.md +1 -1
- package/src/components/Select/Select.vue +1 -1
- package/src/components/Separator/README.md +5 -1
- package/src/components/Separator/Separator.vue +20 -3
- package/src/components/Spinner/README.md +1 -1
- package/src/components/Spinner/Spinner.vue +10 -4
- package/src/components/StatusIndicator/README.md +2 -2
- package/src/components/StatusIndicator/StatusIndicator.vue +11 -5
- package/src/components/Stepper/README.md +38 -0
- package/src/components/Stepper/Stepper.vue +104 -0
- package/src/components/Stepper/index.ts +1 -0
- package/src/components/Tabs/README.md +1 -1
- package/src/components/Tabs/TabTrigger.vue +5 -4
- package/src/components/Tabs/Tabs.vue +4 -1
- package/src/components/Tag/Tag.vue +45 -0
- package/src/components/Tag/index.ts +1 -0
- package/src/components/TextField/README.md +24 -6
- package/src/components/TextField/TextField.vue +25 -5
- package/src/components/TextField/TextFieldIcon.vue +19 -0
- package/src/components/TextStyle/README.md +1 -1
- package/src/components/TextStyle/TextStyle.vue +1 -1
- package/src/components/Toast/DismissToastAction.vue +1 -1
- package/src/components/Toast/README.md +1 -1
- package/src/components/Toggle/README.md +1 -1
- package/src/components/Toggle/Toggle.vue +8 -5
- package/src/components/Tooltip/README.md +1 -1
- package/src/components/Tooltip/Tooltip.vue +15 -41
- package/src/components/TopBar/TopBarSearch.vue +2 -2
- package/src/components/index.ts +68 -12
- package/src/components/types.ts +5 -0
- package/src/composables/useTheme.ts +13 -1
- package/src/composables/useToastNotifications.ts +1 -1
- package/src/composables/useUniqueId.ts +4 -3
- package/src/index.css +17 -13
- package/src/index.ts +0 -11
- package/dist/focus-ui.es.js +0 -33
- package/dist/types/components/Accordion/Accordion.vue.d.ts +0 -32
- package/dist/types/components/Accordion/AccordionItem.vue.d.ts +0 -2
- package/dist/types/components/Accordion/index.d.ts +0 -2
- package/dist/types/components/index.d.ts +0 -1
- package/dist/types/index.d.ts +0 -7
- package/src/components/CategoryBar/CategoryBar.vue +0 -25
- package/src/components/CategoryBar/CategoryBarItem.vue +0 -34
- package/src/components/CategoryBar/README.md +0 -17
- package/src/components/CategoryBar/index.ts +0 -2
- package/src/components/MetricCard/MetricCard.vue +0 -11
- package/src/components/MetricCard/MetricCardLabel.vue +0 -9
- package/src/components/MetricCard/MetricCardSection.vue +0 -11
- package/src/components/MetricCard/README.md +0 -53
- package/src/components/MetricCard/index.ts +0 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { default as Card } from './Card.vue';
|
|
2
|
-
export { default as CardSection } from './CardSection.vue';
|
|
3
2
|
export { default as CardDescription } from './CardDescription.vue';
|
|
4
3
|
export { default as CardFooter } from './CardFooter.vue';
|
|
5
4
|
export { default as CardHeader } from './CardHeader.vue';
|
|
5
|
+
export { default as CardHelp } from './CardHelp.vue';
|
|
6
|
+
export { default as CardSection } from './CardSection.vue';
|
|
6
7
|
export { default as CardTitle } from './CardTitle.vue';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
+
import { computed, ref } from 'vue';
|
|
2
3
|
import { InputLabel } from '../InputLabel';
|
|
3
4
|
import { useUniqueId, useTheme } from '../../composables';
|
|
4
5
|
import { TextStyle } from '../TextStyle';
|
|
@@ -13,29 +14,52 @@ const props = withDefaults(defineProps<{
|
|
|
13
14
|
/** The label for the checkbox. */
|
|
14
15
|
label: string;
|
|
15
16
|
|
|
17
|
+
/** Whether the label is hidden. */
|
|
18
|
+
labelHidden?: boolean;
|
|
19
|
+
|
|
16
20
|
/** The value of the checkbox. */
|
|
17
21
|
value: any;
|
|
18
22
|
}>(), {
|
|
19
23
|
id: null,
|
|
20
24
|
helpText: null,
|
|
25
|
+
labelHidden: false,
|
|
21
26
|
});
|
|
22
27
|
|
|
23
|
-
const
|
|
28
|
+
const $checkboxElement = ref<HTMLInputElement | null>(null);
|
|
29
|
+
|
|
30
|
+
const model = defineModel<boolean | boolean[]>();
|
|
24
31
|
|
|
25
32
|
const elementId = props.id || useUniqueId('checkbox');
|
|
33
|
+
|
|
34
|
+
const elementIsChecked = computed((): boolean | undefined => {
|
|
35
|
+
if (Array.isArray(model.value)) {
|
|
36
|
+
return model.value.includes(props.value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return model.value;
|
|
40
|
+
});
|
|
26
41
|
</script>
|
|
27
42
|
|
|
28
43
|
<template>
|
|
29
|
-
<div
|
|
44
|
+
<div
|
|
45
|
+
:aria-checked="elementIsChecked"
|
|
46
|
+
:aria-labelledby="elementId"
|
|
47
|
+
class="flex items-start space-x-2"
|
|
48
|
+
role="checkbox"
|
|
49
|
+
>
|
|
30
50
|
<input
|
|
31
51
|
:id="elementId"
|
|
52
|
+
ref="$checkboxElement"
|
|
32
53
|
v-model="model"
|
|
33
|
-
:value="value"
|
|
34
|
-
class="appearance-none border-slate-400 shadow-sm border bg-white h-4 w-4 rounded text-brand-500 focus:ring-indigo-600"
|
|
35
54
|
:class="useTheme('focus')"
|
|
55
|
+
:value="value"
|
|
56
|
+
class="h-4 w-4 flex-shrink-0 appearance-none rounded border border-slate-400 bg-white shadow-sm text-brand-500"
|
|
36
57
|
type="checkbox"
|
|
37
58
|
>
|
|
38
|
-
<div
|
|
59
|
+
<div
|
|
60
|
+
v-if="!labelHidden"
|
|
61
|
+
class="-mt-[2px] space-y-1"
|
|
62
|
+
>
|
|
39
63
|
<InputLabel
|
|
40
64
|
:label="label"
|
|
41
65
|
:label-for="elementId"
|
|
@@ -3,7 +3,7 @@ import { FormLayout, Checkbox } from '../../src/components';
|
|
|
3
3
|
import api from '../component-meta/Checkbox.json';
|
|
4
4
|
import { ref } from "vue";
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const checkboxValues = ref([]);
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
9
|
# Checkbox
|
|
@@ -18,24 +18,53 @@ also be used as a way to have users indicate they agree to specific terms and se
|
|
|
18
18
|
<ComponentWrapper>
|
|
19
19
|
<FormLayout>
|
|
20
20
|
<Checkbox
|
|
21
|
-
v-model="
|
|
21
|
+
v-model="checkboxValues"
|
|
22
22
|
value="comments"
|
|
23
23
|
label="Comments"
|
|
24
24
|
help-text="Get notified when someones posts a comment on a posting." />
|
|
25
25
|
<Checkbox
|
|
26
|
-
v-model="
|
|
26
|
+
v-model="checkboxValues"
|
|
27
27
|
value="candidates"
|
|
28
28
|
label="Candidates"
|
|
29
29
|
help-text="Get notified when a candidate applies for a job." />
|
|
30
30
|
<Checkbox
|
|
31
|
-
v-model="
|
|
31
|
+
v-model="checkboxValues"
|
|
32
32
|
value="offers"
|
|
33
33
|
label="Offers"
|
|
34
34
|
help-text="Get notified when a candidate accepts or rejects an offer." />
|
|
35
35
|
</FormLayout>
|
|
36
|
-
<pre class="mt-4">{{
|
|
36
|
+
<pre class="mt-4">{{ checkboxValues }}</pre>
|
|
37
37
|
</ComponentWrapper>
|
|
38
38
|
|
|
39
|
+
```js-vue
|
|
40
|
+
<script lang="ts" setup>
|
|
41
|
+
import { ref } from 'vue';
|
|
42
|
+
import { FormLayout, Checkbox } from '@returnless/focus-ui';
|
|
43
|
+
|
|
44
|
+
const checkboxValues = ref([]);
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<FormLayout>
|
|
49
|
+
<Checkbox
|
|
50
|
+
v-model="checkboxValues"
|
|
51
|
+
value="comments"
|
|
52
|
+
label="Comments"
|
|
53
|
+
help-text="Get notified when someones posts a comment on a posting." />
|
|
54
|
+
<Checkbox
|
|
55
|
+
v-model="checkboxValues"
|
|
56
|
+
value="candidates"
|
|
57
|
+
label="Candidates"
|
|
58
|
+
help-text="Get notified when a candidate applies for a job." />
|
|
59
|
+
<Checkbox
|
|
60
|
+
v-model="checkboxValues"
|
|
61
|
+
value="offers"
|
|
62
|
+
label="Offers"
|
|
63
|
+
help-text="Get notified when a candidate accepts or rejects an offer." />
|
|
64
|
+
</FormLayout>
|
|
65
|
+
</template>
|
|
66
|
+
```
|
|
67
|
+
|
|
39
68
|
## Best practices
|
|
40
69
|
|
|
41
70
|
Checkboxes should:
|
|
@@ -9,14 +9,12 @@ type Day = {
|
|
|
9
9
|
value: Dayjs;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
withDefaults(defineProps<{
|
|
13
|
+
locale?: string;
|
|
14
14
|
}>(), {
|
|
15
|
-
|
|
15
|
+
locale: 'en',
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
const activeRangePick = ref<0 | 1>(0);
|
|
19
|
-
|
|
20
18
|
function getCalendarMonthDays(input: Dayjs): Day[] {
|
|
21
19
|
const startOfMonth = input.startOf('month');
|
|
22
20
|
const days = [];
|
|
@@ -40,32 +38,18 @@ function getCalendarMonthDays(input: Dayjs): Day[] {
|
|
|
40
38
|
return days;
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
const selectedDate = defineModel<string
|
|
41
|
+
const selectedDate = defineModel<string>({
|
|
44
42
|
required: true,
|
|
45
43
|
});
|
|
46
44
|
|
|
47
|
-
const currentDate = ref(dayjs(
|
|
45
|
+
const currentDate = ref(dayjs(selectedDate.value));
|
|
48
46
|
|
|
49
47
|
const days = computed(() => {
|
|
50
48
|
return getCalendarMonthDays(dayjs(currentDate.value));
|
|
51
49
|
});
|
|
52
50
|
|
|
53
|
-
function isRangePicker(): boolean {
|
|
54
|
-
return props.rangePicker;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
51
|
function selectDate(date: Day): void {
|
|
58
|
-
|
|
59
|
-
const temporarySelectedDate = selectedDate.value as string[];
|
|
60
|
-
|
|
61
|
-
temporarySelectedDate[activeRangePick.value] = date.value.format('YYYY-MM-DD');
|
|
62
|
-
|
|
63
|
-
activeRangePick.value = activeRangePick.value === 0 ? 1 : 0;
|
|
64
|
-
|
|
65
|
-
selectedDate.value = temporarySelectedDate;
|
|
66
|
-
} else {
|
|
67
|
-
selectedDate.value = date.value.format('YYYY-MM-DD');
|
|
68
|
-
}
|
|
52
|
+
selectedDate.value = date.value.format('YYYY-MM-DD');
|
|
69
53
|
}
|
|
70
54
|
|
|
71
55
|
function selectPreviousMonth(): void {
|
|
@@ -76,11 +60,7 @@ function selectNextMonth(): void {
|
|
|
76
60
|
}
|
|
77
61
|
|
|
78
62
|
function isSelected(date: Day): boolean {
|
|
79
|
-
return
|
|
80
|
-
? selectedDate.value.includes(date.value.format('YYYY-MM-DD'))
|
|
81
|
-
: selectedDate.value === date.value.format('YYYY-MM-DD');
|
|
82
|
-
|
|
83
|
-
// return dayjs(selectedDate.value).format('YYYY-MM-DD') === date.value.format('YYYY-MM-DD');
|
|
63
|
+
return dayjs(selectedDate.value).format('YYYY-MM-DD') === date.value.format('YYYY-MM-DD');
|
|
84
64
|
}
|
|
85
65
|
|
|
86
66
|
function isToday(date: Day): boolean {
|
|
@@ -3,7 +3,7 @@ import { computed, inject } from 'vue';
|
|
|
3
3
|
|
|
4
4
|
const descriptionListAlignment = inject<'horizontal' | 'vertical'>('descriptionListAlignment');
|
|
5
5
|
|
|
6
|
-
const classList = computed(() => {
|
|
6
|
+
const classList = computed((): Record<string, boolean>[] => {
|
|
7
7
|
return [
|
|
8
8
|
{ 'grid-cols-4 gap-4 py-3': descriptionListAlignment === 'horizontal' },
|
|
9
9
|
{ 'py-2': descriptionListAlignment === 'vertical' },
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
DescriptionListDescription,
|
|
46
46
|
DescriptionListItem,
|
|
47
47
|
DescriptionListTerm,
|
|
48
|
-
} from 'focus-ui';
|
|
48
|
+
} from '@returnless/focus-ui';
|
|
49
49
|
</script>
|
|
50
50
|
|
|
51
51
|
<template>
|
|
@@ -100,7 +100,7 @@ import {
|
|
|
100
100
|
DescriptionListDescription,
|
|
101
101
|
DescriptionListItem,
|
|
102
102
|
DescriptionListTerm,
|
|
103
|
-
} from 'focus-ui';
|
|
103
|
+
} from '@returnless/focus-ui';
|
|
104
104
|
</script>
|
|
105
105
|
|
|
106
106
|
<template>
|
|
File without changes
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { useFileDialog, useDropZone } from '@vueuse/core';
|
|
3
|
+
import { Button } from '../Button';
|
|
4
|
+
import { computed, ref } from 'vue';
|
|
5
|
+
import { FileAccepts } from '../types';
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<{
|
|
8
|
+
/** The file types to accept. */
|
|
9
|
+
accepts: FileAccepts;
|
|
10
|
+
|
|
11
|
+
label: string;
|
|
12
|
+
|
|
13
|
+
/** The label for the action button. */
|
|
14
|
+
actionLabel: string;
|
|
15
|
+
|
|
16
|
+
/** Whether to accept multiple files. */
|
|
17
|
+
multiple?: boolean;
|
|
18
|
+
}>(), {
|
|
19
|
+
accepts: '*',
|
|
20
|
+
multiple: false,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const model = defineModel<File[]>({
|
|
24
|
+
default: [],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const dropZoneRef = ref<HTMLDivElement>();
|
|
28
|
+
|
|
29
|
+
const acceptTypes = computed(() => {
|
|
30
|
+
switch (props.accepts) {
|
|
31
|
+
case 'image':
|
|
32
|
+
return 'image/*';
|
|
33
|
+
case 'pdf':
|
|
34
|
+
return 'application/pdf';
|
|
35
|
+
default:
|
|
36
|
+
return '*';
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const { open, onChange } = useFileDialog({
|
|
41
|
+
accept: acceptTypes.value,
|
|
42
|
+
multiple: props.multiple,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const { isOverDropZone } = useDropZone(dropZoneRef, {
|
|
46
|
+
onDrop: onFileUpload,
|
|
47
|
+
// specify the types of data to be received.
|
|
48
|
+
dataTypes: (types: readonly string[]) => {
|
|
49
|
+
if (props.accepts === '*') {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (props.accepts === 'image') {
|
|
54
|
+
return types.some((type) => type.startsWith('image'));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return types.includes(props.accepts);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
onChange((files: FileList | null): void => {
|
|
62
|
+
const listOfFiles: File[] = Array.from(files || []);
|
|
63
|
+
|
|
64
|
+
onFileUpload(listOfFiles);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
function onFileUpload(files: File[] | null): void {
|
|
68
|
+
if (files === null) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
model.value = props.multiple
|
|
73
|
+
? [...model.value, ...files]
|
|
74
|
+
: [files[0]];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const classList = computed((): Record<string, boolean>[] => {
|
|
78
|
+
return [
|
|
79
|
+
{ 'bg-blue-500': isOverDropZone.value },
|
|
80
|
+
];
|
|
81
|
+
});
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<template>
|
|
85
|
+
<button
|
|
86
|
+
ref="dropZoneRef"
|
|
87
|
+
:class="classList"
|
|
88
|
+
class="block w-full cursor-pointer rounded border border-dashed bg-white hover:border-solid hover:bg-slate-50"
|
|
89
|
+
@click="() => open()"
|
|
90
|
+
>
|
|
91
|
+
<span class="flex flex-col justify-center px-4 py-12 space-y-4">
|
|
92
|
+
<span>
|
|
93
|
+
<Button variant="secondary">
|
|
94
|
+
{{ actionLabel }}
|
|
95
|
+
</Button>
|
|
96
|
+
</span>
|
|
97
|
+
<span
|
|
98
|
+
v-if="label"
|
|
99
|
+
class="block"
|
|
100
|
+
>
|
|
101
|
+
{{ label }}
|
|
102
|
+
</span>
|
|
103
|
+
</span>
|
|
104
|
+
</button>
|
|
105
|
+
</template>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Drop zone
|
|
2
|
+
|
|
3
|
+
The drop zone component lets users upload files by dragging and dropping the files into an area on a page, or activating
|
|
4
|
+
a button.
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
|
|
8
|
+
## Best practices
|
|
9
|
+
|
|
10
|
+
Drop zones should:
|
|
11
|
+
|
|
12
|
+
- Inform users when the file(s) can't be uploaded.
|
|
13
|
+
- When possible, use validation errors on drag to detect and explain things like file size limits or file types
|
|
14
|
+
accepted.
|
|
15
|
+
- Use the banner component with a critical status to communicate errors that happen on the server.
|
|
16
|
+
- Provide feedback once the file(s) have been dropped and uploading begins.
|
|
17
|
+
- Provide a file upload button to allow users to select files for upload in traditional way.
|
|
18
|
+
|
|
19
|
+
### Validation errors
|
|
20
|
+
|
|
21
|
+
The drop zone component validates file type by default. File types you wish to accept can be defined by editing the
|
|
22
|
+
`accept` property. This component also accepts custom validations using the `customValidator` property. When
|
|
23
|
+
validation fails, the components sets itself to error mode.
|
|
24
|
+
|
|
25
|
+
## Content guidelines
|
|
26
|
+
|
|
27
|
+
### Client-side validation error messages
|
|
28
|
+
|
|
29
|
+
Validation error messages should be:
|
|
30
|
+
|
|
31
|
+
- Explicit: help users understand why their file can't be uploaded and what they should change to successfully upload
|
|
32
|
+
their file
|
|
33
|
+
- In sentence case: capitalize only the first word in a message
|
|
34
|
+
- Concise: use simple, clear language that can be read at a glance: For example:
|
|
35
|
+
- `File size must be less than 20MB`
|
|
36
|
+
- `File type must be .pdf, .doc, or .docx`
|
|
37
|
+
|
|
38
|
+
## Accessibility
|
|
39
|
+
|
|
40
|
+
The drop zone component builds on the native HTML `<input type="upload" />` element. It includes a visual `<button>`
|
|
41
|
+
as well as a drag and drop area that can receive keyboard focus.
|
|
42
|
+
|
|
43
|
+
## Keyboard support
|
|
44
|
+
|
|
45
|
+
To upload a file with the keyboard, users can interact with the drag-and-drop region.
|
|
46
|
+
|
|
47
|
+
- To give the input keyboard focus, use the tab key (or shift + tab when tabbing backwards)
|
|
48
|
+
- To activate the input, use the enter/return or space keys
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as DropZone } from './DropZone.vue';
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
</script>
|
|
3
3
|
|
|
4
4
|
<template>
|
|
5
|
-
<li
|
|
5
|
+
<li
|
|
6
|
+
class="relative flex gap-x-2 [&:not(:last-child)]:pb-4"
|
|
7
|
+
role="listitem"
|
|
8
|
+
>
|
|
6
9
|
<div class="absolute top-0 -bottom-0 left-0 flex w-6 justify-center">
|
|
7
10
|
<div class="w-px bg-slate-200" />
|
|
8
11
|
</div>
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
</script>
|
|
3
3
|
|
|
4
4
|
<template>
|
|
5
|
-
<div
|
|
5
|
+
<div
|
|
6
|
+
class="flex-auto rounded border bg-white/50 px-3 py-2 text-xs leading-5 shadow-sm"
|
|
7
|
+
role="note"
|
|
8
|
+
>
|
|
6
9
|
<slot />
|
|
7
10
|
</div>
|
|
8
11
|
</template>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import { useFileDialog } from '@vueuse/core';
|
|
4
|
+
import { Button, ButtonProps } from '../Button';
|
|
5
|
+
import { FileAccepts } from '../types';
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<ButtonProps & {
|
|
8
|
+
/** The file types to accept. */
|
|
9
|
+
accepts: FileAccepts;
|
|
10
|
+
|
|
11
|
+
/** Whether to accept multiple files. */
|
|
12
|
+
multiple?: boolean;
|
|
13
|
+
}>(), {
|
|
14
|
+
accepts: '*',
|
|
15
|
+
multiple: false,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const model = defineModel<File[]>({
|
|
19
|
+
default: [],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const acceptTypes = computed(() => {
|
|
23
|
+
switch (props.accepts) {
|
|
24
|
+
case 'image':
|
|
25
|
+
return 'image/*';
|
|
26
|
+
case 'pdf':
|
|
27
|
+
return 'application/pdf';
|
|
28
|
+
default:
|
|
29
|
+
return '*';
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const { open, onChange } = useFileDialog({
|
|
34
|
+
accept: acceptTypes.value,
|
|
35
|
+
multiple: props.multiple,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
onChange((files: FileList | null): void => {
|
|
39
|
+
const listOfFiles: File[] = Array.from(files || []);
|
|
40
|
+
|
|
41
|
+
onFileUpload(listOfFiles);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function onFileUpload(files: File[] | null): void {
|
|
45
|
+
if (files === null) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
model.value = props.multiple
|
|
50
|
+
? [...model.value, ...files]
|
|
51
|
+
: [files[0]];
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<Button
|
|
57
|
+
v-bind="$props"
|
|
58
|
+
@click="open"
|
|
59
|
+
>
|
|
60
|
+
<slot />
|
|
61
|
+
</Button>
|
|
62
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as FileUploadButton } from './FileUploadButton.vue';
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
import { useUniqueId } from '../../composables';
|
|
3
3
|
|
|
4
4
|
const props = withDefaults(defineProps<{
|
|
5
|
+
/** The aria-label attribute to be applied to the link */
|
|
6
|
+
accessibilityLabel?: string;
|
|
7
|
+
|
|
5
8
|
/** The encoding type for the form. */
|
|
6
|
-
enctype?: 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain'
|
|
9
|
+
enctype?: 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain';
|
|
7
10
|
|
|
8
11
|
/** The ID of the form. */
|
|
9
12
|
id?: string | null;
|
|
10
13
|
}>(), {
|
|
11
|
-
|
|
14
|
+
accessibilityLabel: undefined,
|
|
15
|
+
enctype: undefined,
|
|
12
16
|
id: null,
|
|
13
17
|
});
|
|
14
18
|
|
|
@@ -22,6 +26,7 @@ const elementId = props.id || useUniqueId('form');
|
|
|
22
26
|
<template>
|
|
23
27
|
<form
|
|
24
28
|
:id="elementId"
|
|
29
|
+
:aria-label="accessibilityLabel"
|
|
25
30
|
:enctype="enctype"
|
|
26
31
|
@submit.prevent="$emit('submit')"
|
|
27
32
|
>
|
|
@@ -21,7 +21,7 @@ A wrapper component that handles the submission of forms.
|
|
|
21
21
|
|
|
22
22
|
```js-vue
|
|
23
23
|
<script lang="ts" setup>
|
|
24
|
-
import { Form, FormLayout, TextField, Button } from 'focus-ui';
|
|
24
|
+
import { Form, FormLayout, TextField, Button } from '@returnless/focus-ui';
|
|
25
25
|
</script>
|
|
26
26
|
|
|
27
27
|
<template>
|
|
@@ -1,7 +1,25 @@
|
|
|
1
|
-
<script lang="ts" setup
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(defineProps<{
|
|
5
|
+
columns: number;
|
|
6
|
+
}>(), {
|
|
7
|
+
columns: 1,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const classList = computed((): Record<string, boolean>[] => {
|
|
11
|
+
return [
|
|
12
|
+
{ 'md:grid-cols-1': props.columns === 1 },
|
|
13
|
+
{ 'md:grid-cols-2': props.columns === 2 },
|
|
14
|
+
];
|
|
15
|
+
});
|
|
16
|
+
</script>
|
|
2
17
|
|
|
3
18
|
<template>
|
|
4
|
-
<div
|
|
19
|
+
<div
|
|
20
|
+
class="grid grid-cols-1 gap-4"
|
|
21
|
+
:class="classList"
|
|
22
|
+
>
|
|
5
23
|
<slot />
|
|
6
24
|
</div>
|
|
7
25
|
</template>
|
|
@@ -19,7 +19,7 @@ support horizontal groups of fields.
|
|
|
19
19
|
|
|
20
20
|
```js-vue
|
|
21
21
|
<script lang="ts" setup>
|
|
22
|
-
import { FormLayout, TextField } from 'focus-ui';
|
|
22
|
+
import { FormLayout, TextField } from '@returnless/focus-ui';
|
|
23
23
|
</script>
|
|
24
24
|
|
|
25
25
|
<template>
|
|
@@ -30,6 +30,44 @@ import { FormLayout, TextField } from 'focus-ui';
|
|
|
30
30
|
</template>
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
### Horizontal form layout
|
|
34
|
+
|
|
35
|
+
<ComponentWrapper>
|
|
36
|
+
<FormLayout :columns="2">
|
|
37
|
+
<TextField label="Company name" />
|
|
38
|
+
<TextField label="Invoice email" />
|
|
39
|
+
<TextField label="Street + house number" />
|
|
40
|
+
<FormLayout :columns="2">
|
|
41
|
+
<TextField label="Postcode" />
|
|
42
|
+
<TextField label="City" />
|
|
43
|
+
</FormLayout>
|
|
44
|
+
<TextField label="Country" />
|
|
45
|
+
<TextField label="VAT number" />
|
|
46
|
+
<TextField label="P.O. number / reference" />
|
|
47
|
+
</FormLayout>
|
|
48
|
+
</ComponentWrapper>
|
|
49
|
+
|
|
50
|
+
```js-vue
|
|
51
|
+
<script lang="ts" setup>
|
|
52
|
+
import { FormLayout, TextField } from '@returnless/focus-ui';
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<FormLayout :columns="2">
|
|
57
|
+
<TextField label="Company name" />
|
|
58
|
+
<TextField label="Invoice email" />
|
|
59
|
+
<TextField label="Street + house number" />
|
|
60
|
+
<FormLayout :columns="2">
|
|
61
|
+
<TextField label="Postcode" />
|
|
62
|
+
<TextField label="City" />
|
|
63
|
+
</FormLayout>
|
|
64
|
+
<TextField label="Country" />
|
|
65
|
+
<TextField label="VAT number" />
|
|
66
|
+
<TextField label="P.O. number / reference" />
|
|
67
|
+
</FormLayout>
|
|
68
|
+
</template>
|
|
69
|
+
```
|
|
70
|
+
|
|
33
71
|
## Best practices
|
|
34
72
|
|
|
35
73
|
Forms should:
|