dxd-style-code 0.1.11 → 0.1.12
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/dxd-style-code.js +7470 -6336
- package/dist/dxd-style-code.umd.cjs +3 -3
- package/dist/style.css +1 -1
- package/package.json +2 -2
- package/src/components/atoms/DX/DX.stories.js +265 -0
- package/src/components/atoms/DX/DX.vue +80 -0
- package/src/components/atoms/DX/index.js +2 -0
- package/src/components/atoms/DXAvatar/DXAvatar.stories.js +1 -2
- package/src/components/atoms/DXBackdrop/DXBackdrop.stories.js +77 -1
- package/src/components/atoms/DXBadge/DXBadge.stories.js +83 -1
- package/src/components/atoms/DXBlockquote/DXBlockquote.stories.js +67 -1
- package/src/components/atoms/DXBox/DXBox.stories.js +1 -1
- package/src/components/atoms/DXBox/DXBox.vue +69 -2
- package/src/components/atoms/DXButton/DXButton.stories.js +94 -1
- package/src/components/atoms/DXButton/DXButton.vue +145 -11
- package/src/components/atoms/DXCard/DXCard.stories.js +72 -14
- package/src/components/atoms/DXCard/DXCard.vue +3 -4
- package/src/components/atoms/DXCheckbox/DXCheckbox.stories.js +85 -1
- package/src/components/atoms/DXCode/DXCode.stories.js +67 -1
- package/src/components/atoms/DXContainer/DXContainer.stories.js +1 -1
- package/src/components/atoms/DXContainer/DXContainer.vue +28 -1
- package/src/components/atoms/DXDivider/DXDivider.stories.js +84 -1
- package/src/components/atoms/DXFlex/DXFlex.stories.js +1 -1
- package/src/components/atoms/DXFlex/DXFlex.vue +57 -3
- package/src/components/atoms/DXFormLabel/DXFormLabel.stories.js +70 -1
- package/src/components/atoms/DXGrid/DXGrid.stories.js +1 -1
- package/src/components/atoms/DXHeading/DXHeading.stories.js +90 -1
- package/src/components/atoms/DXIcon/DXIcon.stories.js +74 -0
- package/src/components/atoms/DXIconWrapper/DXIconWrapper.stories.js +69 -0
- package/src/components/atoms/DXImage/DXImage.stories.js +483 -0
- package/src/components/atoms/DXImage/DXImage.vue +294 -0
- package/src/components/atoms/DXImage/index.js +2 -0
- package/src/components/atoms/DXInputAddon/DXInputAddon.stories.js +86 -1
- package/src/components/atoms/DXLabel/DXLabel.stories.js +62 -1
- package/src/components/atoms/DXLink/DXLink.stories.js +75 -10
- package/src/components/atoms/DXLink/DXLink.vue +59 -3
- package/src/components/atoms/DXList/DXList.stories.js +76 -1
- package/src/components/atoms/DXLoader/DXLoader.stories.js +75 -1
- package/src/components/atoms/DXNav/DXNav.stories.js +236 -0
- package/src/components/atoms/DXNav/DXNav.vue +114 -0
- package/src/components/atoms/DXNav/index.js +2 -0
- package/src/components/atoms/DXProgress/DXProgress.stories.js +76 -1
- package/src/components/atoms/DXRadio/DXRadio.stories.js +85 -1
- package/src/components/atoms/DXSkeleton/DXSkeleton.stories.js +59 -1
- package/src/components/atoms/DXSlider/DXSlider.stories.js +89 -0
- package/src/components/atoms/DXSpacer/DXSpacer.stories.js +1 -1
- package/src/components/atoms/DXStack/DXStack.stories.js +1 -1
- package/src/components/atoms/DXStack/DXStack.vue +5 -2
- package/src/components/atoms/DXTags/DXTags.stories.js +77 -0
- package/src/components/atoms/DXText/DXText.stories.js +83 -1
- package/src/components/atoms/DXToast/DXToast.stories.js +64 -1
- package/src/components/atoms/DXToggle/DXToggle.stories.js +84 -1
- package/src/components/atoms/DXToggleButton/DXToggleButton.stories.js +78 -1
- package/src/components/atoms/DXTooltip/DXTooltip.stories.js +98 -1
- package/src/components/atoms/index.js +3 -0
- package/src/components/molecules/DXActionButtons/DXActionButtons.stories.js +280 -77
- package/src/components/molecules/DXActionButtons/DXActionButtons.vue +31 -31
- package/src/components/molecules/DXAlert/DXAlert.stories.js +199 -1
- package/src/components/molecules/DXAlert/DXAlert.vue +35 -13
- package/src/components/molecules/DXBreadcrumb/DXBreadcrumb.stories.js +125 -1
- package/src/components/molecules/DXBreadcrumb/DXBreadcrumb.vue +22 -18
- package/src/components/molecules/DXButtonGroup/DXButtonGroup.stories.js +193 -6
- package/src/components/molecules/DXButtonGroup/DXButtonGroup.vue +39 -3
- package/src/components/molecules/DXCloseButton/DXCloseButton.stories.js +64 -1
- package/src/components/molecules/DXComboBox/DXComboBox.stories.js +66 -0
- package/src/components/molecules/DXCopyField/DXCopyField.stories.js +128 -1
- package/src/components/molecules/DXCopyField/DXCopyField.vue +60 -7
- package/src/components/molecules/DXDataFilter/DXDataFilter.vue +8 -6
- package/src/components/molecules/DXDatePicker/DXDatePicker.stories.js +58 -0
- package/src/components/molecules/DXFileUpload/DXFileUpload.stories.js +66 -0
- package/src/components/molecules/DXFilterGroup/DXFilterGroup.stories.js +61 -0
- package/src/components/molecules/DXFormControl/DXFormControl.stories.js +76 -0
- package/src/components/molecules/DXFormControl/DXFormControl.vue +9 -8
- package/src/components/molecules/DXInput/DXInput.stories.js +100 -1
- package/src/components/molecules/DXInputGroup/DXInputGroup.stories.js +89 -1
- package/src/components/molecules/DXInputMask/DXInputMask.stories.js +67 -0
- package/src/components/molecules/DXMenu/DXMenu.stories.js +111 -4
- package/src/components/molecules/DXMenu/DXMenu.vue +18 -5
- package/src/components/molecules/DXMenu/README.md +1 -1
- package/src/components/molecules/DXPagination/DXPagination.stories.js +105 -2
- package/src/components/molecules/DXPagination/DXPagination.vue +18 -33
- package/src/components/molecules/DXPasswordInput/DXPasswordInput.stories.js +67 -1
- package/src/components/molecules/DXRadioCard/DXRadioCard.stories.js +64 -0
- package/src/components/molecules/DXRadioGroup/DXRadioGroup.stories.js +84 -0
- package/src/components/molecules/DXRating/DXRating.stories.js +3 -2
- package/src/components/molecules/DXSearchBar/DXSearchBar.stories.js +1 -1
- package/src/components/molecules/DXSearchBar/DXSearchBar.vue +16 -12
- package/src/components/molecules/DXSearchSelect/DXSearchSelect.stories.js +71 -0
- package/src/components/molecules/DXSegmentedControl/DXSegmentedControl.stories.js +74 -0
- package/src/components/molecules/DXSelect/DXSelect.stories.js +92 -1
- package/src/components/molecules/DXStatCard/DXStatCard.stories.js +1 -1
- package/src/components/molecules/DXStatCard/DXStatCard.vue +30 -26
- package/src/components/molecules/DXTableFiltersPanel/index.js +2 -0
- package/src/components/molecules/DXTablePagination/DXTablePagination.stories.js +67 -0
- package/src/components/molecules/DXTableToolbar/DXTableToolbar.stories.js +71 -0
- package/src/components/molecules/DXTextarea/DXTextarea.stories.js +87 -1
- package/src/components/molecules/DXTimePicker/DXTimePicker.stories.js +1 -1
- package/src/components/molecules/DXValidationIcon/DXValidationIcon.stories.js +59 -1
- package/src/components/molecules/index.js +0 -1
- package/src/components/organisms/DXAccordion/DXAccordion.stories.js +75 -0
- package/src/components/organisms/DXAppLayout/DXAppLayout.stories.js +27 -25
- package/src/components/organisms/DXAuthenticationForm/DXAuthenticationForm.stories.js +0 -2
- package/src/components/organisms/DXAuthenticationForm/DXAuthenticationForm.vue +5 -8
- package/src/components/{molecules → organisms}/DXBaseTable/DXBaseTable.stories.js +78 -1
- package/src/components/{molecules → organisms}/DXBaseTable/DXBaseTable.vue +2 -2
- package/src/components/organisms/DXChartContainer/DXChartContainer.stories.js +1 -1
- package/src/components/organisms/DXChartContainer/DXChartContainer.vue +10 -6
- package/src/components/organisms/DXChatInterface/DXChatInterface.stories.js +1 -1
- package/src/components/organisms/DXChatInterface/DXChatInterface.vue +6 -4
- package/src/components/organisms/DXCommentSection/DXCommentSection.stories.js +1 -1
- package/src/components/organisms/DXCommentSection/DXCommentSection.vue +7 -6
- package/src/components/organisms/DXDashboardGrid/DXDashboardGrid.stories.js +1 -1
- package/src/components/organisms/DXDashboardGrid/DXDashboardGrid.vue +4 -2
- package/src/components/organisms/DXDashboardWidget/DXDashboardWidget.stories.js +1 -1
- package/src/components/organisms/DXDashboardWidget/DXDashboardWidget.vue +3 -2
- package/src/components/organisms/DXDataTable/DXDataTable.stories.js +1 -1
- package/src/components/organisms/DXDropdown/DXDropdown.stories.js +84 -1
- package/src/components/organisms/DXEmptyState/DXEmptyState.stories.js +64 -0
- package/src/components/organisms/DXEmptyState/DXEmptyState.vue +4 -2
- package/src/components/organisms/DXHeaderBar/DXHeaderBar.stories.js +409 -3
- package/src/components/organisms/DXHeaderBar/DXHeaderBar.vue +261 -52
- package/src/components/organisms/DXMediaGallery/DXMediaGallery.stories.js +1 -1
- package/src/components/organisms/DXMediaGallery/DXMediaGallery.vue +6 -4
- package/src/components/organisms/DXModal/DXModal.stories.js +93 -1
- package/src/components/organisms/DXModal/DXModal.vue +3 -2
- package/src/components/organisms/DXNotificationCenter/DXNotificationCenter.stories.js +1 -1
- package/src/components/organisms/DXNotificationCenter/DXNotificationCenter.vue +2 -1
- package/src/components/organisms/DXReportGenerator/DXReportGenerator.vue +4 -3
- package/src/components/organisms/DXSettingsPanel/DXSettingsPanel.vue +11 -8
- package/src/components/organisms/DXSidebar/DXSidebar.stories.js +1 -1
- package/src/components/organisms/DXSidebarMenu/DXSidebarMenu.stories.js +104 -1
- package/src/components/organisms/DXSidebarMenu/DXSidebarMenu.vue +14 -4
- package/src/components/organisms/DXSidebarMenu/README.md +3 -3
- package/src/components/organisms/DXTable/DXTable.stories.js +138 -11
- package/src/components/organisms/DXTable/DXTable.vue +1 -1
- package/src/components/organisms/DXTabs/DXTabs.stories.js +91 -1
- package/src/components/organisms/DXUserProfileCard/DXUserProfileCard.stories.js +1 -1
- package/src/components/organisms/DXUserProfileCard/DXUserProfileCard.vue +20 -18
- package/src/components/organisms/index.js +1 -0
- package/src/components/utilities/DXAnimatePresence/DXAnimatePresence.stories.js +1 -1
- package/src/components/utilities/DXBreakpointProvider/DXBreakpointProvider.stories.js +1 -1
- package/src/components/utilities/DXObserver/DXObserver.stories.js +1 -1
- package/src/components/utilities/DXPortal/DXPortal.stories.js +1 -1
- package/src/components/utilities/DXStaggeredAnimation/DXStaggeredAnimation.stories.js +2 -2
- package/src/components/utilities/DXThemeProvider/DXThemeProvider.stories.js +1 -1
- package/src/components/utilities/DXTransitionGroup/DXTransitionGroup.stories.js +1 -1
- package/src/composables/useSize.js +8 -1
- /package/src/components/{molecules → organisms}/DXBaseTable/index.js +0 -0
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
:data-variant="variant"
|
|
13
13
|
:data-size="size"
|
|
14
14
|
:data-disabled="disabled"
|
|
15
|
+
:data-inactive="inactive"
|
|
15
16
|
:data-external="isExternal"
|
|
16
17
|
@click="handleClick"
|
|
17
18
|
>
|
|
18
19
|
<slot />
|
|
19
20
|
<DXIcon
|
|
20
|
-
v-if="showExternalIcon && isExternal && !disabled"
|
|
21
|
+
v-if="showExternalIcon && isExternal && !disabled && !inactive"
|
|
21
22
|
:icon="ArrowTopRightOnSquareIcon"
|
|
22
23
|
:size="iconSize"
|
|
23
24
|
class="ml-0.5"
|
|
@@ -49,6 +50,8 @@ const props = defineProps({
|
|
|
49
50
|
size: { type: String, default: "md" },
|
|
50
51
|
/** Отключенное состояние */
|
|
51
52
|
disabled: { type: Boolean, default: false },
|
|
53
|
+
/** Неактивное состояние (визуально приглушенная, но кликабельная) */
|
|
54
|
+
inactive: { type: Boolean, default: false },
|
|
52
55
|
/** Кастомный aria-label */
|
|
53
56
|
ariaLabel: { type: String, default: null },
|
|
54
57
|
/** Показывать иконку для внешних ссылок */
|
|
@@ -161,16 +164,69 @@ const textSizeClass = computed(() => {
|
|
|
161
164
|
return useSize(props.size, "text");
|
|
162
165
|
});
|
|
163
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Классы padding для вариантов (кроме link)
|
|
169
|
+
*/
|
|
170
|
+
const variantPaddingClass = computed(() => {
|
|
171
|
+
if (props.variant === "link") return "";
|
|
172
|
+
|
|
173
|
+
// Небольшие отступы в зависимости от размера
|
|
174
|
+
const paddingMap = {
|
|
175
|
+
xs: "px-1 py-0.5",
|
|
176
|
+
sm: "px-1.5 py-0.5",
|
|
177
|
+
md: "px-2 py-0.5",
|
|
178
|
+
lg: "px-2.5 py-1",
|
|
179
|
+
xl: "px-3 py-1",
|
|
180
|
+
};
|
|
181
|
+
return paddingMap[props.size] || paddingMap.md;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Классы для неактивного состояния
|
|
186
|
+
*/
|
|
187
|
+
const inactiveVariantClass = computed(() => {
|
|
188
|
+
if (props.variant === "link") {
|
|
189
|
+
return "text-slate-600 no-underline cursor-default";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Для остальных вариантов - базовые цвета без hover, с меньшей opacity для яркости
|
|
193
|
+
const baseColors = {
|
|
194
|
+
primary: "bg-slate-800 text-white",
|
|
195
|
+
secondary: "bg-slate-200 text-slate-700",
|
|
196
|
+
ghost: "text-slate-600 bg-transparent",
|
|
197
|
+
outline: "border border-slate-200 text-slate-600 bg-white",
|
|
198
|
+
success: "bg-emerald-600 text-white",
|
|
199
|
+
warning: "bg-amber-600 text-white",
|
|
200
|
+
danger: "bg-rose-600 text-white",
|
|
201
|
+
info: "bg-blue-600 text-white",
|
|
202
|
+
soft: "bg-slate-50 text-slate-600",
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
return useClassComposition(
|
|
206
|
+
baseColors[props.variant] || baseColors.primary,
|
|
207
|
+
variantPaddingClass.value,
|
|
208
|
+
"rounded-lg opacity-75"
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
|
|
164
212
|
/**
|
|
165
213
|
* Классы варианта стилизации
|
|
166
214
|
*/
|
|
167
215
|
const variantClass = computed(() => {
|
|
216
|
+
if (props.inactive) {
|
|
217
|
+
return inactiveVariantClass.value;
|
|
218
|
+
}
|
|
219
|
+
|
|
168
220
|
if (props.variant === "link") {
|
|
169
221
|
// Кастомные классы для варианта link
|
|
170
222
|
return "text-blue-600 hover:text-blue-700 underline-offset-2 hover:underline";
|
|
171
223
|
}
|
|
172
|
-
// Для остальных вариантов используем useVariantButton
|
|
173
|
-
return
|
|
224
|
+
// Для остальных вариантов используем useVariantButton + padding + border-radius
|
|
225
|
+
return useClassComposition(
|
|
226
|
+
useVariantButton(props.variant),
|
|
227
|
+
variantPaddingClass.value,
|
|
228
|
+
"rounded-lg"
|
|
229
|
+
);
|
|
174
230
|
});
|
|
175
231
|
|
|
176
232
|
/**
|
|
@@ -3,7 +3,82 @@ import DXList from './DXList.vue';
|
|
|
3
3
|
export default {
|
|
4
4
|
title: 'Atoms/DXList',
|
|
5
5
|
component: DXList,
|
|
6
|
-
tags: ['autodocs'],
|
|
6
|
+
tags: ['autodocs', 'category:typography'],
|
|
7
|
+
parameters: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: {
|
|
10
|
+
component: `
|
|
11
|
+
# DXList
|
|
12
|
+
|
|
13
|
+
Компонент списка с поддержкой упорядоченных и неупорядоченных списков, различных стилей маркеров и размеров.
|
|
14
|
+
|
|
15
|
+
## Назначение
|
|
16
|
+
|
|
17
|
+
DXList предоставляет стандартизированный способ создания списков с настраиваемыми параметрами
|
|
18
|
+
типографики. Компонент автоматически рендерится как \`<ul>\` или \`<ol>\` для семантической правильности.
|
|
19
|
+
|
|
20
|
+
## Архитектура
|
|
21
|
+
|
|
22
|
+
### Использует
|
|
23
|
+
- Динамический рендеринг HTML тегов - для семантической правильности
|
|
24
|
+
|
|
25
|
+
### Используется в
|
|
26
|
+
- Списки элементов
|
|
27
|
+
- Навигационные меню
|
|
28
|
+
- Оглавления
|
|
29
|
+
- Любые места, требующие структурированных списков
|
|
30
|
+
|
|
31
|
+
## Внутренняя логика
|
|
32
|
+
|
|
33
|
+
### Типы списков
|
|
34
|
+
- **unordered** (по умолчанию): Неупорядоченный список (\`<ul>\`)
|
|
35
|
+
- **ordered**: Упорядоченный список (\`<ol>\`) - при \`ordered={true}\`
|
|
36
|
+
|
|
37
|
+
### Стили маркеров
|
|
38
|
+
Поддерживает различные стили маркеров:
|
|
39
|
+
- **disc** - заполненные круги (по умолчанию для ul)
|
|
40
|
+
- **circle** - пустые круги
|
|
41
|
+
- **square** - квадраты
|
|
42
|
+
- **decimal** - числа (по умолчанию для ol)
|
|
43
|
+
- **none** - без маркеров
|
|
44
|
+
|
|
45
|
+
### Размеры
|
|
46
|
+
Поддерживает 3 размера текста:
|
|
47
|
+
- **sm** - маленький текст
|
|
48
|
+
- **md** - средний текст (по умолчанию)
|
|
49
|
+
- **lg** - большой текст
|
|
50
|
+
|
|
51
|
+
### Отступы между элементами
|
|
52
|
+
Поддерживает 4 варианта отступов:
|
|
53
|
+
- **none** - без отступов
|
|
54
|
+
- **sm** - маленькие отступы (по умолчанию)
|
|
55
|
+
- **md** - средние отступы
|
|
56
|
+
- **lg** - большие отступы
|
|
57
|
+
|
|
58
|
+
## Особенности
|
|
59
|
+
|
|
60
|
+
### Семантика
|
|
61
|
+
Компонент использует семантически правильные HTML теги:
|
|
62
|
+
- \`<ul>\` для неупорядоченных списков
|
|
63
|
+
- \`<ol>\` для упорядоченных списков
|
|
64
|
+
|
|
65
|
+
Это важно для:
|
|
66
|
+
- SEO (поисковые системы понимают структуру)
|
|
67
|
+
- Доступности (скринридеры используют списки для навигации)
|
|
68
|
+
- Семантической правильности HTML
|
|
69
|
+
|
|
70
|
+
### Элементы списка
|
|
71
|
+
Элементы списка должны быть обернуты в \`<li>\` теги через slot:
|
|
72
|
+
\`\`\`vue
|
|
73
|
+
<DXList>
|
|
74
|
+
<li>Первый элемент</li>
|
|
75
|
+
<li>Второй элемент</li>
|
|
76
|
+
</DXList>
|
|
77
|
+
\`\`\`
|
|
78
|
+
`,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
7
82
|
};
|
|
8
83
|
|
|
9
84
|
export const Unordered = {
|
|
@@ -3,7 +3,81 @@ import DXLoader from './DXLoader.vue';
|
|
|
3
3
|
export default {
|
|
4
4
|
title: 'Atoms/DXLoader',
|
|
5
5
|
component: DXLoader,
|
|
6
|
-
tags: ['autodocs'],
|
|
6
|
+
tags: ['autodocs', 'category:feedback'],
|
|
7
|
+
parameters: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: {
|
|
10
|
+
component: `
|
|
11
|
+
# DXLoader
|
|
12
|
+
|
|
13
|
+
Компонент индикатора загрузки с поддержкой различных размеров, цветов и текста.
|
|
14
|
+
|
|
15
|
+
## Назначение
|
|
16
|
+
|
|
17
|
+
DXLoader предоставляет стандартизированный способ отображения состояния загрузки.
|
|
18
|
+
Компонент поддерживает различные размеры, цвета и возможность отображения текста рядом с индикатором.
|
|
19
|
+
|
|
20
|
+
## Архитектура
|
|
21
|
+
|
|
22
|
+
### Использует
|
|
23
|
+
- \`useSize\` composable - для унификации размеров (xs, sm, md, lg, xl)
|
|
24
|
+
- \`useVariantLoader\` composable - для цветовых вариантов
|
|
25
|
+
|
|
26
|
+
### Используется в
|
|
27
|
+
- Загрузка данных
|
|
28
|
+
- Обработка запросов
|
|
29
|
+
- Состояния ожидания
|
|
30
|
+
- Любые места, требующие индикации загрузки
|
|
31
|
+
|
|
32
|
+
## Внутренняя логика
|
|
33
|
+
|
|
34
|
+
### Размеры
|
|
35
|
+
Поддерживает 5 размеров:
|
|
36
|
+
- **xs** - очень маленький (8×8px)
|
|
37
|
+
- **sm** - маленький
|
|
38
|
+
- **md** - средний (по умолчанию)
|
|
39
|
+
- **lg** - большой
|
|
40
|
+
- **xl** - очень большой
|
|
41
|
+
|
|
42
|
+
### Цвета
|
|
43
|
+
Поддерживает 7 цветовых вариантов:
|
|
44
|
+
- **default** (по умолчанию) - стандартный цвет
|
|
45
|
+
- **primary** - основной цвет
|
|
46
|
+
- **success** - зеленый цвет
|
|
47
|
+
- **warning** - желтый цвет
|
|
48
|
+
- **danger** - красный цвет
|
|
49
|
+
- **info** - синий цвет
|
|
50
|
+
- **slate** - серый цвет
|
|
51
|
+
|
|
52
|
+
### Текст
|
|
53
|
+
Поддерживает отображение текста рядом с индикатором:
|
|
54
|
+
- Через \`text\` prop
|
|
55
|
+
- Через default slot
|
|
56
|
+
- Управляется через \`showText\` prop
|
|
57
|
+
|
|
58
|
+
## Особенности
|
|
59
|
+
|
|
60
|
+
### Анимация
|
|
61
|
+
Использует CSS анимацию \`animate-spin\` для вращения индикатора.
|
|
62
|
+
|
|
63
|
+
### Отображение текста
|
|
64
|
+
Логика отображения текста:
|
|
65
|
+
- Если \`showText\` явно указан → используется его значение
|
|
66
|
+
- Если есть default slot → текст показывается
|
|
67
|
+
- Если есть \`text\` prop → текст показывается
|
|
68
|
+
- Иначе → текст не показывается
|
|
69
|
+
|
|
70
|
+
### Визуальное оформление
|
|
71
|
+
- Круглый индикатор с границей
|
|
72
|
+
- Вращающаяся анимация
|
|
73
|
+
- Цветная верхняя граница для визуального эффекта
|
|
74
|
+
|
|
75
|
+
### Слоты
|
|
76
|
+
- **default** - кастомный текст (если нужно)
|
|
77
|
+
`,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
7
81
|
argTypes: {
|
|
8
82
|
size: {
|
|
9
83
|
control: 'select',
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import DXNav from './DXNav.vue';
|
|
2
|
+
import DXLink from '../DXLink/DXLink.vue';
|
|
3
|
+
import DXButton from '../DXButton/DXButton.vue';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Atoms/DXNav',
|
|
7
|
+
component: DXNav,
|
|
8
|
+
tags: ['autodocs', 'category:navigation'],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component: `
|
|
13
|
+
# DXNav
|
|
14
|
+
|
|
15
|
+
Универсальный компонент для обертки навигационных элементов с автоматическим управлением accessibility.
|
|
16
|
+
|
|
17
|
+
## Назначение
|
|
18
|
+
|
|
19
|
+
DXNav - атомарный компонент для создания семантически правильных навигационных элементов.
|
|
20
|
+
Автоматически добавляет правильные \`aria-label\` атрибуты на основе типа навигации,
|
|
21
|
+
что улучшает accessibility и соответствие стандартам.
|
|
22
|
+
|
|
23
|
+
## Архитектура
|
|
24
|
+
|
|
25
|
+
### Используется в
|
|
26
|
+
- \`DXBreadcrumb\` - для хлебных крошек
|
|
27
|
+
- \`DXPagination\` - для пагинации
|
|
28
|
+
- \`DXMenu\` - для навигационных меню
|
|
29
|
+
- \`DXSidebarMenu\` - для боковых меню
|
|
30
|
+
- \`DXHeaderBar\` - для основной навигации в шапке
|
|
31
|
+
- Любые другие компоненты, требующие навигации
|
|
32
|
+
|
|
33
|
+
## Внутренняя логика
|
|
34
|
+
|
|
35
|
+
### Автоматические aria-label
|
|
36
|
+
Компонент автоматически добавляет \`aria-label\` на основе типа:
|
|
37
|
+
- \`breadcrumb\` → \`aria-label="Breadcrumb"\`
|
|
38
|
+
- \`pagination\` → \`aria-label="Pagination"\`
|
|
39
|
+
- \`menu\` → \`aria-label="Navigation menu"\`
|
|
40
|
+
- \`main\` → \`aria-label="Main navigation"\`
|
|
41
|
+
- \`custom\` → без автоматического label (требует \`ariaLabel\` prop)
|
|
42
|
+
|
|
43
|
+
### Приоритет aria-label
|
|
44
|
+
1. Если передан \`ariaLabel\` prop → используется он (переопределяет тип)
|
|
45
|
+
2. Если \`type !== 'custom'\` → используется автоматический label
|
|
46
|
+
3. Если \`type === 'custom'\` и нет \`ariaLabel\` → aria-label не добавляется
|
|
47
|
+
|
|
48
|
+
### Передача атрибутов
|
|
49
|
+
Все атрибуты (кроме \`class\` и \`aria-label\`) автоматически передаются через \`$attrs\`:
|
|
50
|
+
- HTML атрибуты (id, data-*, и т.д.)
|
|
51
|
+
- События (@click, @input, и т.д.)
|
|
52
|
+
|
|
53
|
+
## Особенности
|
|
54
|
+
|
|
55
|
+
### Типы навигации
|
|
56
|
+
- **breadcrumb**: Для хлебных крошек (показывает путь к текущей странице)
|
|
57
|
+
- **pagination**: Для пагинации (навигация по страницам)
|
|
58
|
+
- **menu**: Для навигационных меню (боковые панели, выпадающие меню)
|
|
59
|
+
- **main**: Для основной навигации (главное меню сайта)
|
|
60
|
+
- **custom**: Для кастомных случаев (требует явного \`ariaLabel\`)
|
|
61
|
+
|
|
62
|
+
### Accessibility
|
|
63
|
+
Компонент автоматически обеспечивает правильную семантику и accessibility:
|
|
64
|
+
- Семантический \`<nav>\` тег
|
|
65
|
+
- Правильные \`aria-label\` для screen readers
|
|
66
|
+
- Поддержка всех стандартных HTML атрибутов
|
|
67
|
+
- Совместимость с клавиатурной навигацией
|
|
68
|
+
|
|
69
|
+
## Ограничения
|
|
70
|
+
|
|
71
|
+
- Для \`custom\` типа рекомендуется всегда указывать \`ariaLabel\` для accessibility
|
|
72
|
+
- Компонент не добавляет стили - используйте \`class\` prop для стилизации
|
|
73
|
+
- Все атрибуты передаются как есть (кроме \`class\` и \`aria-label\`)
|
|
74
|
+
`,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
argTypes: {
|
|
79
|
+
type: {
|
|
80
|
+
control: { type: 'select' },
|
|
81
|
+
options: ['breadcrumb', 'pagination', 'menu', 'main', 'custom'],
|
|
82
|
+
description: 'Тип навигации',
|
|
83
|
+
table: {
|
|
84
|
+
type: { summary: 'string' },
|
|
85
|
+
defaultValue: { summary: 'main' },
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
ariaLabel: {
|
|
89
|
+
control: { type: 'text' },
|
|
90
|
+
description: 'Кастомный aria-label (переопределяет автоматический)',
|
|
91
|
+
table: {
|
|
92
|
+
type: { summary: 'string' },
|
|
93
|
+
defaultValue: { summary: 'null' },
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
class: {
|
|
97
|
+
control: { type: 'text' },
|
|
98
|
+
description: 'Дополнительные CSS классы',
|
|
99
|
+
table: {
|
|
100
|
+
type: { summary: 'string | array | object' },
|
|
101
|
+
defaultValue: { summary: 'null' },
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const Default = {
|
|
108
|
+
args: {
|
|
109
|
+
type: 'main',
|
|
110
|
+
},
|
|
111
|
+
render: (args) => ({
|
|
112
|
+
components: { DXNav, DXLink },
|
|
113
|
+
setup() {
|
|
114
|
+
return { args };
|
|
115
|
+
},
|
|
116
|
+
template: `
|
|
117
|
+
<DXNav v-bind="args" class="flex gap-4">
|
|
118
|
+
<DXLink href="/">Home</DXLink>
|
|
119
|
+
<DXLink href="/about">About</DXLink>
|
|
120
|
+
<DXLink href="/contact">Contact</DXLink>
|
|
121
|
+
</DXNav>
|
|
122
|
+
`,
|
|
123
|
+
}),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const Breadcrumb = {
|
|
127
|
+
args: {
|
|
128
|
+
type: 'breadcrumb',
|
|
129
|
+
},
|
|
130
|
+
render: (args) => ({
|
|
131
|
+
components: { DXNav, DXLink },
|
|
132
|
+
setup() {
|
|
133
|
+
return { args };
|
|
134
|
+
},
|
|
135
|
+
template: `
|
|
136
|
+
<DXNav v-bind="args" class="flex items-center gap-2">
|
|
137
|
+
<DXLink href="/">Home</DXLink>
|
|
138
|
+
<span class="text-slate-400">/</span>
|
|
139
|
+
<DXLink href="/products">Products</DXLink>
|
|
140
|
+
<span class="text-slate-400">/</span>
|
|
141
|
+
<DXLink href="/products/current" inactive>Current Page</DXLink>
|
|
142
|
+
</DXNav>
|
|
143
|
+
`,
|
|
144
|
+
}),
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const Pagination = {
|
|
148
|
+
args: {
|
|
149
|
+
type: 'pagination',
|
|
150
|
+
},
|
|
151
|
+
render: (args) => ({
|
|
152
|
+
components: { DXNav, DXButton },
|
|
153
|
+
setup() {
|
|
154
|
+
return { args };
|
|
155
|
+
},
|
|
156
|
+
template: `
|
|
157
|
+
<DXNav v-bind="args" class="flex items-center gap-1">
|
|
158
|
+
<DXButton variant="ghost" size="sm">Previous</DXButton>
|
|
159
|
+
<DXButton variant="primary" size="sm">1</DXButton>
|
|
160
|
+
<DXButton variant="ghost" size="sm">2</DXButton>
|
|
161
|
+
<DXButton variant="ghost" size="sm">3</DXButton>
|
|
162
|
+
<DXButton variant="ghost" size="sm">Next</DXButton>
|
|
163
|
+
</DXNav>
|
|
164
|
+
`,
|
|
165
|
+
}),
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const Menu = {
|
|
169
|
+
args: {
|
|
170
|
+
type: 'menu',
|
|
171
|
+
},
|
|
172
|
+
render: (args) => ({
|
|
173
|
+
components: { DXNav, DXLink },
|
|
174
|
+
setup() {
|
|
175
|
+
return { args };
|
|
176
|
+
},
|
|
177
|
+
template: `
|
|
178
|
+
<DXNav v-bind="args" class="flex flex-col gap-1">
|
|
179
|
+
<DXLink href="/dashboard" variant="ghost">Dashboard</DXLink>
|
|
180
|
+
<DXLink href="/settings" variant="ghost">Settings</DXLink>
|
|
181
|
+
<DXLink href="/profile" variant="ghost">Profile</DXLink>
|
|
182
|
+
</DXNav>
|
|
183
|
+
`,
|
|
184
|
+
}),
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export const CustomAriaLabel = {
|
|
188
|
+
args: {
|
|
189
|
+
type: 'custom',
|
|
190
|
+
ariaLabel: 'Site navigation',
|
|
191
|
+
},
|
|
192
|
+
render: (args) => ({
|
|
193
|
+
components: { DXNav, DXLink },
|
|
194
|
+
setup() {
|
|
195
|
+
return { args };
|
|
196
|
+
},
|
|
197
|
+
template: `
|
|
198
|
+
<DXNav v-bind="args" class="flex gap-4">
|
|
199
|
+
<DXLink href="/">Home</DXLink>
|
|
200
|
+
<DXLink href="/about">About</DXLink>
|
|
201
|
+
<DXLink href="/contact">Contact</DXLink>
|
|
202
|
+
</DXNav>
|
|
203
|
+
<p class="text-sm text-slate-600 mt-4">
|
|
204
|
+
Используется кастомный aria-label: "Site navigation"
|
|
205
|
+
</p>
|
|
206
|
+
`,
|
|
207
|
+
}),
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const WithAttributes = {
|
|
211
|
+
args: {
|
|
212
|
+
type: 'main',
|
|
213
|
+
},
|
|
214
|
+
render: (args) => ({
|
|
215
|
+
components: { DXNav, DXLink },
|
|
216
|
+
setup() {
|
|
217
|
+
return { args };
|
|
218
|
+
},
|
|
219
|
+
template: `
|
|
220
|
+
<DXNav
|
|
221
|
+
v-bind="args"
|
|
222
|
+
id="main-nav"
|
|
223
|
+
class="flex gap-4 p-4 bg-slate-50 rounded-lg"
|
|
224
|
+
data-testid="navigation"
|
|
225
|
+
>
|
|
226
|
+
<DXLink href="/">Home</DXLink>
|
|
227
|
+
<DXLink href="/about">About</DXLink>
|
|
228
|
+
<DXLink href="/contact">Contact</DXLink>
|
|
229
|
+
</DXNav>
|
|
230
|
+
<p class="text-sm text-slate-600 mt-4">
|
|
231
|
+
Все атрибуты (id, data-testid, class) передаются корректно
|
|
232
|
+
</p>
|
|
233
|
+
`,
|
|
234
|
+
}),
|
|
235
|
+
};
|
|
236
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<nav
|
|
3
|
+
:aria-label="computedAriaLabel"
|
|
4
|
+
:class="computedClass"
|
|
5
|
+
data-component="DXNav"
|
|
6
|
+
:data-type="type"
|
|
7
|
+
v-bind="navAttrs"
|
|
8
|
+
>
|
|
9
|
+
<slot />
|
|
10
|
+
</nav>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup>
|
|
14
|
+
import { computed, useAttrs } from "vue";
|
|
15
|
+
|
|
16
|
+
const props = defineProps({
|
|
17
|
+
/** Тип навигации: breadcrumb | pagination | menu | main | custom */
|
|
18
|
+
type: {
|
|
19
|
+
type: String,
|
|
20
|
+
default: "main",
|
|
21
|
+
validator: (value) => ["breadcrumb", "pagination", "menu", "main", "custom"].includes(value),
|
|
22
|
+
},
|
|
23
|
+
/** Кастомный aria-label (переопределяет автоматический label для типа) */
|
|
24
|
+
ariaLabel: {
|
|
25
|
+
type: String,
|
|
26
|
+
default: null,
|
|
27
|
+
},
|
|
28
|
+
/** Дополнительные CSS классы */
|
|
29
|
+
class: {
|
|
30
|
+
type: [String, Array, Object],
|
|
31
|
+
default: null,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const attrs = useAttrs();
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Атрибуты для передачи на nav элемент (исключая class и aria-label, которые обрабатываются отдельно)
|
|
39
|
+
*
|
|
40
|
+
* @description
|
|
41
|
+
* В Vue 3 с <script setup>, props определенные через defineProps автоматически
|
|
42
|
+
* не попадают в $attrs. Исключаем class и aria-label, так как они обрабатываются отдельно.
|
|
43
|
+
*/
|
|
44
|
+
const navAttrs = computed(() => {
|
|
45
|
+
const { class: _, "aria-label": __, ...rest } = attrs;
|
|
46
|
+
return rest;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Автоматические aria-label для типов навигации
|
|
51
|
+
*
|
|
52
|
+
* @description
|
|
53
|
+
* Маппинг типов навигации на стандартные aria-label значения
|
|
54
|
+
* для улучшения accessibility.
|
|
55
|
+
*/
|
|
56
|
+
const typeAriaLabels = {
|
|
57
|
+
breadcrumb: "Breadcrumb",
|
|
58
|
+
pagination: "Pagination",
|
|
59
|
+
menu: "Navigation menu",
|
|
60
|
+
main: "Main navigation",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Вычисляет aria-label для nav элемента
|
|
65
|
+
*
|
|
66
|
+
* @description
|
|
67
|
+
* Приоритет:
|
|
68
|
+
* 1. Если передан ariaLabel prop → используем его
|
|
69
|
+
* 2. Если type !== 'custom' → используем автоматический label из typeAriaLabels
|
|
70
|
+
* 3. Если type === 'custom' и нет ariaLabel → возвращаем null (не добавляем aria-label)
|
|
71
|
+
*
|
|
72
|
+
* @returns {string|null} Значение aria-label или null
|
|
73
|
+
*/
|
|
74
|
+
const computedAriaLabel = computed(() => {
|
|
75
|
+
// Если передан кастомный aria-label, используем его
|
|
76
|
+
if (props.ariaLabel) {
|
|
77
|
+
return props.ariaLabel;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Если тип не custom, используем автоматический label
|
|
81
|
+
if (props.type !== "custom") {
|
|
82
|
+
return typeAriaLabels[props.type] || null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Для custom типа без ariaLabel не добавляем aria-label
|
|
86
|
+
return null;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Вычисляет классы для nav элемента
|
|
91
|
+
*
|
|
92
|
+
* @description
|
|
93
|
+
* Объединяет prop class с любыми классами из $attrs.class.
|
|
94
|
+
*
|
|
95
|
+
* @returns {string|Array|Object|null} Классы для применения к элементу
|
|
96
|
+
*/
|
|
97
|
+
const computedClass = computed(() => {
|
|
98
|
+
const classes = [];
|
|
99
|
+
|
|
100
|
+
// Добавляем класс из prop class
|
|
101
|
+
if (props.class) {
|
|
102
|
+
classes.push(props.class);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Добавляем класс из $attrs.class если есть
|
|
106
|
+
if (attrs.class) {
|
|
107
|
+
classes.push(attrs.class);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Возвращаем массив если есть несколько классов, иначе одно значение или null
|
|
111
|
+
return classes.length > 1 ? classes : classes[0] || null;
|
|
112
|
+
});
|
|
113
|
+
</script>
|
|
114
|
+
|