@nexdom/uimed-vue 2.0.0-beta.43 → 2.0.0-beta.45

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/AGENTS.md CHANGED
@@ -205,7 +205,8 @@ Use an existing composable (e.g. `use-toast`) as the reference, and deliver in t
205
205
 
206
206
  - Component layout: `src/components/<name>/<name>.vue`, `types.ts` for prop/option types, `__tests__/<name>.test.ts` for unit tests. Components that belong to a group live in the group's folder (e.g. the internal `src/components/dialogs/toast.vue`).
207
207
  - Composables live in a group folder: `src/composables/<group>/<name>.ts`, with `types.ts` and `__tests__/` in the group folder (e.g. `src/composables/dialogs/use-toast.ts`). A composable (`use-*.ts`) must depend on `vue` or `vue-router`, directly or through other modules (enforced by dependency-cruiser's `composables-use-vue` rule).
208
- - Library-wide configuration constants that aren't components or composables live in `src/consts/<name>.ts` (e.g. `src/consts/locale.ts`, the language set by `createUimed()` and `vueTestUtilsPluginUimed()`).
208
+ - Library-wide configuration constants that aren't components or composables live in `src/consts/<name>.ts` (e.g. `src/consts/locale.ts`, the language set by `createUimed()` and `vueTestUtilsPluginUimed()`), with their types in `src/consts/types.ts`.
209
+ - Components accept icons by name (the `Icon` type in `src/consts/types.ts`), mapped to icon font classes by the internal `icons` object in `src/consts/icons.ts`. To add an icon, add it to that map and to the list on the "Ícones" guide page (`docs/guide/icons.md`), and check that its class exists in the installed icon font: the unit test of `src/consts/icons.ts` checks every class, and the page's E2E spec fails when the page and the map list different icons.
209
210
  - Pure helpers that don't use Vue live in `src/utils/<name>.ts`, with tests in `src/utils/__tests__/`. Generic helpers that any TypeScript project could use (e.g. `isDeepEqual`) belong in `@nexdom/shared` instead.
210
211
  - Public components/composables are re-exported from `src/components/index.ts` and `src/composables/index.ts`. Prop types stay internal (`types.ts` is not re-exported); consumers derive them with `ComponentProps` (see [Usage](#usage)).
211
212
  - Every publicly exported component must use the `U` prefix on its export identifier (e.g., `UButton`, `UMain`), while internal file names and component names remain unprefixed.
@@ -1,4 +1,4 @@
1
- import { t as ButtonProps } from "./types-CjaJ2WAC.js";
1
+ import { r as ColorVariant, t as ButtonProps } from "./types-BbWo6RIy.js";
2
2
  import { RouteLocationRaw } from "vue-router";
3
3
  //#region src/components/app-bar/types.d.ts
4
4
  /**
@@ -93,6 +93,53 @@ interface Option {
93
93
  action?: () => void;
94
94
  }
95
95
  //#endregion
96
+ //#region src/consts/icons.d.ts
97
+ /**
98
+ * Icon font class of each icon available to the components that accept one (e.g. `UIconButton`
99
+ * and the items of the navigation menu), by its name, in the order the "Ícones" guide page lists
100
+ * them. A name ending in `-alternative` is an alternative version of the same drawing (currently,
101
+ * outlined).
102
+ *
103
+ * To add an icon, add it here and to the list on the "Ícones" guide page (`docs/guide/icons.md`).
104
+ */
105
+ declare const icons: {
106
+ home: string;
107
+ "home-alternative": string;
108
+ account: string;
109
+ "account-alternative": string;
110
+ "account-group": string;
111
+ "account-group-alternative": string;
112
+ calendar: string;
113
+ "calendar-alternative": string;
114
+ "clipboard-text": string;
115
+ "clipboard-text-alternative": string;
116
+ "file-document": string;
117
+ "file-document-alternative": string;
118
+ flask: string;
119
+ "flask-alternative": string;
120
+ folder: string;
121
+ "folder-alternative": string;
122
+ "chart-box": string;
123
+ "chart-box-alternative": string;
124
+ wallet: string;
125
+ "wallet-alternative": string;
126
+ cog: string;
127
+ "cog-alternative": string;
128
+ stethoscope: string;
129
+ pill: string;
130
+ hospital: string;
131
+ };
132
+ //#endregion
133
+ //#region src/consts/types.d.ts
134
+ /**
135
+ * Name of an icon available to the components that accept one (e.g. `UIconButton` and the items
136
+ * of the navigation menu). The full list, with a preview of each icon, is on the "Ícones" guide
137
+ * page.
138
+ *
139
+ * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/icons | Icons Guide}
140
+ */
141
+ type Icon = keyof typeof icons;
142
+ //#endregion
96
143
  //#region src/components/navigation-menu/types.d.ts
97
144
  /**
98
145
  * Props exposed by the {@link NavigationMenu} component.
@@ -114,6 +161,13 @@ type NavigationMenuParentItem = {
114
161
  * Text to present for the group.
115
162
  */
116
163
  description: string;
164
+ /**
165
+ * Icon shown before the group's description. One of the names listed on the "Ícones" guide
166
+ * page.
167
+ *
168
+ * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/icons | Icons Guide}
169
+ */
170
+ icon?: Icon;
117
171
  /**
118
172
  * Child {@link NavigationMenuItem} list.
119
173
  */
@@ -124,6 +178,13 @@ interface NavigationMenuItem {
124
178
  * Text to present for the item.
125
179
  */
126
180
  description: string;
181
+ /**
182
+ * Icon shown before the item's description. One of the names listed on the "Ícones" guide
183
+ * page.
184
+ *
185
+ * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/icons | Icons Guide}
186
+ */
187
+ icon?: Icon;
127
188
  /**
128
189
  * Route to navigate when item is clicked.
129
190
  */
@@ -208,8 +269,8 @@ type MainProps = {
208
269
  *
209
270
  * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/components/main | Main Guide}
210
271
  */
211
- declare const _default$13: typeof __VLS_export$19;
212
- declare const __VLS_export$19: __VLS_WithSlots$11<import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<MainProps & {
272
+ declare const _default$14: typeof __VLS_export$20;
273
+ declare const __VLS_export$20: __VLS_WithSlots$11<import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<MainProps & {
213
274
  notificationsOpen?: boolean;
214
275
  }, {}, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {
215
276
  "update:notificationsOpen": (value: boolean) => any;
@@ -241,8 +302,8 @@ type __VLS_WithSlots$11<T, S> = T & {
241
302
  *
242
303
  * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/components/button | Button Guide}
243
304
  */
244
- declare const _default$1: typeof __VLS_export$18;
245
- declare const __VLS_export$18: __VLS_WithSlots$10<import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<ButtonProps, {}, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {} & {
305
+ declare const _default$1: typeof __VLS_export$19;
306
+ declare const __VLS_export$19: __VLS_WithSlots$10<import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<ButtonProps, {}, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {} & {
246
307
  click: (event: MouseEvent) => any;
247
308
  }, string, import("vue", { with: { "resolution-mode": "import" } }).PublicProps, Readonly<ButtonProps> & Readonly<{
248
309
  onClick?: ((event: MouseEvent) => any) | undefined;
@@ -255,6 +316,57 @@ type __VLS_WithSlots$10<T, S> = T & {
255
316
  };
256
317
  };
257
318
  //#endregion
319
+ //#region src/components/icon-button/types.d.ts
320
+ /**
321
+ * Props exposed by the {@link IconButton} component.
322
+ */
323
+ type IconButtonProps = {
324
+ /**
325
+ * Icon drawn on the button. One of the names listed on the "Ícones" guide page. Assistive
326
+ * technologies ignore it and read `label` instead.
327
+ *
328
+ * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/icons | Icons Guide}
329
+ */
330
+ icon: Icon;
331
+ /**
332
+ * Accessible name of the button, read by assistive technologies instead of the icon. Describe
333
+ * the action the button performs (e.g. "Editar paciente"), not the icon's drawing (e.g.
334
+ * "Lápis").
335
+ */
336
+ label: string;
337
+ /**
338
+ * Applies a color to the button.
339
+ * One of `primary`, `secondary`, `positive`, `informative`, `caution`, or `danger`.
340
+ * @default "primary"
341
+ */
342
+ color?: ColorVariant;
343
+ /**
344
+ * Component id to use on automated tests.
345
+ */
346
+ dataTestid?: string;
347
+ };
348
+ //#endregion
349
+ //#region src/components/icon-button/icon-button.vue.d.ts
350
+ /**
351
+ * Button that shows only an icon, for actions whose icon is enough to identify them, such as
352
+ * editing a record of a list. Its `label` is the name assistive technologies read.
353
+ *
354
+ * @example
355
+ * ```vue
356
+ * <template>
357
+ * <u-icon-button icon="cog" label="Configurações" @click="onClick" />
358
+ * </template>
359
+ * ```
360
+ *
361
+ * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/components/icon-button | IconButton Guide}
362
+ */
363
+ declare const _default$11: typeof __VLS_export$18;
364
+ declare const __VLS_export$18: import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<IconButtonProps, {}, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {} & {
365
+ click: (event: MouseEvent) => any;
366
+ }, string, import("vue", { with: { "resolution-mode": "import" } }).PublicProps, Readonly<IconButtonProps> & Readonly<{
367
+ onClick?: ((event: MouseEvent) => any) | undefined;
368
+ }>, {}, {}, {}, {}, string, import("vue", { with: { "resolution-mode": "import" } }).ComponentProvideOptions, false, {}, any>;
369
+ //#endregion
258
370
  //#region src/components/inputs/text-field/types.d.ts
259
371
  type TextFieldVariant = "primary" | "secondary";
260
372
  type TextFieldType = "text" | "phone" | "email" | "url" | "password" | "search";
@@ -452,7 +564,7 @@ type __VLS_PrettifyLocal$2<T> = (T extends any ? { [K in keyof T]: T[K]; } : { [
452
564
  *
453
565
  * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/components/text-field | TextField Guide}
454
566
  */
455
- declare const _default$19: typeof __VLS_export$16;
567
+ declare const _default$20: typeof __VLS_export$16;
456
568
  declare const __VLS_export$16: import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<TextFieldProps & {
457
569
  modelValue?: string;
458
570
  }, {}, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {
@@ -780,7 +892,7 @@ type MaskFieldProps = {
780
892
  *
781
893
  * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/components/mask-field | MaskField Guide}
782
894
  */
783
- declare const _default$14: typeof __VLS_export$13;
895
+ declare const _default$15: typeof __VLS_export$13;
784
896
  declare const __VLS_export$13: import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<MaskFieldProps & {
785
897
  modelValue?: string;
786
898
  }, {}, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {
@@ -926,7 +1038,7 @@ type RowProps = {
926
1038
  *
927
1039
  * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/components/layout | Row Guide}
928
1040
  */
929
- declare const _default$15: typeof __VLS_export$11;
1041
+ declare const _default$16: typeof __VLS_export$11;
930
1042
  declare const __VLS_export$11: __VLS_WithSlots$8<import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<RowProps, {}, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {}, string, import("vue", { with: { "resolution-mode": "import" } }).PublicProps, Readonly<RowProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue", { with: { "resolution-mode": "import" } }).ComponentProvideOptions, false, {}, any>, {
931
1043
  default?: (props: {}) => any;
932
1044
  }>;
@@ -1055,7 +1167,7 @@ type __VLS_WithSlots$6<T, S> = T & {
1055
1167
  *
1056
1168
  * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/components/section | Section Guide}
1057
1169
  */
1058
- declare const _default$17: typeof __VLS_export$8;
1170
+ declare const _default$18: typeof __VLS_export$8;
1059
1171
  declare const __VLS_export$8: __VLS_WithSlots$5<import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<{}, {}, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {}, string, import("vue", { with: { "resolution-mode": "import" } }).PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue", { with: { "resolution-mode": "import" } }).ComponentProvideOptions, true, {}, any>, {
1060
1172
  default?: (props: {}) => any;
1061
1173
  }>;
@@ -1160,7 +1272,7 @@ interface SectionAction extends ButtonProps {
1160
1272
  *
1161
1273
  * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/components/section | Section Guide}
1162
1274
  */
1163
- declare const _default$16: typeof __VLS_export$7;
1275
+ declare const _default$17: typeof __VLS_export$7;
1164
1276
  declare const __VLS_export$7: __VLS_WithSlots$4<import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<SectionProps, {}, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {}, string, import("vue", { with: { "resolution-mode": "import" } }).PublicProps, Readonly<SectionProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue", { with: { "resolution-mode": "import" } }).ComponentProvideOptions, false, {}, any>, {
1165
1277
  default?: (props: {}) => any;
1166
1278
  }>;
@@ -1235,7 +1347,7 @@ type TableProps = {
1235
1347
  *
1236
1348
  * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/components/table | Table Guide}
1237
1349
  */
1238
- declare const _default$18: typeof __VLS_export$6;
1350
+ declare const _default$19: typeof __VLS_export$6;
1239
1351
  declare const __VLS_export$6: import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<TableProps, {}, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {}, string, import("vue", { with: { "resolution-mode": "import" } }).PublicProps, Readonly<TableProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue", { with: { "resolution-mode": "import" } }).ComponentProvideOptions, false, {}, any>;
1240
1352
  //#endregion
1241
1353
  //#region src/components/details/types.d.ts
@@ -1354,7 +1466,7 @@ type ImgProps = {
1354
1466
  *
1355
1467
  * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/components/img | Img Guide}
1356
1468
  */
1357
- declare const _default$11: typeof __VLS_export$4;
1469
+ declare const _default$12: typeof __VLS_export$4;
1358
1470
  declare const __VLS_export$4: import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<ImgProps, {}, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {}, string, import("vue", { with: { "resolution-mode": "import" } }).PublicProps, Readonly<ImgProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue", { with: { "resolution-mode": "import" } }).ComponentProvideOptions, false, {}, any>;
1359
1471
  //#endregion
1360
1472
  //#region src/components/link/types.d.ts
@@ -1389,7 +1501,7 @@ type LinkProps = {
1389
1501
  *
1390
1502
  * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/components/link | Link Guide}
1391
1503
  */
1392
- declare const _default$12: typeof __VLS_export$3;
1504
+ declare const _default$13: typeof __VLS_export$3;
1393
1505
  declare const __VLS_export$3: __VLS_WithSlots$2<import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<LinkProps, {}, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {}, string, import("vue", { with: { "resolution-mode": "import" } }).PublicProps, Readonly<LinkProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue", { with: { "resolution-mode": "import" } }).ComponentProvideOptions, false, {}, any>, {
1394
1506
  default?: (props: {}) => any;
1395
1507
  }>;
@@ -1666,4 +1778,4 @@ type __VLS_WithSlots<T, S> = T & {
1666
1778
  };
1667
1779
  };
1668
1780
  //#endregion
1669
- export { _default as UAutocompleteField, _default$1 as UButton, _default$2 as UCheckbox, _default$3 as UColumn, _default$4 as UContainer, _default$5 as UDataSet, _default$6 as UDataSetItem, _default$7 as UDateTimeField, _default$8 as UDetails, _default$9 as UDialog, _default$10 as UForm, _default$11 as UImg, _default$12 as ULink, _default$13 as UMain, _default$14 as UMaskField, _default$15 as URow, _default$16 as USection, _default$17 as USectionContent, _default$18 as UTable, _default$19 as UTextField };
1781
+ export { _default as UAutocompleteField, _default$1 as UButton, _default$2 as UCheckbox, _default$3 as UColumn, _default$4 as UContainer, _default$5 as UDataSet, _default$6 as UDataSetItem, _default$7 as UDateTimeField, _default$8 as UDetails, _default$9 as UDialog, _default$10 as UForm, _default$11 as UIconButton, _default$12 as UImg, _default$13 as ULink, _default$14 as UMain, _default$15 as UMaskField, _default$16 as URow, _default$17 as USection, _default$18 as USectionContent, _default$19 as UTable, _default$20 as UTextField };
@@ -423,6 +423,43 @@ var app_bar_default = /* @__PURE__ */ defineComponent({
423
423
  }
424
424
  });
425
425
  //#endregion
426
+ //#region src/consts/icons.ts
427
+ /**
428
+ * Icon font class of each icon available to the components that accept one (e.g. `UIconButton`
429
+ * and the items of the navigation menu), by its name, in the order the "Ícones" guide page lists
430
+ * them. A name ending in `-alternative` is an alternative version of the same drawing (currently,
431
+ * outlined).
432
+ *
433
+ * To add an icon, add it here and to the list on the "Ícones" guide page (`docs/guide/icons.md`).
434
+ */
435
+ const icons = {
436
+ home: "mdi-home",
437
+ "home-alternative": "mdi-home-outline",
438
+ account: "mdi-account",
439
+ "account-alternative": "mdi-account-outline",
440
+ "account-group": "mdi-account-group",
441
+ "account-group-alternative": "mdi-account-group-outline",
442
+ calendar: "mdi-calendar",
443
+ "calendar-alternative": "mdi-calendar-outline",
444
+ "clipboard-text": "mdi-clipboard-text",
445
+ "clipboard-text-alternative": "mdi-clipboard-text-outline",
446
+ "file-document": "mdi-file-document",
447
+ "file-document-alternative": "mdi-file-document-outline",
448
+ flask: "mdi-flask",
449
+ "flask-alternative": "mdi-flask-outline",
450
+ folder: "mdi-folder",
451
+ "folder-alternative": "mdi-folder-outline",
452
+ "chart-box": "mdi-chart-box",
453
+ "chart-box-alternative": "mdi-chart-box-outline",
454
+ wallet: "mdi-wallet",
455
+ "wallet-alternative": "mdi-wallet-outline",
456
+ cog: "mdi-cog",
457
+ "cog-alternative": "mdi-cog-outline",
458
+ stethoscope: "mdi-stethoscope",
459
+ pill: "mdi-pill",
460
+ hospital: "mdi-hospital-building"
461
+ };
462
+ //#endregion
426
463
  //#region src/composables/inputs/rules.ts
427
464
  function required(value) {
428
465
  return !isEmpty(value) || "Campo obrigatório";
@@ -771,18 +808,31 @@ var navigation_menu_default = /* @__PURE__ */ defineComponent({
771
808
  default: withCtx(() => [createVNode(unref(VList), { nav: "" }, {
772
809
  default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(filteredItems.value, (item, index) => {
773
810
  return openBlock(), createElementBlock(Fragment, { key: index }, [isParentItem(item) ? (openBlock(), createBlock(unref(VListGroup), { key: 0 }, {
774
- activator: withCtx(({ props: activatorProps }) => [createVNode(unref(VListItem), mergeProps({ ref_for: true }, activatorProps, { title: item.description }), null, 16, ["title"])]),
811
+ activator: withCtx(({ props: activatorProps }) => [createVNode(unref(VListItem), mergeProps({ ref_for: true }, activatorProps, {
812
+ title: item.description,
813
+ "prepend-icon": item.icon && unref(icons)[item.icon]
814
+ }), null, 16, ["title", "prepend-icon"])]),
775
815
  default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(item.items, (childItem, childIndex) => {
776
816
  return openBlock(), createBlock(unref(VListItem), mergeProps({ key: childIndex }, { ref_for: true }, unref(routeToProps)(childItem.route), {
777
817
  title: childItem.description,
818
+ "prepend-icon": childItem.icon && unref(icons)[childItem.icon],
778
819
  onClick: childItem.action
779
- }), null, 16, ["title", "onClick"]);
820
+ }), null, 16, [
821
+ "title",
822
+ "prepend-icon",
823
+ "onClick"
824
+ ]);
780
825
  }), 128))]),
781
826
  _: 2
782
827
  }, 1024)) : (openBlock(), createBlock(unref(VListItem), mergeProps({
783
828
  key: 1,
784
- title: item.description
785
- }, { ref_for: true }, unref(routeToProps)(item.route), { onClick: item.action }), null, 16, ["title", "onClick"]))], 64);
829
+ title: item.description,
830
+ "prepend-icon": item.icon && unref(icons)[item.icon]
831
+ }, { ref_for: true }, unref(routeToProps)(item.route), { onClick: item.action }), null, 16, [
832
+ "title",
833
+ "prepend-icon",
834
+ "onClick"
835
+ ]))], 64);
786
836
  }), 128))]),
787
837
  _: 1
788
838
  })]),
@@ -924,13 +974,31 @@ function useButtonVariant(variant) {
924
974
  return variantValue ? buttonVariantToVuetifyVariant[variantValue] : buttonVariantToVuetifyVariant.primary;
925
975
  });
926
976
  }
927
- function useButtonType(type) {
977
+ /**
978
+ * The route the button navigates to, or `undefined` while it's blocked, so a `disabled` or
979
+ * `loading` button is rendered as a disabled `<button>` instead of a link that the keyboard,
980
+ * assistive technologies or `element.click()` could still follow.
981
+ */
982
+ function useButtonRoute(route, blocked) {
983
+ return computed(() => toValue(blocked) ? void 0 : toValue(route));
984
+ }
985
+ /**
986
+ * The button's `type`. It doesn't apply to a button with a `route`, which is `undefined` while
987
+ * rendered as a link, and `"button"` while blocked, so the disabled `<button>` it's rendered as
988
+ * isn't a submit button.
989
+ */
990
+ function useButtonType(type, route, blocked) {
928
991
  return computed(() => {
929
- return toValue(type) ?? "button";
992
+ if (!toValue(route)) return toValue(type) ?? "button";
993
+ return toValue(blocked) ? "button" : void 0;
930
994
  });
931
995
  }
932
- function useButtonForm(form) {
933
- return computed(() => toValue(form));
996
+ /**
997
+ * The button's `form`, or `undefined` when it has a `route`, even while blocked, since `form`
998
+ * doesn't apply to it.
999
+ */
1000
+ function useButtonForm(form, route) {
1001
+ return computed(() => toValue(route) ? void 0 : toValue(form));
934
1002
  }
935
1003
  //#endregion
936
1004
  //#region src/components/button/button.vue
@@ -966,6 +1034,10 @@ var button_default = /* @__PURE__ */ defineComponent({
966
1034
  type: String,
967
1035
  required: false
968
1036
  },
1037
+ route: {
1038
+ type: null,
1039
+ required: false
1040
+ },
969
1041
  dataTestid: {
970
1042
  type: String,
971
1043
  required: false
@@ -977,15 +1049,16 @@ var button_default = /* @__PURE__ */ defineComponent({
977
1049
  const emit = __emit;
978
1050
  const variant = useButtonVariant(() => props.variant);
979
1051
  const color = useVuetifyColor(() => props.color);
980
- const type = useButtonType(() => props.type);
981
- const form = useButtonForm(() => props.form);
982
1052
  const widthClass = useFullWidth(() => props.fullWidth);
983
1053
  const isDisabled = computed(() => props.disabled || props.loading);
1054
+ const routeProps = useRouteProps(useButtonRoute(() => props.route, isDisabled));
1055
+ const type = useButtonType(() => props.type, () => props.route, isDisabled);
1056
+ const form = useButtonForm(() => props.form, () => props.route);
984
1057
  function onClick(event) {
985
1058
  emit("click", event);
986
1059
  }
987
1060
  return (_ctx, _cache) => {
988
- return openBlock(), createBlock(unref(VBtn), {
1061
+ return openBlock(), createBlock(unref(VBtn), mergeProps({
989
1062
  disabled: isDisabled.value,
990
1063
  loading: props.loading,
991
1064
  "data-testid": props.dataTestid,
@@ -993,9 +1066,11 @@ var button_default = /* @__PURE__ */ defineComponent({
993
1066
  color: unref(color),
994
1067
  type: unref(type),
995
1068
  form: unref(form),
996
- class: normalizeClass(unref(widthClass)),
1069
+ class: unref(widthClass)
1070
+ }, unref(routeProps), {
1071
+ active: false,
997
1072
  onClick
998
- }, {
1073
+ }), {
999
1074
  loader: withCtx(() => [createVNode(unref(VProgressCircular), {
1000
1075
  indeterminate: "",
1001
1076
  width: "2",
@@ -1003,7 +1078,7 @@ var button_default = /* @__PURE__ */ defineComponent({
1003
1078
  })]),
1004
1079
  default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
1005
1080
  _: 3
1006
- }, 8, [
1081
+ }, 16, [
1007
1082
  "disabled",
1008
1083
  "loading",
1009
1084
  "data-testid",
@@ -1352,6 +1427,53 @@ var main_default = /* @__PURE__ */ defineComponent({
1352
1427
  }
1353
1428
  });
1354
1429
  //#endregion
1430
+ //#region src/components/icon-button/icon-button.vue
1431
+ var icon_button_default = /* @__PURE__ */ defineComponent({
1432
+ inheritAttrs: false,
1433
+ __name: "icon-button",
1434
+ props: {
1435
+ icon: {
1436
+ type: null,
1437
+ required: true
1438
+ },
1439
+ label: {
1440
+ type: String,
1441
+ required: true
1442
+ },
1443
+ color: {
1444
+ type: String,
1445
+ required: false
1446
+ },
1447
+ dataTestid: {
1448
+ type: String,
1449
+ required: false
1450
+ }
1451
+ },
1452
+ emits: ["click"],
1453
+ setup(__props, { emit: __emit }) {
1454
+ const props = __props;
1455
+ const emit = __emit;
1456
+ const color = useVuetifyColor(() => props.color);
1457
+ function onClick(event) {
1458
+ emit("click", event);
1459
+ }
1460
+ return (_ctx, _cache) => {
1461
+ return openBlock(), createBlock(unref(VIconBtn), {
1462
+ icon: unref(icons)[props.icon],
1463
+ "aria-label": props.label,
1464
+ color: unref(color),
1465
+ "data-testid": props.dataTestid,
1466
+ onClick
1467
+ }, null, 8, [
1468
+ "icon",
1469
+ "aria-label",
1470
+ "color",
1471
+ "data-testid"
1472
+ ]);
1473
+ };
1474
+ }
1475
+ });
1476
+ //#endregion
1355
1477
  //#region src/composables/inputs/autocomplete-field.ts
1356
1478
  function createAutocompleteRequiredRule(options) {
1357
1479
  return createRequiredRule(options.required, (value) => {
@@ -3007,4 +3129,4 @@ var data_set_item_default = /* @__PURE__ */ defineComponent({
3007
3129
  }
3008
3130
  });
3009
3131
  //#endregion
3010
- export { autocomplete_field_default as UAutocompleteField, button_default as UButton, checkbox_default as UCheckbox, column_default as UColumn, container_default as UContainer, data_set_default as UDataSet, data_set_item_default as UDataSetItem, date_time_field_default as UDateTimeField, details_default as UDetails, dialog_default as UDialog, form_default as UForm, img_default as UImg, link_default as ULink, main_default as UMain, mask_field_default as UMaskField, row_default as URow, section_default as USection, section_content_default as USectionContent, table_default as UTable, text_field_default as UTextField };
3132
+ export { autocomplete_field_default as UAutocompleteField, button_default as UButton, checkbox_default as UCheckbox, column_default as UColumn, container_default as UContainer, data_set_default as UDataSet, data_set_item_default as UDataSetItem, date_time_field_default as UDateTimeField, details_default as UDetails, dialog_default as UDialog, form_default as UForm, icon_button_default as UIconButton, img_default as UImg, link_default as ULink, main_default as UMain, mask_field_default as UMaskField, row_default as URow, section_default as USection, section_content_default as USectionContent, table_default as UTable, text_field_default as UTextField };
@@ -1,4 +1,4 @@
1
- import { i as FeedbackColorVariant, n as ButtonVariant, r as ColorVariant } from "./types-CjaJ2WAC.js";
1
+ import { i as FeedbackColorVariant, n as ButtonVariant, r as ColorVariant } from "./types-BbWo6RIy.js";
2
2
  import { ComputedRef, MaybeRefOrGetter } from "vue";
3
3
  //#region src/composables/dialogs/types.d.ts
4
4
  interface ToastOptions {
@@ -1,3 +1,4 @@
1
+ import { RouteLocationRaw } from "vue-router";
1
2
  //#region src/composables/colors/types.d.ts
2
3
  type ColorVariant = "primary" | "secondary" | FeedbackColorVariant;
3
4
  type FeedbackColorVariant = "positive" | "informative" | "caution" | "danger";
@@ -16,7 +17,7 @@ type ButtonProps = {
16
17
  variant?: ButtonVariant;
17
18
  /**
18
19
  * Applies a distinct behavior to the button.
19
- * One of `button` or `submit`.
20
+ * One of `button` or `submit`. Ignored when `route` is set.
20
21
  * @default "button"
21
22
  */
22
23
  type?: "submit" | "button";
@@ -49,8 +50,16 @@ type ButtonProps = {
49
50
  fullWidth?: boolean;
50
51
  /**
51
52
  * Associated [form](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/form) id.
53
+ * Ignored when `route` is set.
52
54
  */
53
55
  form?: string;
56
+ /**
57
+ * Where the button goes when clicked, rendering it as a link with the button's look. A route
58
+ * inside the app (`"/patients"`, `{ name: "patient", params: { id } }`) navigates through the
59
+ * app's router; a URL starting with `http` navigates through the browser, in the same tab. While
60
+ * `disabled` or `loading`, the button doesn't navigate and is rendered as a disabled button.
61
+ */
62
+ route?: RouteLocationRaw;
54
63
  /**
55
64
  * Component id to use on automated tests.
56
65
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexdom/uimed-vue",
3
- "version": "2.0.0-beta.43",
3
+ "version": "2.0.0-beta.45",
4
4
  "description": "A ui framework for NEXDOM applications.",
5
5
  "keywords": [
6
6
  "lib",