diff --git a/demo/partials/lists.html b/demo/partials/lists.html
index 22e5832..1af4b7f 100644
--- a/demo/partials/lists.html
+++ b/demo/partials/lists.html
@@ -51,4 +51,88 @@
`
}).mount("#vue-action-list-app");
+
+
Definition list
+
+
+
- Item
+ - Basic interface entity.
+
+
+
- Group
+ - Logical grouping of elements.
+
+
+
- Flow
+ - Set of actions performed sequentially.
+
+
+
+ Vue definition list
+
+
+
+ Navigation list
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ Vue navigation list
+
+
diff --git a/demo/partials/progress.html b/demo/partials/progress.html
index 5d8a547..25f5196 100644
--- a/demo/partials/progress.html
+++ b/demo/partials/progress.html
@@ -58,6 +58,38 @@
}).mount("#vue-progress-app");
+ Staged progress
+
+
+ Queued
+ Build
+ Deploy
+ Verify
+
+
+
+ Vue staged progress
+
+
+
Usage meter
diff --git a/docs/component-coverage.md b/docs/component-coverage.md
index 506e332..90055b6 100644
--- a/docs/component-coverage.md
+++ b/docs/component-coverage.md
@@ -37,5 +37,19 @@
| Tooltip | done | GnTooltip | partial |
| Dropdown | done | GnDropdown, GnDropdownItem | partial |
| NavigationShell | done | GnNavigationShell, GnNavigationItem | partial |
+| DescriptionList | done | GnDescriptionList | partial |
+| DefinitionList | done | GnDefinitionList | partial |
+| List | done | GnList, GnListItem | partial |
+| NavList | done | GnNavList | partial |
+| Steps | done | GnSteps, GnStep | partial |
+| ProgressStages | done | GnProgressStages | partial |
+| Breadcrumbs | done | GnBreadcrumbs, GnBreadcrumbItem | partial |
+| Divider | done | GnDivider | partial |
+| Popover | done | GnPopover | partial |
+| Pagination | done | GnPagination | partial |
+| Timeline | done | GnTimeline | partial |
+| FileUpload | done | GnFileUpload, GnFileAttachment | partial |
+| Repeater | done | GnRepeater | partial |
+| UsageMeter | done | GnUsageMeter | partial |
Статусы: `—` не начато, `WIP` в работе, `done` готово, `redesigned` перерисован в новом стиле.
diff --git a/src/scss/components/_lists.scss b/src/scss/components/_lists.scss
index 2f07442..1226e6c 100644
--- a/src/scss/components/_lists.scss
+++ b/src/scss/components/_lists.scss
@@ -137,3 +137,105 @@
}
}
}
+
+// Definition list — term/value pairs inside a .list container.
+.list-definition {
+ .list-row {
+ display: grid;
+ grid-template-columns: minmax(140px, 0.36fr) minmax(0, 1fr);
+ gap: $space-4;
+ padding: $space-3 $space-4;
+ border-bottom: $border-width-base $border-style-base rgba($neutral-900, 0.06);
+
+ &:last-child {
+ border-bottom: 0;
+ }
+ }
+
+ .list-term {
+ margin: 0;
+ color: $neutral-600;
+ font-size: 13px;
+ font-weight: $font-weight-semibold;
+ line-height: 1.5;
+ text-transform: uppercase;
+ }
+
+ .list-desc {
+ min-width: 0;
+ margin: 0;
+ color: $neutral-900;
+ font-size: 15px;
+ font-weight: $font-weight-medium;
+ line-height: 1.5;
+ }
+}
+
+@media (max-width: 640px) {
+ .list-definition {
+ .list-row {
+ grid-template-columns: 1fr;
+ gap: $space-1;
+ }
+ }
+}
+
+// Navigation / interactive list.
+.list-nav {
+ .list-item {
+ padding: 0;
+
+ &.list-item-active .list-action {
+ background-color: rgba($mint-500, 0.12);
+ color: $neutral-900;
+ }
+
+ &.list-item-disabled .list-action {
+ opacity: 0.6;
+ cursor: not-allowed;
+ }
+ }
+
+ .list-action {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: $space-3;
+ width: 100%;
+ padding: $space-3 $space-4;
+ border: 0;
+ background: transparent;
+ color: inherit;
+ font: inherit;
+ text-align: left;
+ text-decoration: none;
+ cursor: pointer;
+ transition: background-color $motion-base $motion-ease;
+
+ &:hover:not(:disabled) {
+ background-color: rgba($neutral-900, 0.04);
+ }
+
+ &:focus-visible {
+ @include focus_ring;
+ outline-offset: -2px;
+ }
+ }
+
+ .list-label {
+ display: inline-flex;
+ align-items: center;
+ gap: $space-2;
+
+ .ph {
+ color: $neutral-600;
+ }
+ }
+
+ .list-meta {
+ margin-left: auto;
+ color: $neutral-500;
+ font-size: 12px;
+ font-weight: $font-weight-semibold;
+ }
+}
diff --git a/src/scss/components/_progress.scss b/src/scss/components/_progress.scss
index 88126c5..4d751d8 100644
--- a/src/scss/components/_progress.scss
+++ b/src/scss/components/_progress.scss
@@ -86,6 +86,56 @@
}
}
+// Staged progress
+.progress-stages {
+ display: inline-flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: $space-2;
+ font-size: 13px;
+ font-weight: $font-weight-semibold;
+ line-height: 1;
+
+ .progress-stage {
+ display: inline-flex;
+ align-items: center;
+ gap: $space-2;
+ padding: $space-2 $space-3;
+ border-radius: $radius-md;
+ background-color: $neutral-100;
+ color: $neutral-500;
+ transition: background-color $motion-base $motion-ease, color $motion-base $motion-ease;
+
+ &::before {
+ content: "";
+ width: 8px;
+ height: 8px;
+ border-radius: $radius-full;
+ background-color: currentColor;
+ opacity: 0.5;
+ }
+
+ &.progress-stage-complete {
+ background-color: rgba($success-text, 0.12);
+ color: $success-text;
+
+ &::before {
+ opacity: 1;
+ }
+ }
+
+ &.progress-stage-current {
+ background-color: rgba($mint-600, 0.12);
+ color: $mint-700;
+
+ &::before {
+ opacity: 1;
+ box-shadow: 0 0 0 3px rgba($mint-600, 0.2);
+ }
+ }
+ }
+}
+
/* Usage meter */
.usage-meter {
width: 100%;
diff --git a/src/vue/components/GnDefinitionList.js b/src/vue/components/GnDefinitionList.js
new file mode 100644
index 0000000..51a741b
--- /dev/null
+++ b/src/vue/components/GnDefinitionList.js
@@ -0,0 +1,27 @@
+/**
+ * GnDefinitionList — term/value pairs rendered as a definition list.
+ *
+ * API-compatible with gnexus-ui-kit GnDefinitionList.
+ */
+
+import { defineComponent, h } from "vue";
+import { cx } from "../utils.js";
+
+export default defineComponent({
+ name: "GnDefinitionList",
+ inheritAttrs: false,
+ props: {
+ items: { type: Array, default: () => [] }
+ },
+ setup(props, { attrs, slots }) {
+ return () => h("dl", {
+ ...attrs,
+ class: cx("list", "list-definition", attrs.class)
+ }, props.items.map(item => h("div", {
+ class: "list-row"
+ }, [
+ h("dt", { class: "list-term" }, item.term || item.label || item.key),
+ h("dd", { class: "list-desc" }, slots[item.key]?.({ item }) || item.description || item.value)
+ ])));
+ }
+});
diff --git a/src/vue/components/GnNavList.js b/src/vue/components/GnNavList.js
new file mode 100644
index 0000000..253f1e1
--- /dev/null
+++ b/src/vue/components/GnNavList.js
@@ -0,0 +1,91 @@
+/**
+ * GnNavList — interactive navigation list with optional vue-router support.
+ *
+ * API-compatible with gnexus-ui-kit GnNavList.
+ */
+
+import { computed, defineComponent, h } from "vue";
+import { cx, iconNode } from "../utils.js";
+import { tryUseRouter, tryUseRoute, isRouteActive } from "../composables/useVueRouter.js";
+
+export default defineComponent({
+ name: "GnNavList",
+ inheritAttrs: false,
+ props: {
+ items: { type: Array, default: () => [] },
+ activeMatch: { type: String, default: "prefix" }
+ },
+ emits: ["select"],
+ setup(props, { attrs, emit, slots }) {
+ const router = tryUseRouter();
+ const route = tryUseRoute();
+ const hasRouter = Boolean(router && route);
+
+ const resolveHref = to => {
+ if (!to) {
+ return undefined;
+ }
+
+ if (typeof to === "string") {
+ return to;
+ }
+
+ if (to.path) {
+ return to.path;
+ }
+
+ return undefined;
+ };
+
+ const navItems = computed(() => props.items.map(item => {
+ const hasTo = Boolean(item.to);
+ const resolvedHref = hasTo
+ ? (hasRouter ? router.resolve(item.to).href : resolveHref(item.to))
+ : item.href;
+ const isActive = hasTo && hasRouter
+ ? isRouteActive(route, item.to, props.activeMatch)
+ : Boolean(item.active);
+
+ return {
+ ...item,
+ resolvedHref,
+ isActive,
+ hasTo
+ };
+ }));
+
+ return () => h("ul", {
+ ...attrs,
+ class: cx("list", "list-nav", attrs.class)
+ }, navItems.value.map(item => h("li", {
+ class: cx("list-item", { "list-item-active": item.isActive, "list-item-disabled": item.disabled })
+ }, [
+ h(item.resolvedHref ? "a" : "button", {
+ class: "list-action",
+ href: item.resolvedHref,
+ type: item.resolvedHref ? undefined : "button",
+ disabled: item.disabled || undefined,
+ onClick: event => {
+ if (item.disabled) {
+ event.preventDefault();
+ return;
+ }
+
+ if (item.hasTo && hasRouter) {
+ event.preventDefault();
+ router.push(item.to);
+ }
+
+ item.onSelect?.(item, event);
+ emit("select", item);
+ }
+ }, [
+ h("span", { class: "list-label" }, [
+ iconNode(item.icon),
+ slots.label?.({ item }) || item.label
+ ]),
+ (item.meta || slots.meta) && h("span", { class: "list-meta" }, slots.meta?.({ item }) || item.meta)
+ ])
+ ])));
+ }
+});
diff --git a/src/vue/components/GnProgressStages.js b/src/vue/components/GnProgressStages.js
new file mode 100644
index 0000000..ea4901c
--- /dev/null
+++ b/src/vue/components/GnProgressStages.js
@@ -0,0 +1,27 @@
+/**
+ * GnProgressStages — horizontal staged progress indicator.
+ *
+ * API-compatible with gnexus-ui-kit GnProgressStages.
+ */
+
+import { defineComponent, h } from "vue";
+import { cx } from "../utils.js";
+
+export default defineComponent({
+ name: "GnProgressStages",
+ inheritAttrs: false,
+ props: {
+ items: { type: Array, default: () => [] }
+ },
+ setup(props, { attrs }) {
+ return () => h("div", {
+ ...attrs,
+ class: cx("progress-stages", attrs.class)
+ }, props.items.map(item => h("span", {
+ class: cx("progress-stage", {
+ "progress-stage-complete": item.status === "complete",
+ "progress-stage-current": item.status === "current"
+ })
+ }, item.label || item)));
+ }
+});
diff --git a/src/vue/index.js b/src/vue/index.js
index 42c19a9..c2ee38c 100644
--- a/src/vue/index.js
+++ b/src/vue/index.js
@@ -40,12 +40,12 @@
export { default as GnTableRow } from "./components/GnTableRow.js";
export { default as GnTableHeader } from "./components/GnTableHeader.js";
export { default as GnDescriptionList } from "./components/GnDescriptionList.js";
-export { default as GnDefinitionList } from "./components/GnDescriptionList.js";
+export { default as GnDefinitionList } from "./components/GnDefinitionList.js";
export { default as GnList } from "./components/GnList.js";
-export { default as GnNavList } from "./components/GnList.js";
+export { default as GnNavList } from "./components/GnNavList.js";
export { default as GnSteps } from "./components/GnSteps.js";
export { default as GnStep } from "./components/GnStep.js";
-export { default as GnProgressStages } from "./components/GnSteps.js";
+export { default as GnProgressStages } from "./components/GnProgressStages.js";
export { default as GnListItem } from "./components/GnListItem.js";
export { default as GnEmptyState } from "./components/GnEmptyState.js";
export { default as GnPageHeader } from "./components/GnPageHeader.js";
diff --git a/src/vue/plugin.js b/src/vue/plugin.js
index a94d040..e56082a 100644
--- a/src/vue/plugin.js
+++ b/src/vue/plugin.js
@@ -40,7 +40,9 @@
import GnTableRow from "./components/GnTableRow.js";
import GnTableHeader from "./components/GnTableHeader.js";
import GnDescriptionList from "./components/GnDescriptionList.js";
+import GnDefinitionList from "./components/GnDefinitionList.js";
import GnList from "./components/GnList.js";
+import GnNavList from "./components/GnNavList.js";
import GnListItem from "./components/GnListItem.js";
import GnEmptyState from "./components/GnEmptyState.js";
import GnPageHeader from "./components/GnPageHeader.js";
@@ -56,6 +58,7 @@
import GnAccordion from "./components/GnAccordion.js";
import GnSteps from "./components/GnSteps.js";
import GnStep from "./components/GnStep.js";
+import GnProgressStages from "./components/GnProgressStages.js";
import GnDivider from "./components/GnDivider.js";
import GnFileUpload from "./components/GnFileUpload.js";
import GnFileAttachment from "./components/GnFileAttachment.js";
@@ -113,9 +116,9 @@
GnTableRow,
GnTableHeader,
GnDescriptionList,
- GnDefinitionList: GnDescriptionList,
+ GnDefinitionList,
GnList,
- GnNavList: GnList,
+ GnNavList,
GnListItem,
GnEmptyState,
GnPageHeader,
@@ -131,7 +134,7 @@
GnAccordion,
GnSteps,
GnStep,
- GnProgressStages: GnSteps,
+ GnProgressStages,
GnDivider,
GnFileUpload,
GnFileAttachment,