@eos-ui/vue 0.2.0 → 0.3.0
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/components/Tabs/ETabs.js.map +1 -1
- package/dist/components/Tabs/ETabs.vue.d.ts +4 -0
- package/dist/components/Tabs/ETabs.vue_vue_type_script_setup_true_lang.js +28 -21
- package/dist/components/Tabs/ETabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/style.css +10 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ETabs.js","names":["_sfc_main","ETabs_vue_vue_type_script_setup_true_lang_default","ETabs_default"],"sources":["../../../src/components/Tabs/ETabs.vue"],"sourcesContent":["<script lang=\"ts\">\nexport interface TabItem {\n /** Names the tab's slot, and is the value of `v-model`. */\n id: string;\n /** The tab's words. */\n label: string;\n /** A number or short word in a small badge after the label. */\n count?: number | string;\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, ref, watch } from \"vue\";\nimport { TabsContent, TabsList, TabsRoot, TabsTrigger } from \"reka-ui\";\nimport { useConfig } from \"../../config.js\";\nimport \"../../styles/base.css\";\n\nconst props = defineProps<{\n /** The tabs, in order: `{ id, label, count? }`. Two to five, with short labels. `id` names the slot and is the `v-model` value. */\n items?: TabItem[];\n}>();\n\ndefineSlots<{\n [id: string]: ((props: { item: TabItem }) => unknown) | undefined;\n /** The panel of any tab that has no slot of its own. */\n panel?(props: { item: TabItem }): unknown;\n}>();\n\nconst flat = computed(() => props.items ?? []);\n\n/** The `id` of the selected tab. Keeps its own state when unbound. @default first tab */\nconst model = defineModel<string>();\n\nconst config = useConfig();\nconst current = computed(() => model.value ?? flat.value[0]?.id);\nconst dir = computed<\"ltr\" | \"rtl\">(\n () => config.dir ?? (typeof document !== \"undefined\" && document.documentElement.dir === \"rtl\" ? \"rtl\" : \"ltr\"),\n);\n\nconst list = ref<{ $el: HTMLElement } | null>(null);\n\nasync function reveal(smooth: boolean) {\n await nextTick();\n const el = list.value?.$el;\n const tab = el?.querySelector<HTMLElement>('[aria-selected=\"true\"]');\n if (!el || !tab || el.scrollWidth <= el.clientWidth) return;\n const calm = typeof matchMedia !== \"undefined\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n const a = tab.getBoundingClientRect();\n const b = el.getBoundingClientRect();\n el.scrollBy({ left: a.left + a.width / 2 - (b.left + b.width / 2), behavior: smooth && !calm ? \"smooth\" : \"auto\" });\n}\n\nonMounted(() => reveal(false));\nwatch(current, () => reveal(true));\n</script>\n\n<template>\n <TabsRoot class=\"e-tabs\" :model-value=\"current\" :dir=\"dir\" @update:model-value=\"model = String($event)\">\n <TabsList ref=\"list\" class=\"e-tablist\">\n <TabsTrigger v-for=\"item in flat\" :key=\"item.id\" :value=\"item.id\" class=\"e-tab\">\n {{ item.label }}\n <span v-if=\"item.count != null\" class=\"e-tab-count\">{{ item.count }}</span>\n </TabsTrigger>\n </TabsList>\n <TabsContent v-for=\"item in flat\" :key=\"item.id\" :value=\"item.id\" class=\"e-tabpanel\">\n <slot :name=\"item.id\" :item=\"item\"><slot name=\"panel\" :item=\"item\" /></slot>\n </TabsContent>\n </TabsRoot>\n</template>\n\n<style>\n@layer e.containers {\n .e-tablist {\n display: flex;\n gap: 0;\n /* stylelint-disable-next-line declaration-block-no-redundant-longhand-properties -- the two axes are set apart */\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n }\n\n .e-tablist::-webkit-scrollbar {\n display: none;\n }\n\n /* Tabs are button-like: every tab is ringed and neighbours sit flush and share an edge, so only the outer ends of the row are rounded. The selected tab is outlined in the primary colour with a thick slab underneath. */\n .e-tab {\n --e-bc: var(--e-border-strong);\n\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: var(--e-space-2);\n flex: none;\n height: var(--e-control-height);\n padding: 0 var(--e-space-4);\n border: var(--e-stroke-edge) solid var(--e-bc);\n border-radius: 0;\n outline: var(--e-stroke-edge) solid var(--e-bc);\n outline-offset: calc(-1 * var(--e-stroke-edge));\n background: transparent;\n color: var(--e-muted);\n font: 500 var(--e-text-md) / 1 var(--e-font-sans);\n cursor: pointer;\n transition:\n color var(--e-duration-tap) var(--e-easing-out),\n background-color var(--e-duration-tap) var(--e-easing-out);\n }\n\n /* Only the outer ends of the row are rounded. */\n .e-tab:first-child {\n border-start-start-radius: var(--e-radius-md);\n border-end-start-radius: var(--e-radius-md);\n }\n\n .e-tab:last-child {\n border-start-end-radius: var(--e-radius-md);\n border-end-end-radius: var(--e-radius-md);\n }\n\n .e-tab + .e-tab {\n margin-inline-start: calc(-1 * var(--e-stroke-edge));\n }\n\n .e-tab:hover {\n --e-bc: var(--e-ink);\n\n z-index: var(--e-z-raised);\n color: var(--e-ink);\n background-color: var(--e-surface-hover);\n }\n\n .e-tab[aria-selected=\"true\"] {\n --e-bc: var(--e-primary);\n\n z-index: var(--e-z-sticky);\n border-bottom-width: calc(var(--e-stroke-edge) + var(--e-depth));\n background-color: color-mix(in srgb, var(--e-surface-tint), var(--e-ink) 3%);\n color: var(--e-ink);\n font-weight: 600;\n }\n\n .e-tab:focus-visible {\n z-index: var(--e-z-focus);\n outline: var(--e-stroke-focus) solid var(--e-focus-ring);\n outline-offset: var(--e-focus-gap);\n }\n\n .e-tab-count {\n min-width: var(--e-space-6);\n padding: 0 var(--e-space-2);\n border-radius: var(--e-radius-pill);\n background: color-mix(in srgb, var(--e-primary) 12%, var(--e-surface));\n box-shadow: inset 0 0 0 var(--e-stroke-hair) var(--e-primary);\n outline: var(--e-stroke-hair) solid var(--e-primary);\n outline-offset: calc(-1 * var(--e-stroke-hair));\n font: 600 var(--e-text-xs) / 20px var(--e-font-mono);\n text-align: center;\n color: var(--e-ink);\n }\n\n .e-tabpanel {\n padding-top: var(--e-space-4);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .e-tab {\n transition: none;\n }\n }\n\n @media (forced-colors: active) {\n .e-tab[aria-selected=\"true\"] {\n forced-color-adjust: none;\n background: Highlight;\n color: HighlightText;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"ETabs.js","names":["_sfc_main","ETabs_vue_vue_type_script_setup_true_lang_default","ETabs_default"],"sources":["../../../src/components/Tabs/ETabs.vue"],"sourcesContent":["<script lang=\"ts\">\nexport interface TabItem {\n /** Names the tab's slot, and is the value of `v-model`. */\n id: string;\n /** The tab's words. */\n label: string;\n /** A number or short word in a small badge after the label. */\n count?: number | string;\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, ref, watch } from \"vue\";\nimport { TabsContent, TabsList, TabsRoot, TabsTrigger } from \"reka-ui\";\nimport { useConfig } from \"../../config.js\";\nimport \"../../styles/base.css\";\n\nconst props = defineProps<{\n /** The tabs, in order: `{ id, label, count? }`. Two to five, with short labels. `id` names the slot and is the `v-model` value. */\n items?: TabItem[];\n /** Share the width equally between the tabs instead of letting each take its own. For a narrow container where the tabs must all fit; labels truncate. @default false */\n fill?: boolean;\n}>();\n\ndefineSlots<{\n [id: string]: ((props: { item: TabItem }) => unknown) | undefined;\n /** The panel of any tab that has no slot of its own. */\n panel?(props: { item: TabItem }): unknown;\n}>();\n\nconst flat = computed(() => props.items ?? []);\n\n/** The `id` of the selected tab. Keeps its own state when unbound. @default first tab */\nconst model = defineModel<string>();\n\nconst config = useConfig();\nconst current = computed(() => model.value ?? flat.value[0]?.id);\nconst dir = computed<\"ltr\" | \"rtl\">(\n () => config.dir ?? (typeof document !== \"undefined\" && document.documentElement.dir === \"rtl\" ? \"rtl\" : \"ltr\"),\n);\n\nconst list = ref<{ $el: HTMLElement } | null>(null);\n\nasync function reveal(smooth: boolean) {\n await nextTick();\n const el = list.value?.$el;\n const tab = el?.querySelector<HTMLElement>('[aria-selected=\"true\"]');\n if (!el || !tab || el.scrollWidth <= el.clientWidth) return;\n const calm = typeof matchMedia !== \"undefined\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n const a = tab.getBoundingClientRect();\n const b = el.getBoundingClientRect();\n el.scrollBy({ left: a.left + a.width / 2 - (b.left + b.width / 2), behavior: smooth && !calm ? \"smooth\" : \"auto\" });\n}\n\nonMounted(() => reveal(false));\nwatch(current, () => reveal(true));\n</script>\n\n<template>\n <TabsRoot class=\"e-tabs\" :class=\"{ 'e-tabs--fill': fill }\" :model-value=\"current\" :dir=\"dir\" @update:model-value=\"model = String($event)\">\n <TabsList ref=\"list\" class=\"e-tablist\">\n <TabsTrigger v-for=\"item in flat\" :key=\"item.id\" :value=\"item.id\" class=\"e-tab\">\n {{ item.label }}\n <span v-if=\"item.count != null\" class=\"e-tab-count\">{{ item.count }}</span>\n </TabsTrigger>\n </TabsList>\n <TabsContent v-for=\"item in flat\" :key=\"item.id\" :value=\"item.id\" class=\"e-tabpanel\">\n <slot :name=\"item.id\" :item=\"item\"><slot name=\"panel\" :item=\"item\" /></slot>\n </TabsContent>\n </TabsRoot>\n</template>\n\n<style>\n@layer e.containers {\n .e-tablist {\n display: flex;\n gap: 0;\n /* stylelint-disable-next-line declaration-block-no-redundant-longhand-properties -- the two axes are set apart */\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n }\n\n .e-tablist::-webkit-scrollbar {\n display: none;\n }\n\n /* Tabs are button-like: every tab is ringed and neighbours sit flush and share an edge, so only the outer ends of the row are rounded. The selected tab is outlined in the primary colour with a thick slab underneath. */\n .e-tab {\n --e-bc: var(--e-border-strong);\n\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: var(--e-space-2);\n flex: none;\n height: var(--e-control-height);\n padding: 0 var(--e-space-4);\n border: var(--e-stroke-edge) solid var(--e-bc);\n border-radius: 0;\n outline: var(--e-stroke-edge) solid var(--e-bc);\n outline-offset: calc(-1 * var(--e-stroke-edge));\n background: transparent;\n color: var(--e-muted);\n font: 500 var(--e-text-md) / 1 var(--e-font-sans);\n cursor: pointer;\n transition:\n color var(--e-duration-tap) var(--e-easing-out),\n background-color var(--e-duration-tap) var(--e-easing-out);\n }\n\n /* Only the outer ends of the row are rounded. */\n .e-tab:first-child {\n border-start-start-radius: var(--e-radius-md);\n border-end-start-radius: var(--e-radius-md);\n }\n\n .e-tab:last-child {\n border-start-end-radius: var(--e-radius-md);\n border-end-end-radius: var(--e-radius-md);\n }\n\n .e-tab + .e-tab {\n margin-inline-start: calc(-1 * var(--e-stroke-edge));\n }\n\n .e-tab:hover {\n --e-bc: var(--e-ink);\n\n z-index: var(--e-z-raised);\n color: var(--e-ink);\n background-color: var(--e-surface-hover);\n }\n\n .e-tab[aria-selected=\"true\"] {\n --e-bc: var(--e-primary);\n\n z-index: var(--e-z-sticky);\n border-bottom-width: calc(var(--e-stroke-edge) + var(--e-depth));\n background-color: color-mix(in srgb, var(--e-surface-tint), var(--e-ink) 3%);\n color: var(--e-ink);\n font-weight: 600;\n }\n\n .e-tab:focus-visible {\n z-index: var(--e-z-focus);\n outline: var(--e-stroke-focus) solid var(--e-focus-ring);\n outline-offset: var(--e-focus-gap);\n }\n\n .e-tabs--fill .e-tablist {\n overflow-x: hidden;\n }\n\n .e-tabs--fill .e-tab {\n flex: 1 1 0;\n min-width: 0;\n justify-content: center;\n padding-inline: var(--e-space-1);\n }\n\n .e-tab-count {\n min-width: var(--e-space-6);\n padding: 0 var(--e-space-2);\n border-radius: var(--e-radius-pill);\n background: color-mix(in srgb, var(--e-primary) 12%, var(--e-surface));\n box-shadow: inset 0 0 0 var(--e-stroke-hair) var(--e-primary);\n outline: var(--e-stroke-hair) solid var(--e-primary);\n outline-offset: calc(-1 * var(--e-stroke-hair));\n font: 600 var(--e-text-xs) / 20px var(--e-font-mono);\n text-align: center;\n color: var(--e-ink);\n }\n\n .e-tabpanel {\n padding-top: var(--e-space-4);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .e-tab {\n transition: none;\n }\n }\n\n @media (forced-colors: active) {\n .e-tab[aria-selected=\"true\"] {\n forced-color-adjust: none;\n background: Highlight;\n color: HighlightText;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -11,6 +11,8 @@ export default _default;
|
|
|
11
11
|
declare const __VLS_export: __VLS_WithSlots<import('vue').DefineComponent<{
|
|
12
12
|
/** The tabs, in order: `{ id, label, count? }`. Two to five, with short labels. `id` names the slot and is the `v-model` value. */
|
|
13
13
|
items?: TabItem[];
|
|
14
|
+
/** Share the width equally between the tabs instead of letting each take its own. For a narrow container where the tabs must all fit; labels truncate. @default false */
|
|
15
|
+
fill?: boolean;
|
|
14
16
|
} & {
|
|
15
17
|
/** The `id` of the selected tab. Keeps its own state when unbound. @default first tab */
|
|
16
18
|
modelValue?: string;
|
|
@@ -19,6 +21,8 @@ declare const __VLS_export: __VLS_WithSlots<import('vue').DefineComponent<{
|
|
|
19
21
|
}, string, import('vue').PublicProps, Readonly<{
|
|
20
22
|
/** The tabs, in order: `{ id, label, count? }`. Two to five, with short labels. `id` names the slot and is the `v-model` value. */
|
|
21
23
|
items?: TabItem[];
|
|
24
|
+
/** Share the width equally between the tabs instead of letting each take its own. For a narrow container where the tabs must all fit; labels truncate. @default false */
|
|
25
|
+
fill?: boolean;
|
|
22
26
|
} & {
|
|
23
27
|
/** The `id` of the selected tab. Keeps its own state when unbound. @default first tab */
|
|
24
28
|
modelValue?: string;
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
/* empty css */
|
|
2
2
|
import { useConfig as e } from "../../config.js";
|
|
3
|
-
import { Fragment as t, computed as n, createBlock as r, createCommentVNode as i, createElementBlock as a, createTextVNode as o, createVNode as s, defineComponent as c, mergeModels as l, nextTick as u,
|
|
4
|
-
import { TabsContent as
|
|
3
|
+
import { Fragment as t, computed as n, createBlock as r, createCommentVNode as i, createElementBlock as a, createTextVNode as o, createVNode as s, defineComponent as c, mergeModels as l, nextTick as u, normalizeClass as d, onMounted as f, openBlock as p, ref as m, renderList as h, renderSlot as g, toDisplayString as _, unref as v, useModel as y, watch as b, withCtx as x } from "vue";
|
|
4
|
+
import { TabsContent as S, TabsList as C, TabsRoot as w, TabsTrigger as T } from "reka-ui";
|
|
5
5
|
//#region src/components/Tabs/ETabs.vue?vue&type=script&setup=true&lang.ts
|
|
6
|
-
var
|
|
6
|
+
var E = {
|
|
7
7
|
key: 0,
|
|
8
8
|
class: "e-tab-count"
|
|
9
|
-
},
|
|
9
|
+
}, D = /*@__PURE__*/ c({
|
|
10
10
|
__name: "ETabs",
|
|
11
|
-
props: /*@__PURE__*/ l({
|
|
11
|
+
props: /*@__PURE__*/ l({
|
|
12
|
+
items: {},
|
|
13
|
+
fill: { type: Boolean }
|
|
14
|
+
}, {
|
|
12
15
|
modelValue: {},
|
|
13
16
|
modelModifiers: {}
|
|
14
17
|
}),
|
|
15
18
|
emits: ["update:modelValue"],
|
|
16
19
|
setup(c) {
|
|
17
|
-
let l = c,
|
|
18
|
-
async function
|
|
20
|
+
let l = c, D = n(() => l.items ?? []), O = y(c, "modelValue"), k = e(), A = n(() => O.value ?? D.value[0]?.id), j = n(() => k.dir ?? (typeof document < "u" && document.documentElement.dir === "rtl" ? "rtl" : "ltr")), M = m(null);
|
|
21
|
+
async function N(e) {
|
|
19
22
|
await u();
|
|
20
|
-
let t =
|
|
23
|
+
let t = M.value?.$el, n = t?.querySelector("[aria-selected=\"true\"]");
|
|
21
24
|
if (!t || !n || t.scrollWidth <= t.clientWidth) return;
|
|
22
25
|
let r = typeof matchMedia < "u" && matchMedia("(prefers-reduced-motion: reduce)").matches, i = n.getBoundingClientRect(), a = t.getBoundingClientRect();
|
|
23
26
|
t.scrollBy({
|
|
@@ -25,39 +28,43 @@ var T = {
|
|
|
25
28
|
behavior: e && !r ? "smooth" : "auto"
|
|
26
29
|
});
|
|
27
30
|
}
|
|
28
|
-
return
|
|
29
|
-
class: "e-tabs",
|
|
30
|
-
"model-value":
|
|
31
|
-
dir:
|
|
32
|
-
"onUpdate:modelValue": n[0] ||= (e) =>
|
|
31
|
+
return f(() => N(!1)), b(A, () => N(!0)), (e, n) => (p(), r(v(w), {
|
|
32
|
+
class: d(["e-tabs", { "e-tabs--fill": c.fill }]),
|
|
33
|
+
"model-value": A.value,
|
|
34
|
+
dir: j.value,
|
|
35
|
+
"onUpdate:modelValue": n[0] ||= (e) => O.value = String(e)
|
|
33
36
|
}, {
|
|
34
|
-
default:
|
|
37
|
+
default: x(() => [s(v(C), {
|
|
35
38
|
ref_key: "list",
|
|
36
|
-
ref:
|
|
39
|
+
ref: M,
|
|
37
40
|
class: "e-tablist"
|
|
38
41
|
}, {
|
|
39
|
-
default:
|
|
42
|
+
default: x(() => [(p(!0), a(t, null, h(D.value, (e) => (p(), r(v(T), {
|
|
40
43
|
key: e.id,
|
|
41
44
|
value: e.id,
|
|
42
45
|
class: "e-tab"
|
|
43
46
|
}, {
|
|
44
|
-
default:
|
|
47
|
+
default: x(() => [o(_(e.label) + " ", 1), e.count == null ? i("", !0) : (p(), a("span", E, _(e.count), 1))]),
|
|
45
48
|
_: 2
|
|
46
49
|
}, 1032, ["value"]))), 128))]),
|
|
47
50
|
_: 1
|
|
48
|
-
}, 512), (
|
|
51
|
+
}, 512), (p(!0), a(t, null, h(D.value, (t) => (p(), r(v(S), {
|
|
49
52
|
key: t.id,
|
|
50
53
|
value: t.id,
|
|
51
54
|
class: "e-tabpanel"
|
|
52
55
|
}, {
|
|
53
|
-
default:
|
|
56
|
+
default: x(() => [g(e.$slots, t.id, { item: t }, () => [g(e.$slots, "panel", { item: t })])]),
|
|
54
57
|
_: 2
|
|
55
58
|
}, 1032, ["value"]))), 128))]),
|
|
56
59
|
_: 3
|
|
57
|
-
}, 8, [
|
|
60
|
+
}, 8, [
|
|
61
|
+
"class",
|
|
62
|
+
"model-value",
|
|
63
|
+
"dir"
|
|
64
|
+
]));
|
|
58
65
|
}
|
|
59
66
|
});
|
|
60
67
|
//#endregion
|
|
61
|
-
export {
|
|
68
|
+
export { D as default };
|
|
62
69
|
|
|
63
70
|
//# sourceMappingURL=ETabs.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ETabs.vue_vue_type_script_setup_true_lang.js","names":["_useModel","_openBlock","_createBlock","_unref","_withCtx","_createVNode","_createElementBlock","_Fragment","_renderList","_createTextVNode","_toDisplayString","_createCommentVNode","_renderSlot","_hoisted_1","ETabs_vue_vue_type_script_setup_true_lang_default","\"modelValue\"","\"modelModifiers\"","props","__props","flat","computed","model","useModel","config","useConfig","current","dir","list","ref","reveal","nextTick","el","tab","calm","a","b","smooth","onMounted","watch","openBlock","createBlock","unref","TabsRoot","_cache","$event","withCtx","createVNode","TabsList","createElementBlock","Fragment","renderList","TabsTrigger","item","createTextVNode","toDisplayString","createCommentVNode","TabsContent","renderSlot","_ctx"],"sources":["../../../src/components/Tabs/ETabs.vue"],"sourcesContent":["<script lang=\"ts\">\nexport interface TabItem {\n /** Names the tab's slot, and is the value of `v-model`. */\n id: string;\n /** The tab's words. */\n label: string;\n /** A number or short word in a small badge after the label. */\n count?: number | string;\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, ref, watch } from \"vue\";\nimport { TabsContent, TabsList, TabsRoot, TabsTrigger } from \"reka-ui\";\nimport { useConfig } from \"../../config.js\";\nimport \"../../styles/base.css\";\n\nconst props = defineProps<{\n /** The tabs, in order: `{ id, label, count? }`. Two to five, with short labels. `id` names the slot and is the `v-model` value. */\n items?: TabItem[];\n}>();\n\ndefineSlots<{\n [id: string]: ((props: { item: TabItem }) => unknown) | undefined;\n /** The panel of any tab that has no slot of its own. */\n panel?(props: { item: TabItem }): unknown;\n}>();\n\nconst flat = computed(() => props.items ?? []);\n\n/** The `id` of the selected tab. Keeps its own state when unbound. @default first tab */\nconst model = defineModel<string>();\n\nconst config = useConfig();\nconst current = computed(() => model.value ?? flat.value[0]?.id);\nconst dir = computed<\"ltr\" | \"rtl\">(\n () => config.dir ?? (typeof document !== \"undefined\" && document.documentElement.dir === \"rtl\" ? \"rtl\" : \"ltr\"),\n);\n\nconst list = ref<{ $el: HTMLElement } | null>(null);\n\nasync function reveal(smooth: boolean) {\n await nextTick();\n const el = list.value?.$el;\n const tab = el?.querySelector<HTMLElement>('[aria-selected=\"true\"]');\n if (!el || !tab || el.scrollWidth <= el.clientWidth) return;\n const calm = typeof matchMedia !== \"undefined\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n const a = tab.getBoundingClientRect();\n const b = el.getBoundingClientRect();\n el.scrollBy({ left: a.left + a.width / 2 - (b.left + b.width / 2), behavior: smooth && !calm ? \"smooth\" : \"auto\" });\n}\n\nonMounted(() => reveal(false));\nwatch(current, () => reveal(true));\n</script>\n\n<template>\n <TabsRoot class=\"e-tabs\" :model-value=\"current\" :dir=\"dir\" @update:model-value=\"model = String($event)\">\n <TabsList ref=\"list\" class=\"e-tablist\">\n <TabsTrigger v-for=\"item in flat\" :key=\"item.id\" :value=\"item.id\" class=\"e-tab\">\n {{ item.label }}\n <span v-if=\"item.count != null\" class=\"e-tab-count\">{{ item.count }}</span>\n </TabsTrigger>\n </TabsList>\n <TabsContent v-for=\"item in flat\" :key=\"item.id\" :value=\"item.id\" class=\"e-tabpanel\">\n <slot :name=\"item.id\" :item=\"item\"><slot name=\"panel\" :item=\"item\" /></slot>\n </TabsContent>\n </TabsRoot>\n</template>\n\n<style>\n@layer e.containers {\n .e-tablist {\n display: flex;\n gap: 0;\n /* stylelint-disable-next-line declaration-block-no-redundant-longhand-properties -- the two axes are set apart */\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n }\n\n .e-tablist::-webkit-scrollbar {\n display: none;\n }\n\n /* Tabs are button-like: every tab is ringed and neighbours sit flush and share an edge, so only the outer ends of the row are rounded. The selected tab is outlined in the primary colour with a thick slab underneath. */\n .e-tab {\n --e-bc: var(--e-border-strong);\n\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: var(--e-space-2);\n flex: none;\n height: var(--e-control-height);\n padding: 0 var(--e-space-4);\n border: var(--e-stroke-edge) solid var(--e-bc);\n border-radius: 0;\n outline: var(--e-stroke-edge) solid var(--e-bc);\n outline-offset: calc(-1 * var(--e-stroke-edge));\n background: transparent;\n color: var(--e-muted);\n font: 500 var(--e-text-md) / 1 var(--e-font-sans);\n cursor: pointer;\n transition:\n color var(--e-duration-tap) var(--e-easing-out),\n background-color var(--e-duration-tap) var(--e-easing-out);\n }\n\n /* Only the outer ends of the row are rounded. */\n .e-tab:first-child {\n border-start-start-radius: var(--e-radius-md);\n border-end-start-radius: var(--e-radius-md);\n }\n\n .e-tab:last-child {\n border-start-end-radius: var(--e-radius-md);\n border-end-end-radius: var(--e-radius-md);\n }\n\n .e-tab + .e-tab {\n margin-inline-start: calc(-1 * var(--e-stroke-edge));\n }\n\n .e-tab:hover {\n --e-bc: var(--e-ink);\n\n z-index: var(--e-z-raised);\n color: var(--e-ink);\n background-color: var(--e-surface-hover);\n }\n\n .e-tab[aria-selected=\"true\"] {\n --e-bc: var(--e-primary);\n\n z-index: var(--e-z-sticky);\n border-bottom-width: calc(var(--e-stroke-edge) + var(--e-depth));\n background-color: color-mix(in srgb, var(--e-surface-tint), var(--e-ink) 3%);\n color: var(--e-ink);\n font-weight: 600;\n }\n\n .e-tab:focus-visible {\n z-index: var(--e-z-focus);\n outline: var(--e-stroke-focus) solid var(--e-focus-ring);\n outline-offset: var(--e-focus-gap);\n }\n\n .e-tab-count {\n min-width: var(--e-space-6);\n padding: 0 var(--e-space-2);\n border-radius: var(--e-radius-pill);\n background: color-mix(in srgb, var(--e-primary) 12%, var(--e-surface));\n box-shadow: inset 0 0 0 var(--e-stroke-hair) var(--e-primary);\n outline: var(--e-stroke-hair) solid var(--e-primary);\n outline-offset: calc(-1 * var(--e-stroke-hair));\n font: 600 var(--e-text-xs) / 20px var(--e-font-mono);\n text-align: center;\n color: var(--e-ink);\n }\n\n .e-tabpanel {\n padding-top: var(--e-space-4);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .e-tab {\n transition: none;\n }\n }\n\n @media (forced-colors: active) {\n .e-tab[aria-selected=\"true\"] {\n forced-color-adjust: none;\n background: Highlight;\n color: HighlightText;\n }\n }\n}\n</style>\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"ETabs.vue_vue_type_script_setup_true_lang.js","names":["_useModel","_openBlock","_createBlock","_unref","_normalizeClass","_withCtx","_createVNode","_createElementBlock","_Fragment","_renderList","_createTextVNode","_toDisplayString","_createCommentVNode","_renderSlot","_hoisted_1","ETabs_vue_vue_type_script_setup_true_lang_default","\"modelValue\"","\"modelModifiers\"","props","__props","flat","computed","model","useModel","config","useConfig","current","dir","list","ref","reveal","nextTick","el","tab","calm","a","b","smooth","onMounted","watch","openBlock","createBlock","unref","TabsRoot","normalizeClass","_cache","$event","withCtx","createVNode","TabsList","createElementBlock","Fragment","renderList","TabsTrigger","item","createTextVNode","toDisplayString","createCommentVNode","TabsContent","renderSlot","_ctx"],"sources":["../../../src/components/Tabs/ETabs.vue"],"sourcesContent":["<script lang=\"ts\">\nexport interface TabItem {\n /** Names the tab's slot, and is the value of `v-model`. */\n id: string;\n /** The tab's words. */\n label: string;\n /** A number or short word in a small badge after the label. */\n count?: number | string;\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, ref, watch } from \"vue\";\nimport { TabsContent, TabsList, TabsRoot, TabsTrigger } from \"reka-ui\";\nimport { useConfig } from \"../../config.js\";\nimport \"../../styles/base.css\";\n\nconst props = defineProps<{\n /** The tabs, in order: `{ id, label, count? }`. Two to five, with short labels. `id` names the slot and is the `v-model` value. */\n items?: TabItem[];\n /** Share the width equally between the tabs instead of letting each take its own. For a narrow container where the tabs must all fit; labels truncate. @default false */\n fill?: boolean;\n}>();\n\ndefineSlots<{\n [id: string]: ((props: { item: TabItem }) => unknown) | undefined;\n /** The panel of any tab that has no slot of its own. */\n panel?(props: { item: TabItem }): unknown;\n}>();\n\nconst flat = computed(() => props.items ?? []);\n\n/** The `id` of the selected tab. Keeps its own state when unbound. @default first tab */\nconst model = defineModel<string>();\n\nconst config = useConfig();\nconst current = computed(() => model.value ?? flat.value[0]?.id);\nconst dir = computed<\"ltr\" | \"rtl\">(\n () => config.dir ?? (typeof document !== \"undefined\" && document.documentElement.dir === \"rtl\" ? \"rtl\" : \"ltr\"),\n);\n\nconst list = ref<{ $el: HTMLElement } | null>(null);\n\nasync function reveal(smooth: boolean) {\n await nextTick();\n const el = list.value?.$el;\n const tab = el?.querySelector<HTMLElement>('[aria-selected=\"true\"]');\n if (!el || !tab || el.scrollWidth <= el.clientWidth) return;\n const calm = typeof matchMedia !== \"undefined\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n const a = tab.getBoundingClientRect();\n const b = el.getBoundingClientRect();\n el.scrollBy({ left: a.left + a.width / 2 - (b.left + b.width / 2), behavior: smooth && !calm ? \"smooth\" : \"auto\" });\n}\n\nonMounted(() => reveal(false));\nwatch(current, () => reveal(true));\n</script>\n\n<template>\n <TabsRoot class=\"e-tabs\" :class=\"{ 'e-tabs--fill': fill }\" :model-value=\"current\" :dir=\"dir\" @update:model-value=\"model = String($event)\">\n <TabsList ref=\"list\" class=\"e-tablist\">\n <TabsTrigger v-for=\"item in flat\" :key=\"item.id\" :value=\"item.id\" class=\"e-tab\">\n {{ item.label }}\n <span v-if=\"item.count != null\" class=\"e-tab-count\">{{ item.count }}</span>\n </TabsTrigger>\n </TabsList>\n <TabsContent v-for=\"item in flat\" :key=\"item.id\" :value=\"item.id\" class=\"e-tabpanel\">\n <slot :name=\"item.id\" :item=\"item\"><slot name=\"panel\" :item=\"item\" /></slot>\n </TabsContent>\n </TabsRoot>\n</template>\n\n<style>\n@layer e.containers {\n .e-tablist {\n display: flex;\n gap: 0;\n /* stylelint-disable-next-line declaration-block-no-redundant-longhand-properties -- the two axes are set apart */\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n }\n\n .e-tablist::-webkit-scrollbar {\n display: none;\n }\n\n /* Tabs are button-like: every tab is ringed and neighbours sit flush and share an edge, so only the outer ends of the row are rounded. The selected tab is outlined in the primary colour with a thick slab underneath. */\n .e-tab {\n --e-bc: var(--e-border-strong);\n\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: var(--e-space-2);\n flex: none;\n height: var(--e-control-height);\n padding: 0 var(--e-space-4);\n border: var(--e-stroke-edge) solid var(--e-bc);\n border-radius: 0;\n outline: var(--e-stroke-edge) solid var(--e-bc);\n outline-offset: calc(-1 * var(--e-stroke-edge));\n background: transparent;\n color: var(--e-muted);\n font: 500 var(--e-text-md) / 1 var(--e-font-sans);\n cursor: pointer;\n transition:\n color var(--e-duration-tap) var(--e-easing-out),\n background-color var(--e-duration-tap) var(--e-easing-out);\n }\n\n /* Only the outer ends of the row are rounded. */\n .e-tab:first-child {\n border-start-start-radius: var(--e-radius-md);\n border-end-start-radius: var(--e-radius-md);\n }\n\n .e-tab:last-child {\n border-start-end-radius: var(--e-radius-md);\n border-end-end-radius: var(--e-radius-md);\n }\n\n .e-tab + .e-tab {\n margin-inline-start: calc(-1 * var(--e-stroke-edge));\n }\n\n .e-tab:hover {\n --e-bc: var(--e-ink);\n\n z-index: var(--e-z-raised);\n color: var(--e-ink);\n background-color: var(--e-surface-hover);\n }\n\n .e-tab[aria-selected=\"true\"] {\n --e-bc: var(--e-primary);\n\n z-index: var(--e-z-sticky);\n border-bottom-width: calc(var(--e-stroke-edge) + var(--e-depth));\n background-color: color-mix(in srgb, var(--e-surface-tint), var(--e-ink) 3%);\n color: var(--e-ink);\n font-weight: 600;\n }\n\n .e-tab:focus-visible {\n z-index: var(--e-z-focus);\n outline: var(--e-stroke-focus) solid var(--e-focus-ring);\n outline-offset: var(--e-focus-gap);\n }\n\n .e-tabs--fill .e-tablist {\n overflow-x: hidden;\n }\n\n .e-tabs--fill .e-tab {\n flex: 1 1 0;\n min-width: 0;\n justify-content: center;\n padding-inline: var(--e-space-1);\n }\n\n .e-tab-count {\n min-width: var(--e-space-6);\n padding: 0 var(--e-space-2);\n border-radius: var(--e-radius-pill);\n background: color-mix(in srgb, var(--e-primary) 12%, var(--e-surface));\n box-shadow: inset 0 0 0 var(--e-stroke-hair) var(--e-primary);\n outline: var(--e-stroke-hair) solid var(--e-primary);\n outline-offset: calc(-1 * var(--e-stroke-hair));\n font: 600 var(--e-text-xs) / 20px var(--e-font-mono);\n text-align: center;\n color: var(--e-ink);\n }\n\n .e-tabpanel {\n padding-top: var(--e-space-4);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .e-tab {\n transition: none;\n }\n }\n\n @media (forced-colors: active) {\n .e-tab[aria-selected=\"true\"] {\n forced-color-adjust: none;\n background: Highlight;\n color: HighlightText;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;EAiBA,IAAMkB,IAAQC,GAaRC,IAAOC,QAAeH,EAAM,SAAS,CAAC,CAAC,GAGvCI,IAAQtB,EAAmBmB,GAAA,YAAC,GAE5BK,IAASC,EAAU,GACnBC,IAAUL,QAAeC,EAAM,SAASF,EAAK,MAAM,EAAE,EAAE,EAAE,GACzDO,IAAMN,QACJG,EAAO,QAAQ,OAAO,WAAa,OAAe,SAAS,gBAAgB,QAAQ,QAAQ,QAAQ,MAC3G,GAEMI,IAAOC,EAAiC,IAAI;EAElD,eAAeC,EAAO,GAAiB;GACrC,MAAMC,EAAS;GACf,IAAMC,IAAKJ,EAAK,OAAO,KACjBK,IAAMD,GAAI,cAA2B,0BAAwB;GACnE,IAAI,CAACA,KAAM,CAACC,KAAOD,EAAG,eAAeA,EAAG,aAAa;GACrD,IAAME,IAAO,OAAO,aAAe,OAAe,WAAW,kCAAkC,CAAC,CAAC,SAC3FC,IAAIF,EAAI,sBAAsB,GAC9BG,IAAIJ,EAAG,sBAAsB;GACnC,EAAG,SAAS;IAAE,MAAMG,EAAE,OAAOA,EAAE,QAAQ,KAAKC,EAAE,OAAOA,EAAE,QAAQ;IAAI,UAAUC,KAAU,CAACH,IAAO,WAAW;GAAO,CAAC;EACpH;E,OAEAI,QAAgBR,EAAO,EAAK,CAAC,GAC7BS,EAAMb,SAAeI,EAAO,EAAI,CAAC,I,UAI/B7B,EAAA,GAAAC,EAUWC,EAAAwC,CAAA,GAAA;GAVD,OAAKvC,EAAA,CAAC,UAAQ,EAAA,gBAA2Be,EAAA,KAAI,CAAA,CAAA;GAAK,eAAaO,EAAA;GAAU,KAAKC,EAAA;GAAM,uBAAkB,AAAAkB,EAAA,QAAA,MAAE,EAAA,QAAQ,OAAOC,CAAM;;GACrI,SAAAzC,QAKW,CALXC,EAKWH,EAAA8C,CAAA,GAAA;IALG,SAAA;IAAJ,KAAIrB;IAAO,OAAM;;IACZ,SAAAvB,QAAoB,EAAjCJ,EAAA,EAAA,GAAAM,EAGcC,GAAA,MAAAC,EAHcW,EAAA,QAAR,OAApBnB,EAAA,GAAAC,EAGcC,EAAAkD,CAAA,GAAA;KAHqB,KAAKC,EAAK;KAAK,OAAOA,EAAK;KAAI,OAAM;;KACtE,SAAAjD,QAAgB,CAAbK,EAAAC,EAAA2C,EAAK,KAAK,IAAG,KAChB,CAAA,GAAYA,EAAK,SAAK,O,aAAtBrD,EAAA,GAAAM,EAA2E,QAA3EO,GAA2EH,EAApB2C,EAAK,KAAK,GAAA,CAAA,E;;;;G,UAGrErD,EAAA,EAAA,GAAAM,EAEcC,GAAA,MAAAC,EAFcW,EAAA,QAAR,OAApBnB,EAAA,GAAAC,EAEcC,EAAAuD,CAAA,GAAA;IAFqB,KAAKJ,EAAK;IAAK,OAAOA,EAAK;IAAI,OAAM;;IACtE,SAAAjD,QAA4E,CAA5EQ,EAA4E+C,EAAA,QAA/DN,EAAK,IAAE,EAAS,QAAI,SAA2C,CAAzCzC,EAAkC+C,EAAA,QAAA,SAAA,EAAR,QAAI,CAAA,C"}
|
package/dist/style.css
CHANGED
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
--e-measure: 62ch;
|
|
59
59
|
--e-stroke-hair: 1px;
|
|
60
60
|
--e-stroke-card: 1px;
|
|
61
|
-
--e-stroke-edge:
|
|
61
|
+
--e-stroke-edge: 1px;
|
|
62
62
|
--e-stroke-focus: 2px;
|
|
63
63
|
--e-focus-gap: 2px;
|
|
64
64
|
--e-focus-gap-field: calc(-1 * var(--e-stroke-edge));
|
|
@@ -4881,6 +4881,15 @@ to {
|
|
|
4881
4881
|
outline: var(--e-stroke-focus) solid var(--e-focus-ring);
|
|
4882
4882
|
outline-offset: var(--e-focus-gap);
|
|
4883
4883
|
}
|
|
4884
|
+
.e-tabs--fill .e-tablist {
|
|
4885
|
+
overflow-x: hidden;
|
|
4886
|
+
}
|
|
4887
|
+
.e-tabs--fill .e-tab {
|
|
4888
|
+
flex: 1 1 0;
|
|
4889
|
+
min-width: 0;
|
|
4890
|
+
justify-content: center;
|
|
4891
|
+
padding-inline: var(--e-space-1);
|
|
4892
|
+
}
|
|
4884
4893
|
.e-tab-count {
|
|
4885
4894
|
min-width: var(--e-space-6);
|
|
4886
4895
|
padding: 0 var(--e-space-2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eos-ui/vue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Eos: accessible Vue 3 components for the Eos portal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@eos-ui/tokens": "0.
|
|
60
|
+
"@eos-ui/tokens": "0.3.0",
|
|
61
61
|
"@tanstack/vue-table": "^9.3.1",
|
|
62
62
|
"@tanstack/vue-virtual": "^3.13.47",
|
|
63
63
|
"@vueuse/core": "^15.0.0",
|