@aerogel/core 0.0.0-next.c2e6acc000e97a1020c2e232678563c53884dd0e → 0.0.0-next.c33ad773d3eb977461630ff22012d99eeedf46cb

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 (79) hide show
  1. package/dist/aerogel-core.d.ts +1025 -429
  2. package/dist/aerogel-core.js +1601 -1440
  3. package/dist/aerogel-core.js.map +1 -1
  4. package/package.json +1 -1
  5. package/src/components/AppModals.vue +1 -1
  6. package/src/components/AppOverlays.vue +2 -7
  7. package/src/components/AppToasts.vue +16 -0
  8. package/src/components/contracts/Button.ts +1 -0
  9. package/src/components/contracts/DropdownMenu.ts +11 -0
  10. package/src/components/contracts/Input.ts +4 -4
  11. package/src/components/contracts/Modal.ts +4 -0
  12. package/src/components/contracts/Select.ts +33 -0
  13. package/src/components/contracts/Toast.ts +13 -0
  14. package/src/components/contracts/index.ts +2 -0
  15. package/src/components/headless/HeadlessButton.vue +7 -2
  16. package/src/components/headless/HeadlessInputDescription.vue +1 -1
  17. package/src/components/headless/HeadlessInputInput.vue +3 -3
  18. package/src/components/headless/HeadlessInputTextArea.vue +2 -2
  19. package/src/components/headless/HeadlessModal.vue +4 -2
  20. package/src/components/headless/HeadlessModalOverlay.vue +2 -2
  21. package/src/components/headless/HeadlessModalTitle.vue +2 -2
  22. package/src/components/headless/HeadlessSelect.vue +92 -0
  23. package/src/components/headless/{forms/AGHeadlessSelectError.vue → HeadlessSelectError.vue} +3 -4
  24. package/src/components/headless/HeadlessSelectLabel.vue +25 -0
  25. package/src/components/headless/HeadlessSelectOption.vue +34 -0
  26. package/src/components/headless/HeadlessSelectOptions.vue +30 -0
  27. package/src/components/headless/HeadlessSelectTrigger.vue +22 -0
  28. package/src/components/headless/HeadlessSelectValue.vue +15 -0
  29. package/src/components/headless/HeadlessToast.vue +18 -0
  30. package/src/components/headless/HeadlessToastAction.vue +13 -0
  31. package/src/components/headless/index.ts +7 -3
  32. package/src/components/index.ts +2 -8
  33. package/src/components/ui/AdvancedOptions.vue +18 -0
  34. package/src/components/ui/AlertModal.vue +1 -1
  35. package/src/components/ui/Button.vue +54 -14
  36. package/src/components/ui/Checkbox.vue +17 -10
  37. package/src/components/ui/ConfirmModal.vue +3 -3
  38. package/src/components/ui/DropdownMenu.vue +33 -0
  39. package/src/components/ui/EditableContent.vue +82 -0
  40. package/src/components/{lib/AGErrorMessage.vue → ui/ErrorMessage.vue} +2 -3
  41. package/src/components/ui/ErrorReportModal.vue +1 -1
  42. package/src/components/ui/ErrorReportModalButtons.vue +6 -8
  43. package/src/components/ui/ErrorReportModalTitle.vue +1 -1
  44. package/src/components/ui/Input.vue +8 -4
  45. package/src/components/ui/Link.vue +2 -2
  46. package/src/components/ui/LoadingModal.vue +3 -3
  47. package/src/components/ui/Markdown.vue +10 -3
  48. package/src/components/ui/Modal.vue +23 -8
  49. package/src/components/ui/PromptModal.vue +4 -4
  50. package/src/components/ui/Select.vue +53 -0
  51. package/src/components/ui/Toast.vue +42 -0
  52. package/src/components/ui/index.ts +7 -0
  53. package/src/components/utils.ts +4 -4
  54. package/src/errors/Errors.ts +4 -5
  55. package/src/index.css +33 -0
  56. package/src/ui/UI.state.ts +2 -2
  57. package/src/ui/UI.ts +12 -20
  58. package/src/ui/index.ts +4 -4
  59. package/src/utils/vue.ts +0 -4
  60. package/src/components/AppSnackbars.vue +0 -13
  61. package/src/components/constants.ts +0 -8
  62. package/src/components/forms/AGSelect.story.vue +0 -46
  63. package/src/components/forms/AGSelect.vue +0 -54
  64. package/src/components/forms/index.ts +0 -1
  65. package/src/components/headless/forms/AGHeadlessSelect.ts +0 -42
  66. package/src/components/headless/forms/AGHeadlessSelect.vue +0 -77
  67. package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -4
  68. package/src/components/headless/forms/AGHeadlessSelectOption.vue +0 -31
  69. package/src/components/headless/forms/AGHeadlessSelectOptions.vue +0 -19
  70. package/src/components/headless/forms/AGHeadlessSelectTrigger.vue +0 -25
  71. package/src/components/headless/forms/composition.ts +0 -10
  72. package/src/components/headless/forms/index.ts +0 -8
  73. package/src/components/headless/snackbars/AGHeadlessSnackbar.vue +0 -10
  74. package/src/components/headless/snackbars/index.ts +0 -40
  75. package/src/components/lib/AGMeasured.vue +0 -16
  76. package/src/components/lib/index.ts +0 -3
  77. package/src/components/snackbars/AGSnackbar.vue +0 -38
  78. package/src/components/snackbars/index.ts +0 -3
  79. /package/src/components/{lib/AGStartupCrash.vue → ui/StartupCrash.vue} +0 -0
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <HeadlessButton :class="renderedClasses" v-bind="props">
2
+ <HeadlessButton :class="renderedClasses" :disabled v-bind="props">
3
3
  <slot />
4
4
  </HeadlessButton>
5
5
  </template>
@@ -10,29 +10,33 @@ import { computedVariantClasses } from '@aerogel/core/components/utils';
10
10
  import type { ButtonProps } from '@aerogel/core/components/contracts/Button';
11
11
  import type { Variants } from '@aerogel/core/components/utils';
12
12
 
13
- const { class: baseClasses, size, variant, ...props } = defineProps<ButtonProps>();
13
+ const { class: baseClasses, size, variant, disabled, ...props } = defineProps<ButtonProps>();
14
14
 
15
15
  /* eslint-disable vue/max-len */
16
16
  // prettier-ignore
17
- const renderedClasses = computedVariantClasses<Variants<Pick<ButtonProps, 'size' | 'variant'>>>(
18
- { baseClasses, variant, size },
17
+ const renderedClasses = computedVariantClasses<Variants<Pick<ButtonProps, 'size' | 'variant' | 'disabled'>>>(
18
+ { baseClasses, variant, size, disabled },
19
19
  {
20
- baseClasses: 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
20
+ baseClasses: 'flex items-center justify-center gap-1 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
21
21
  variants: {
22
22
  variant: {
23
- default: 'bg-primary text-white hover:bg-primary/90 focus-visible:outline-primary',
24
- secondary: 'bg-background text-gray-900 ring-gray-300 hover:bg-accent',
25
- danger: 'bg-danger text-white hover:bg-danger/80 focus-visible:outline-danger',
26
- ghost: 'bg-background hover:bg-accent',
27
- outline: 'bg-background text-primary ring-primary hover:bg-accent',
28
- link: 'text-primary hover:underline',
23
+ default: 'bg-primary text-white focus-visible:outline-primary',
24
+ secondary: 'bg-background text-gray-900 ring-gray-300',
25
+ danger: 'bg-danger text-white focus-visible:outline-danger',
26
+ ghost: 'bg-transparent',
27
+ outline: 'bg-transparent text-primary ring-primary',
28
+ link: 'text-primary',
29
29
  },
30
30
  size: {
31
- small: 'rounded px-2 py-1 text-xs',
32
- default: 'rounded-md px-2.5 py-1.5 text-sm',
33
- large: 'rounded-md px-3 py-2 text-base',
31
+ small: 'text-xs',
32
+ default: 'text-sm',
33
+ large: 'text-base',
34
34
  icon: 'rounded-full p-2.5',
35
35
  },
36
+ disabled: {
37
+ false: null,
38
+ true: 'opacity-50 cursor-not-allowed',
39
+ },
36
40
  },
37
41
  compoundVariants: [
38
42
  {
@@ -47,10 +51,46 @@ const renderedClasses = computedVariantClasses<Variants<Pick<ButtonProps, 'size'
47
51
  variant: ['secondary', 'outline'],
48
52
  class: 'ring-1 ring-inset',
49
53
  },
54
+ {
55
+ variant: ['default', 'secondary', 'danger', 'ghost', 'outline'],
56
+ size: 'small',
57
+ class: 'rounded px-2 py-1',
58
+ },
59
+ {
60
+ variant: ['default', 'secondary', 'danger', 'ghost', 'outline'],
61
+ size: 'default',
62
+ class: 'rounded-md px-2.5 py-1.5',
63
+ },
64
+ {
65
+ variant: ['default', 'secondary', 'danger', 'ghost', 'outline'],
66
+ size: 'large',
67
+ class: 'rounded-md px-3 py-2',
68
+ },
69
+ {
70
+ variant: 'default',
71
+ disabled: false,
72
+ class: 'hover:bg-primary/90',
73
+ },
74
+ {
75
+ variant: ['secondary', 'ghost', 'outline'],
76
+ disabled: false,
77
+ class: 'hover:bg-accent',
78
+ },
79
+ {
80
+ variant: 'danger',
81
+ disabled: false,
82
+ class: 'hover:bg-danger/80',
83
+ },
84
+ {
85
+ variant: 'link',
86
+ disabled: false,
87
+ class: 'hover:underline',
88
+ },
50
89
  ],
51
90
  defaultVariants: {
52
91
  variant: 'default',
53
92
  size: 'default',
93
+ disabled: false,
54
94
  },
55
95
  },
56
96
  );
@@ -8,33 +8,39 @@
8
8
  <div class="flex h-6 items-center">
9
9
  <HeadlessInputInput v-bind="inputAttrs" type="checkbox" :class="renderedInputClasses" />
10
10
  </div>
11
- <div v-if="$slots.default" class="ml-2 text-sm leading-6">
11
+ <div v-if="$slots.default" :class="renderedLabelClasses">
12
12
  <HeadlessInputLabel class="text-gray-900">
13
13
  <slot />
14
14
  </HeadlessInputLabel>
15
- <HeadlessInputError class="text-sm text-red-600" />
15
+ <HeadlessInputError class="text-red-600" />
16
16
  </div>
17
- <div v-else-if="label" class="ml-2 text-sm leading-6">
18
- <HeadlessInputLabel />
19
- <HeadlessInputError class="text-sm text-red-600" />
17
+ <div v-else-if="label" :class="renderedLabelClasses">
18
+ <HeadlessInputLabel class="text-gray-900" />
19
+ <HeadlessInputError class="text-red-600" />
20
20
  </div>
21
21
  </HeadlessInput>
22
22
  </template>
23
23
 
24
24
  <script setup lang="ts">
25
- import { computed } from 'vue';
25
+ import { computed, useTemplateRef } from 'vue';
26
26
  import type { HTMLAttributes } from 'vue';
27
27
 
28
+ import HeadlessInput from '@aerogel/core/components/headless/HeadlessInput.vue';
29
+ import HeadlessInputError from '@aerogel/core/components/headless/HeadlessInputError.vue';
30
+ import HeadlessInputInput from '@aerogel/core/components/headless/HeadlessInputInput.vue';
31
+ import HeadlessInputLabel from '@aerogel/core/components/headless/HeadlessInputLabel.vue';
28
32
  import { classes } from '@aerogel/core/components/utils';
29
33
  import { useInputAttrs } from '@aerogel/core/utils/composition/forms';
30
- import { componentRef } from '@aerogel/core/utils/vue';
31
- import type { InputEmits, InputExpose, InputProps } from '@aerogel/core/components/contracts/Input';
34
+ import type { InputEmits, InputProps } from '@aerogel/core/components/contracts/Input';
32
35
 
33
36
  defineOptions({ inheritAttrs: false });
34
37
  defineEmits<InputEmits>();
35
- const { inputClass, ...props } = defineProps<InputProps & { inputClass?: HTMLAttributes['class'] }>();
36
38
 
37
- const $input = componentRef<InputExpose>();
39
+ const { inputClass, labelClass, ...props } = defineProps<
40
+ InputProps & { inputClass?: HTMLAttributes['class']; labelClass?: HTMLAttributes['class'] }
41
+ >();
42
+
43
+ const $input = useTemplateRef('$input');
38
44
  const [inputAttrs, rootClasses] = useInputAttrs();
39
45
  const renderedClasses = computed(() => classes('relative flex items-start', rootClasses.value));
40
46
  const renderedInputClasses = computed(() =>
@@ -46,4 +52,5 @@ const renderedInputClasses = computed(() =>
46
52
  },
47
53
  inputClass,
48
54
  ));
55
+ const renderedLabelClasses = computed(() => classes('ml-2 text-sm leading-6', labelClass));
49
56
  </script>
@@ -1,7 +1,7 @@
1
1
  <template>
2
- <Modal v-slot="{ close }" :title="title" persistent>
3
- <Form :form="form" @submit="close([true, form.data()])">
4
- <Markdown :text="message" :actions="actions" />
2
+ <Modal v-slot="{ close }" :title persistent>
3
+ <Form :form @submit="close([true, form.data()])">
4
+ <Markdown :text="message" :actions />
5
5
 
6
6
  <ul v-if="checkboxes" class="mt-4 flex flex-col text-sm text-gray-600">
7
7
  <li v-for="(checkbox, name) of checkboxes" :key="name">
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <DropdownMenuRoot>
3
+ <DropdownMenuTrigger>
4
+ <slot />
5
+ </DropdownMenuTrigger>
6
+ <DropdownMenuPortal>
7
+ <DropdownMenuContent class="gap-y-0.5 rounded-lg bg-white p-1.5 shadow-lg ring-1 ring-black/5" :align>
8
+ <DropdownMenuItem
9
+ v-for="(option, key) in options"
10
+ :key
11
+ class="flex w-full items-center rounded-lg px-2 py-2 text-sm text-gray-900 data-[highlighted]:bg-gray-100"
12
+ @select="option.click"
13
+ >
14
+ {{ option.label }}
15
+ </DropdownMenuItem>
16
+ </DropdownMenuContent>
17
+ </DropdownMenuPortal>
18
+ </DropdownMenuRoot>
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ import {
23
+ DropdownMenuContent,
24
+ DropdownMenuItem,
25
+ DropdownMenuPortal,
26
+ DropdownMenuRoot,
27
+ DropdownMenuTrigger,
28
+ } from 'reka-ui';
29
+
30
+ import type { DropdownMenuProps } from '@aerogel/core/components/contracts/DropdownMenu';
31
+
32
+ defineProps<DropdownMenuProps>();
33
+ </script>
@@ -0,0 +1,82 @@
1
+ <template>
2
+ <div class="relative" :class="{ 'pointer-events-none!': disabled && !editing }">
3
+ <div v-if="!editing" :class="renderedContentClass">
4
+ <slot />
5
+ </div>
6
+ <span v-else :class="renderedFillerClass">
7
+ {{ draft }}
8
+ </span>
9
+ <span v-if="type === 'number'" class="inline-block transition-[width]" :class="editing ? 'w-5' : 'w-0'" />
10
+ <form class="w-full" :aria-hidden="formAriaHidden" @submit.prevent="$input?.blur()">
11
+ <input
12
+ ref="$input"
13
+ v-model="draft"
14
+ :tabindex="tabindex ?? undefined"
15
+ :aria-label="ariaLabel ?? undefined"
16
+ :type
17
+ :class="[
18
+ renderedInputClass,
19
+ { 'opacity-0': !editing, 'appearance-textfield': !editing && type === 'number' },
20
+ ]"
21
+ @keyup="$emit('update', draft)"
22
+ @focus="startEditing()"
23
+ @blur="stopEditing()"
24
+ >
25
+ </form>
26
+ </div>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import { computed, ref, watchEffect } from 'vue';
31
+ import type { HTMLAttributes } from 'vue';
32
+
33
+ import { classes } from '@aerogel/core/components/utils';
34
+
35
+ const emit = defineEmits<{ update: [value: string | number]; save: [] }>();
36
+ const {
37
+ type = 'text',
38
+ text,
39
+ contentClass,
40
+ ariaLabel,
41
+ formAriaHidden,
42
+ tabindex,
43
+ disabled,
44
+ } = defineProps<{
45
+ type?: string;
46
+ contentClass?: HTMLAttributes['class'];
47
+ ariaLabel?: string;
48
+ formAriaHidden?: boolean;
49
+ tabindex?: string;
50
+ text: string;
51
+ disabled?: boolean;
52
+ }>();
53
+ const $input = ref<HTMLElement>();
54
+ const editing = ref<string | null>(null);
55
+ const draft = ref(text);
56
+ const renderedContentClass = computed(() => classes('inline whitespace-pre', contentClass));
57
+ const renderedFillerClass = computed(() => classes('invisible whitespace-pre', contentClass));
58
+ const renderedInputClass = computed(() =>
59
+ classes('absolute inset-0 h-full w-full resize-none border-0 bg-transparent p-0 focus:ring-0', contentClass));
60
+
61
+ function startEditing() {
62
+ editing.value = text;
63
+ }
64
+
65
+ function stopEditing() {
66
+ if (!editing.value) {
67
+ return;
68
+ }
69
+
70
+ if (type !== 'number' && draft.value.trim().length === 0) {
71
+ draft.value = editing.value;
72
+
73
+ emit('update', draft.value);
74
+ }
75
+
76
+ editing.value = null;
77
+
78
+ emit('save');
79
+ }
80
+
81
+ watchEffect(() => (draft.value = text));
82
+ </script>
@@ -5,12 +5,11 @@
5
5
  <script setup lang="ts">
6
6
  import { computed } from 'vue';
7
7
 
8
- import { requiredObjectProp } from '@aerogel/core/utils/vue';
9
8
  import { getErrorMessage } from '@aerogel/core/errors/utils';
10
9
  import type { ErrorSource } from '@aerogel/core/errors/Errors.state';
11
10
 
12
11
  import Markdown from '@aerogel/core/components/ui/Markdown.vue';
13
12
 
14
- const props = defineProps({ error: requiredObjectProp<ErrorSource>() });
15
- const message = computed(() => getErrorMessage(props.error));
13
+ const { error } = defineProps<{ error: ErrorSource }>();
14
+ const message = computed(() => getErrorMessage(error));
16
15
  </script>
@@ -33,7 +33,7 @@
33
33
  </Button>
34
34
  </span>
35
35
  </div>
36
- <ErrorReportModalButtons :report="report" class="gap-0.5" />
36
+ <ErrorReportModalButtons :report class="gap-0.5" />
37
37
  </h2>
38
38
  <Markdown v-if="report.description" :text="report.description" class="text-gray-600" />
39
39
  </div>
@@ -7,7 +7,7 @@
7
7
  class="group whitespace-nowrap"
8
8
  :href="button.url"
9
9
  :title="$td(`errors.report_${button.id}`, button.description)"
10
- @click="button.handler"
10
+ @click="button.click"
11
11
  >
12
12
  <span class="sr-only">{{ $td(`errors.report_${button.id}`, button.description) }}</span>
13
13
  <component :is="button.iconComponent" class="size-4" aria-hidden="true" />
@@ -36,7 +36,7 @@ interface ErrorReportModalButtonsDefaultSlotProps {
36
36
  description: string;
37
37
  iconComponent: Component;
38
38
  url?: string;
39
- handler?(): void;
39
+ click?(): void;
40
40
  }
41
41
 
42
42
  defineSlots<{
@@ -75,19 +75,17 @@ const buttons = computed(() =>
75
75
  id: 'clipboard',
76
76
  description: 'Copy to clipboard',
77
77
  iconComponent: IconCopy,
78
- async handler() {
78
+ async click() {
79
79
  await navigator.clipboard.writeText(`${summary.value}\n\n${props.report.details}`);
80
80
 
81
- UI.showSnackbar(
82
- translateWithDefault('errors.copiedToClipboard', 'Debug information copied to clipboard'),
83
- );
81
+ UI.toast(translateWithDefault('errors.copiedToClipboard', 'Debug information copied to clipboard'));
84
82
  },
85
83
  },
86
84
  {
87
85
  id: 'console',
88
86
  description: 'Log to console',
89
87
  iconComponent: IconConsole,
90
- handler() {
88
+ click() {
91
89
  const error = props.report.error ?? props.report;
92
90
 
93
91
  (window as { error?: unknown }).error = error;
@@ -95,7 +93,7 @@ const buttons = computed(() =>
95
93
  // eslint-disable-next-line no-console
96
94
  console.error(error);
97
95
 
98
- UI.showSnackbar(
96
+ UI.toast(
99
97
  translateWithDefault(
100
98
  'errors.addedToConsole',
101
99
  'You can now use the **error** variable in the console',
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <Markdown :text="text" inline />
2
+ <Markdown :text inline />
3
3
  </template>
4
4
 
5
5
  <script setup lang="ts">
@@ -21,20 +21,24 @@
21
21
  <script setup lang="ts">
22
22
  import IconExclamationSolid from '~icons/zondicons/exclamation-solid';
23
23
 
24
- import { computed } from 'vue';
24
+ import { computed, useTemplateRef } from 'vue';
25
25
  import type { HTMLAttributes } from 'vue';
26
26
 
27
+ import HeadlessInput from '@aerogel/core/components/headless/HeadlessInput.vue';
28
+ import HeadlessInputLabel from '@aerogel/core/components/headless/HeadlessInputLabel.vue';
29
+ import HeadlessInputInput from '@aerogel/core/components/headless/HeadlessInputInput.vue';
30
+ import HeadlessInputDescription from '@aerogel/core/components/headless/HeadlessInputDescription.vue';
31
+ import HeadlessInputError from '@aerogel/core/components/headless/HeadlessInputError.vue';
27
32
  import { classes } from '@aerogel/core/components/utils';
28
33
  import { useInputAttrs } from '@aerogel/core/utils/composition/forms';
29
- import { componentRef } from '@aerogel/core/utils/vue';
30
- import type { InputEmits, InputExpose, InputProps } from '@aerogel/core/components/contracts/Input';
34
+ import type { InputEmits, InputProps } from '@aerogel/core/components/contracts/Input';
31
35
 
32
36
  defineOptions({ inheritAttrs: false });
33
37
  defineEmits<InputEmits>();
34
38
  const { label, inputClass, wrapperClass, ...props } = defineProps<
35
39
  InputProps & { inputClass?: HTMLAttributes['class']; wrapperClass?: HTMLAttributes['class'] }
36
40
  >();
37
- const $input = componentRef<InputExpose>();
41
+ const $input = useTemplateRef('$input');
38
42
  const [inputAttrs, rootClasses] = useInputAttrs();
39
43
  const renderedWrapperClasses = computed(() =>
40
44
  classes('relative rounded-md shadow-2xs', { 'mt-1': label }, wrapperClass));
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <Button variant="link" v-bind="props">
2
+ <Button variant="link" v-bind="$props">
3
3
  <slot />
4
4
  </Button>
5
5
  </template>
@@ -8,5 +8,5 @@
8
8
  import Button from '@aerogel/core/components/ui/Button.vue';
9
9
  import type { ButtonProps } from '@aerogel/core/components/contracts/Button';
10
10
 
11
- const props = defineProps<Omit<ButtonProps, 'variant'>>();
11
+ defineProps<Omit<ButtonProps, 'variant'>>();
12
12
  </script>
@@ -3,13 +3,13 @@
3
3
  persistent
4
4
  class="flex"
5
5
  wrapper-class="w-auto"
6
- :title="title"
6
+ :title
7
7
  :class="{ 'flex-col-reverse': showProgress, 'items-center justify-center gap-2': !showProgress }"
8
8
  >
9
9
  <ProgressBar
10
10
  v-if="showProgress"
11
- :progress="progress"
12
- :job="job"
11
+ :progress
12
+ :job
13
13
  class="min-w-[min(400px,80vw)]"
14
14
  />
15
15
  <IconSpinner v-else class="text-primary mr-1 size-6" />
@@ -8,14 +8,15 @@ import { isInstanceOf } from '@noeldemartin/utils';
8
8
  import type { VNode } from 'vue';
9
9
 
10
10
  import { renderMarkdown } from '@aerogel/core/utils/markdown';
11
- import { translate } from '@aerogel/core/lang';
11
+ import { translate, translateWithDefault } from '@aerogel/core/lang';
12
12
  import { renderNode } from '@aerogel/core/utils/vdom';
13
13
 
14
- const { as, inline, langKey, langParams, text, actions } = defineProps<{
14
+ const { as, inline, langKey, langParams, langDefault, text, actions } = defineProps<{
15
15
  as?: string;
16
16
  inline?: boolean;
17
17
  langKey?: string;
18
18
  langParams?: number | Record<string, unknown>;
19
+ langDefault?: string;
19
20
  text?: string;
20
21
  actions?: Record<string, () => unknown>;
21
22
  }>();
@@ -27,7 +28,13 @@ const markdown = computed(() => {
27
28
  return slots.default().map(renderNode).join('');
28
29
  }
29
30
 
30
- return text ?? (langKey && translate(langKey, langParams ?? {}));
31
+ return (
32
+ text ??
33
+ (langKey &&
34
+ (langDefault
35
+ ? translateWithDefault(langKey, langDefault, langParams ?? {})
36
+ : translate(langKey, langParams ?? {})))
37
+ );
31
38
  });
32
39
  const html = computed(() => {
33
40
  if (!markdown.value) {
@@ -1,9 +1,14 @@
1
1
  <template>
2
- <HeadlessModal v-slot="{ close }" :persistent="persistent" v-bind="props">
2
+ <HeadlessModal
3
+ v-slot="{ close }"
4
+ v-bind="props"
5
+ ref="$modal"
6
+ :persistent
7
+ >
3
8
  <HeadlessModalOverlay class="fixed inset-0 bg-gray-500/75" />
4
9
 
5
10
  <HeadlessModalContent :class="renderedWrapperClass">
6
- <div v-if="!persistent" class="absolute top-0 right-0 hidden pt-1.5 pr-1.5 sm:block">
11
+ <div v-if="!persistent && dismissable" class="absolute top-0 right-0 hidden pt-1.5 pr-1.5 sm:block">
7
12
  <Button variant="ghost" size="icon" @click="close()">
8
13
  <span class="sr-only">{{ $td('ui.close', 'Close') }}</span>
9
14
  <IconClose class="size-3 text-gray-400" />
@@ -15,7 +20,7 @@
15
20
  </HeadlessModalTitle>
16
21
 
17
22
  <div :class="renderedContentClass">
18
- <slot :close="close" />
23
+ <slot :close />
19
24
  </div>
20
25
  </HeadlessModalContent>
21
26
  </HeadlessModal>
@@ -24,27 +29,34 @@
24
29
  <script setup lang="ts">
25
30
  import IconClose from '~icons/zondicons/close';
26
31
 
27
- import { computed } from 'vue';
32
+ import { computed, useTemplateRef } from 'vue';
28
33
  import type { HTMLAttributes } from 'vue';
29
34
 
30
35
  import Markdown from '@aerogel/core/components/ui/Markdown.vue';
36
+ import Button from '@aerogel/core/components/ui/Button.vue';
31
37
  import HeadlessModal from '@aerogel/core/components/headless/HeadlessModal.vue';
32
38
  import HeadlessModalContent from '@aerogel/core/components/headless/HeadlessModalContent.vue';
33
39
  import HeadlessModalOverlay from '@aerogel/core/components/headless/HeadlessModalOverlay.vue';
34
40
  import HeadlessModalTitle from '@aerogel/core/components/headless/HeadlessModalTitle.vue';
35
41
  import { classes } from '@aerogel/core/components/utils';
36
- import type { ModalProps, ModalSlots } from '@aerogel/core/components/contracts/Modal';
42
+ import type { ModalExpose, ModalProps, ModalSlots } from '@aerogel/core/components/contracts/Modal';
37
43
 
38
44
  const {
39
45
  class: contentClass = '',
46
+ dismissable = true,
40
47
  wrapperClass = '',
41
48
  title,
42
49
  persistent,
43
50
  ...props
44
- } = defineProps<ModalProps & { wrapperClass?: HTMLAttributes['class']; class?: HTMLAttributes['class'] }>();
45
-
46
- defineSlots<ModalSlots>();
51
+ } = defineProps<
52
+ ModalProps & {
53
+ dismissable?: boolean;
54
+ wrapperClass?: HTMLAttributes['class'];
55
+ class?: HTMLAttributes['class'];
56
+ }
57
+ >();
47
58
 
59
+ const $modal = useTemplateRef('$modal');
48
60
  const renderedContentClass = computed(() => classes({ 'mt-2': title }, contentClass));
49
61
  const renderedWrapperClass = computed(() =>
50
62
  classes(
@@ -52,4 +64,7 @@ const renderedWrapperClass = computed(() =>
52
64
  'fixed top-1/2 left-1/2 z-50 w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl sm:max-w-lg',
53
65
  wrapperClass,
54
66
  ));
67
+
68
+ defineSlots<ModalSlots>();
69
+ defineExpose<ModalExpose>({ close: async () => $modal.value?.close() });
55
70
  </script>
@@ -1,12 +1,12 @@
1
1
  <template>
2
- <Modal v-slot="{ close }" :title="title" persistent>
3
- <Form :form="form" @submit="close(form.draft)">
2
+ <Modal v-slot="{ close }" :title persistent>
3
+ <Form :form @submit="close(form.draft)">
4
4
  <Markdown :text="message" />
5
5
  <Input
6
6
  name="draft"
7
7
  class="mt-2"
8
- :placeholder="placeholder"
9
- :label="label"
8
+ :placeholder
9
+ :label
10
10
  />
11
11
 
12
12
  <div class="mt-4 flex flex-row-reverse gap-2">
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <HeadlessSelect ref="$select" v-bind="$props" @update:model-value="$emit('update:modelValue', $event)">
3
+ <HeadlessSelectLabel class="block text-sm leading-6 font-medium text-gray-900" />
4
+ <slot>
5
+ <HeadlessSelectTrigger
6
+ class="focus:outline-primary grid w-full cursor-default grid-cols-1 rounded-md bg-white py-1.5 pr-2 pl-3 text-left text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 focus:outline focus:outline-2 focus:-outline-offset-2 sm:text-sm/6"
7
+ :class="{ 'mt-1': label }"
8
+ >
9
+ <HeadlessSelectValue class="col-start-1 row-start-1 truncate pr-6" />
10
+ <IconCheveronDown
11
+ class="col-start-1 row-start-1 size-5 self-center justify-self-end text-gray-500 sm:size-4"
12
+ />
13
+ </HeadlessSelectTrigger>
14
+ <HeadlessSelectOptions
15
+ class="z-50 overflow-auto rounded-lg bg-white text-base shadow-lg ring-1 ring-black/5 focus:outline-hidden"
16
+ >
17
+ <HeadlessSelectOption
18
+ v-for="option of $select?.options ?? []"
19
+ :key="option.key"
20
+ :value="option.value"
21
+ class="group p-1 outline-none"
22
+ >
23
+ <div
24
+ class="relative flex max-w-[calc(100vw-2rem)] cursor-pointer items-center truncate rounded-md px-2 py-1 select-none *:truncate group-data-[highlighted]:bg-gray-100 group-data-[state=checked]:font-semibold group-data-[state=unchecked]:opacity-50"
25
+ >
26
+ <span class="text-sm">
27
+ {{ option.label }}
28
+ </span>
29
+ </div>
30
+ </HeadlessSelectOption>
31
+ </HeadlessSelectOptions>
32
+ </slot>
33
+ </HeadlessSelect>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ import IconCheveronDown from '~icons/zondicons/cheveron-down';
38
+
39
+ import { useTemplateRef } from 'vue';
40
+
41
+ import HeadlessSelect from '@aerogel/core/components/headless/HeadlessSelect.vue';
42
+ import HeadlessSelectLabel from '@aerogel/core/components/headless/HeadlessSelectLabel.vue';
43
+ import HeadlessSelectTrigger from '@aerogel/core/components/headless/HeadlessSelectTrigger.vue';
44
+ import HeadlessSelectOptions from '@aerogel/core/components/headless/HeadlessSelectOptions.vue';
45
+ import HeadlessSelectOption from '@aerogel/core/components/headless/HeadlessSelectOption.vue';
46
+ import HeadlessSelectValue from '@aerogel/core/components/headless/HeadlessSelectValue.vue';
47
+ import type { SelectEmits, SelectProps } from '@aerogel/core/components/contracts/Select';
48
+
49
+ defineProps<SelectProps>();
50
+ defineEmits<SelectEmits>();
51
+
52
+ const $select = useTemplateRef('$select');
53
+ </script>
@@ -0,0 +1,42 @@
1
+ <template>
2
+ <HeadlessToast :class="renderedClasses">
3
+ <Markdown v-if="message" :text="message" inline />
4
+
5
+ <Button
6
+ v-for="(action, key) of actions"
7
+ :key
8
+ :action
9
+ :variant
10
+ :as="HeadlessToastAction"
11
+ />
12
+ </HeadlessToast>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import type { HTMLAttributes } from 'vue';
17
+
18
+ import Button from '@aerogel/core/components/ui/Button.vue';
19
+ import Markdown from '@aerogel/core/components/ui/Markdown.vue';
20
+ import HeadlessToast from '@aerogel/core/components/headless/HeadlessToast.vue';
21
+ import HeadlessToastAction from '@aerogel/core/components/headless/HeadlessToastAction.vue';
22
+ import { computedVariantClasses } from '@aerogel/core/components/utils';
23
+ import type { ToastProps } from '@aerogel/core/components/contracts/Toast';
24
+ import type { Variants } from '@aerogel/core/components/utils';
25
+
26
+ const { class: baseClasses, variant = 'secondary' } = defineProps<ToastProps & { class?: HTMLAttributes['class'] }>();
27
+ const renderedClasses = computedVariantClasses<Variants<Pick<ToastProps, 'variant'>>>(
28
+ { baseClasses, variant },
29
+ {
30
+ baseClasses: 'flex items-center gap-2 rounded-md p-2 ring-1 shadow-lg border-gray-200',
31
+ variants: {
32
+ variant: {
33
+ secondary: 'bg-gray-900 text-white ring-black',
34
+ danger: 'bg-red-50 text-red-900 ring-red-100',
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: 'secondary',
39
+ },
40
+ },
41
+ );
42
+ </script>