diff --git a/community/components/navigation/tabs/tab-card/tab-card.component.html b/community/components/navigation/tabs/tab-card/tab-card.component.html new file mode 100644 index 00000000..1edd609b --- /dev/null +++ b/community/components/navigation/tabs/tab-card/tab-card.component.html @@ -0,0 +1,8 @@ + + +

{{ title() }}

+
+ +
+
+
diff --git a/community/components/navigation/tabs/tab-card/tab-card.component.scss b/community/components/navigation/tabs/tab-card/tab-card.component.scss new file mode 100644 index 00000000..fe91b0b7 --- /dev/null +++ b/community/components/navigation/tabs/tab-card/tab-card.component.scss @@ -0,0 +1,24 @@ +.tedi-tab-card { + width: 100%; + + &__content.tedi-card-content { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--dimensions-13); + color: var(--tab-item-active-text); + width: 100%; + } + + &__action-icon { + cursor: pointer; + } + + &--disabled { + pointer-events: none; + + .tedi-card-content { + color: var(--general-text-disabled); + } + } +} diff --git a/community/components/navigation/tabs/tab-card/tab-card.component.ts b/community/components/navigation/tabs/tab-card/tab-card.component.ts new file mode 100644 index 00000000..7ccd3ea4 --- /dev/null +++ b/community/components/navigation/tabs/tab-card/tab-card.component.ts @@ -0,0 +1,44 @@ +import {booleanAttribute, ChangeDetectionStrategy, Component, input, model, ViewEncapsulation} from '@angular/core'; +import {ButtonComponent, IconComponent} from "@tedi-design-system/angular/tedi"; +import {CardComponent, CardContentComponent} from "@tedi-design-system/angular/community"; + +@Component({ + selector: '[tedi-tab-card]', + imports: [ + ButtonComponent, + CardComponent, + CardContentComponent, + IconComponent + ], + templateUrl: './tab-card.component.html', + styleUrl: './tab-card.component.scss', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + "[class.tedi-tab-card]": "true", + "[class.tedi-tab-card--disabled]": "disabledInput()", + "[attr.role]": "'tab'", + "[attr.aria-selected]": "selected()", + "[attr.aria-disabled]": "disabledInput()", + "(click)": "selectTab()", + }, +}) +export class TabCardComponent { + readonly tabId = input.required(); + readonly title = input.required(); + readonly selected = model(false); + + readonly disabledInput = input(false, { + transform: booleanAttribute, + // eslint-disable-next-line @angular-eslint/no-input-rename + alias: "disabled", + }); + + selectTab() { + if (this.disabledInput()) { + return; + } + + this.selected.set(true); + } +} diff --git a/community/components/navigation/tabs/tabs-vertical/tabs-vertical.component.html b/community/components/navigation/tabs/tabs-vertical/tabs-vertical.component.html new file mode 100644 index 00000000..a85f63ee --- /dev/null +++ b/community/components/navigation/tabs/tabs-vertical/tabs-vertical.component.html @@ -0,0 +1,22 @@ + + + + +@if (activeTabContent(); as activeContent) { + + +

{{ activeTabTitle() }}

+ + + + + + +} @else { + + + +} diff --git a/community/components/navigation/tabs/tabs-vertical/tabs-vertical.component.scss b/community/components/navigation/tabs/tabs-vertical/tabs-vertical.component.scss new file mode 100644 index 00000000..1f9df0eb --- /dev/null +++ b/community/components/navigation/tabs/tabs-vertical/tabs-vertical.component.scss @@ -0,0 +1,23 @@ +.tedi-tabs-vertical { + display: flex; + flex-direction: column; + align-items: start; + gap: var(--dimensions-10); + + &__accordion, + &__card { + width: 100%; + } + + &__back-link.tedi-button { + display: flex; + align-items: center; + gap: var(--dimensions-03); + cursor: pointer; + padding: var(--dimensions-02) var(--dimensions-03); + } + + &__back-link > * { + color: var(--button-main-neutral-text-active); + } +} diff --git a/community/components/navigation/tabs/tabs-vertical/tabs-vertical.component.ts b/community/components/navigation/tabs/tabs-vertical/tabs-vertical.component.ts new file mode 100644 index 00000000..756dab32 --- /dev/null +++ b/community/components/navigation/tabs/tabs-vertical/tabs-vertical.component.ts @@ -0,0 +1,52 @@ +import {ChangeDetectionStrategy, Component, computed, contentChildren, ViewEncapsulation} from '@angular/core'; +import {CardComponent, CardContentComponent, TabContentComponent} from "@tedi-design-system/angular/community"; +import {TabCardComponent} from "../tab-card/tab-card.component"; +import { + AccordionComponent, + ButtonComponent, + IconComponent, + TediTranslationPipe, + TextComponent +} from "@tedi-design-system/angular/tedi"; +import {NgTemplateOutlet} from "@angular/common"; + +@Component({ + selector: 'tedi-tabs-vertical', + imports: [ + AccordionComponent, + ButtonComponent, + CardComponent, + CardContentComponent, + IconComponent, + NgTemplateOutlet, + TediTranslationPipe, + TextComponent + ], + templateUrl: './tabs-vertical.component.html', + styleUrl: './tabs-vertical.component.scss', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + "[class.tedi-tabs-vertical]": "true", + }, +}) +export class TabsVerticalComponent { + private readonly tabs = contentChildren(TabCardComponent); + private readonly tabContents = contentChildren(TabContentComponent); + + activeTabId = computed(() => + this.tabs().find((tab) => tab.selected())?.tabId() + ); + + activeTabTitle = computed(() => + this.tabs().find((tab) => tab.selected())?.title() + ); + + activeTabContent = computed(() => + this.tabContents().find((content) => content.tabId() === this.activeTabId())?.content() + ); + + unselectAllTabs() { + this.tabs().forEach(tab => tab.selected.set(false)); + } +} diff --git a/community/components/navigation/tabs/tabs.stories.ts b/community/components/navigation/tabs/tabs.stories.ts index 9c6663e9..cab53c58 100644 --- a/community/components/navigation/tabs/tabs.stories.ts +++ b/community/components/navigation/tabs/tabs.stories.ts @@ -1,9 +1,11 @@ -import { Meta, moduleMetadata, StoryFn, StoryObj } from "@storybook/angular"; +import {Meta, moduleMetadata, StoryFn, StoryObj} from "@storybook/angular"; -import { CommonModule } from "@angular/common"; -import { TabsComponent } from "./tabs.component"; -import { TabComponent } from "./tab/tab.component"; -import { TabContentComponent } from "./tab-content/tab-content.component"; +import {CommonModule} from "@angular/common"; +import {TabsComponent} from "./tabs.component"; +import {TabComponent} from "./tab/tab.component"; +import {TabContentComponent} from "./tab-content/tab-content.component"; +import {TabCardComponent} from "./tab-card/tab-card.component"; +import {TabsVerticalComponent} from "./tabs-vertical/tabs-vertical.component"; /** *

Tabs allow to group content into separate chunks to be displayed one at the time.

@@ -17,7 +19,7 @@ export default { decorators: [ moduleMetadata({ declarations: [], - imports: [CommonModule, TabsComponent, TabComponent, TabContentComponent], + imports: [CommonModule, TabsComponent, TabComponent, TabContentComponent, TabCardComponent, TabsVerticalComponent], }), ], argTypes: { @@ -25,7 +27,7 @@ export default { description: "Tab unique id", table: { category: "tab", - type: { summary: "string" }, + type: {summary: "string"}, }, }, selected: { @@ -33,16 +35,16 @@ export default { "Whether tab is initially selected. Should be used only for non routed tabs", table: { category: "tab", - type: { summary: "boolean" }, - defaultValue: { summary: "false" }, + type: {summary: "boolean"}, + defaultValue: {summary: "false"}, }, }, disabled: { description: "Whether tab is disabled", table: { category: "tab", - type: { summary: "boolean" }, - defaultValue: { summary: "false" }, + type: {summary: "boolean"}, + defaultValue: {summary: "false"}, }, }, contentTabId: { @@ -50,14 +52,39 @@ export default { description: "Id that matches `tabId` given to the `tedi-tab` component", table: { category: "tab-content", - type: { summary: "string" }, + type: {summary: "string"}, + }, + }, + tabCardId: { + name: "tabId", + description: "Tab unique id", + table: { + category: "tab-card", + type: {summary: "string"}, + }, + }, + tabCardTitle: { + name: "title", + description: "Tab title", + table: { + category: "tab-card", + type: {summary: "string"}, + }, + }, + tabCardDisabled: { + name: "disabled", + description: "Whether tab is disabled", + table: { + category: "tab-card", + type: {summary: "boolean"}, + defaultValue: {summary: "false"}, }, }, }, } as Meta; -const TabsTemplate: StoryFn = ({ ...args }) => ({ - props: { ...args }, +const TabsTemplate: StoryFn = ({...args}) => ({ + props: {...args}, template: ` @@ -76,19 +103,40 @@ const TabsTemplate: StoryFn = ({ ...args }) => ({ `, }); -const RoutedTabTemplate: StoryFn = ({ ...args }) => ({ - props: { ...args }, +const RoutedTabTemplate: StoryFn = ({...args}) => ({ + props: {...args}, template: ` Tab 1 Tab 2 Tab 3 - + router-outlet goes here `, }); +const VerticalTabTemplate: StoryFn = ({...args}) => ({ + props: {...args}, + template: ` + +
+
+
+ + + Tab 1 content + + + Tab 2 content + + + Tab 3 content + +
+ `, +}); + type TableStylesStory = StoryObj; export const Default: TableStylesStory = { @@ -98,3 +146,7 @@ export const Default: TableStylesStory = { export const RoutedTabs: TableStylesStory = { render: RoutedTabTemplate, }; + +export const VerticalTabs: TableStylesStory = { + render: VerticalTabTemplate, +}; diff --git a/tedi/services/translation/translations.ts b/tedi/services/translation/translations.ts index 7665e535..d6180db2 100644 --- a/tedi/services/translation/translations.ts +++ b/tedi/services/translation/translations.ts @@ -65,6 +65,12 @@ export const translationsMap = { en: "Breadcrumbs", ru: "Навигационная цепочка", }, + "back": { + components: ["Tabs"], + et: "Tagasi", + en: "Back", + ru: "Назад", + }, more: { components: ["Tabs"], et: "Veel",