mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(editor): Add functionality to create folders (#13473)
This commit is contained in:
committed by
GitHub
parent
f381a24145
commit
2cb9d9e29f
@@ -72,6 +72,8 @@ const save = async (): Promise<void> => {
|
||||
return;
|
||||
}
|
||||
|
||||
const parentFolderId = router.currentRoute.value.params.folderId as string | undefined;
|
||||
|
||||
const currentWorkflowId = props.data.id;
|
||||
isSaving.value = true;
|
||||
|
||||
@@ -102,6 +104,7 @@ const save = async (): Promise<void> => {
|
||||
resetWebhookUrls: true,
|
||||
openInNewWindow: true,
|
||||
resetNodeIds: true,
|
||||
parentFolderId,
|
||||
});
|
||||
|
||||
if (saved) {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import type { FolderPathItem } from '@/Interface';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { ProjectTypes } from '@/types/projects.types';
|
||||
import type { UserAction } from '@n8n/design-system/types';
|
||||
import { type PathItem } from '@n8n/design-system/components/N8nBreadcrumbs/Breadcrumbs.vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
type Props = {
|
||||
actions: UserAction[];
|
||||
breadcrumbs: {
|
||||
visibleItems: FolderPathItem[];
|
||||
hiddenItems: FolderPathItem[];
|
||||
};
|
||||
};
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
itemSelected: [item: PathItem];
|
||||
action: [action: string];
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const currentProject = computed(() => projectsStore.currentProject);
|
||||
|
||||
const projectName = computed(() => {
|
||||
if (currentProject.value?.type === ProjectTypes.Personal) {
|
||||
return i18n.baseText('projects.menu.personal');
|
||||
}
|
||||
return currentProject.value?.name;
|
||||
});
|
||||
|
||||
const onItemSelect = (item: PathItem) => {
|
||||
emit('itemSelected', item);
|
||||
};
|
||||
|
||||
const onAction = (action: string) => {
|
||||
emit('action', action);
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<n8n-breadcrumbs
|
||||
v-if="breadcrumbs.visibleItems"
|
||||
:items="breadcrumbs.visibleItems"
|
||||
:highlight-last-item="false"
|
||||
:path-truncated="breadcrumbs.visibleItems[0].parentFolder"
|
||||
:hidden-items="breadcrumbs.hiddenItems"
|
||||
data-test-id="folder-card-breadcrumbs"
|
||||
@item-selected="onItemSelect"
|
||||
>
|
||||
<template v-if="currentProject" #prepend>
|
||||
<div :class="$style['home-project']">
|
||||
<n8n-link :to="`/projects/${currentProject.id}`">
|
||||
<N8nText size="large" color="text-base">{{ projectName }}</N8nText>
|
||||
</n8n-link>
|
||||
</div>
|
||||
</template>
|
||||
</n8n-breadcrumbs>
|
||||
<n8n-action-toggle
|
||||
v-if="breadcrumbs.visibleItems"
|
||||
:actions="actions"
|
||||
theme="dark"
|
||||
data-test-id="folder-breadcrumbs-actions"
|
||||
@action="onAction"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.home-project {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover * {
|
||||
color: var(--color-text-dark);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event';
|
||||
import FolderCard from './FolderCard.vue';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import type { FolderResource } from '../layouts/ResourcesListLayout.vue';
|
||||
import type { UserAction } from '@/Interface';
|
||||
import type { FolderPathItem, UserAction } from '@/Interface';
|
||||
|
||||
vi.mock('vue-router', () => {
|
||||
const push = vi.fn();
|
||||
@@ -61,6 +61,11 @@ const PARENT_FOLDER: FolderResource = {
|
||||
},
|
||||
} as const satisfies FolderResource;
|
||||
|
||||
const DEFAULT_BREADCRUMBS: { visibleItems: FolderPathItem[]; hiddenItems: FolderPathItem[] } = {
|
||||
visibleItems: [{ id: '1', label: 'Parent 2' }],
|
||||
hiddenItems: [{ id: '2', label: 'Parent 1', parentFolder: '1' }],
|
||||
};
|
||||
|
||||
const renderComponent = createComponentRenderer(FolderCard, {
|
||||
props: {
|
||||
data: DEFAULT_FOLDER,
|
||||
@@ -68,6 +73,7 @@ const renderComponent = createComponentRenderer(FolderCard, {
|
||||
{ label: 'Open', value: 'open', disabled: false },
|
||||
{ label: 'Delete', value: 'delete', disabled: false },
|
||||
] as const satisfies UserAction[],
|
||||
breadcrumbs: DEFAULT_BREADCRUMBS,
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
@@ -145,6 +151,10 @@ describe('FolderCard', () => {
|
||||
},
|
||||
parentFolder: PARENT_FOLDER,
|
||||
},
|
||||
breadcrumbs: {
|
||||
visibleItems: [{ id: PARENT_FOLDER.id, label: PARENT_FOLDER.name, parentFolder: '1' }],
|
||||
hiddenItems: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(getByTestId('folder-card-icon')).toBeInTheDocument();
|
||||
|
||||
@@ -7,11 +7,15 @@ import { useI18n } from '@/composables/useI18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { VIEWS } from '@/constants';
|
||||
import type { PathItem } from '@n8n/design-system/components/N8nBreadcrumbs/Breadcrumbs.vue';
|
||||
import type { UserAction } from '@/Interface';
|
||||
import type { FolderPathItem, UserAction } from '@/Interface';
|
||||
|
||||
type Props = {
|
||||
data: FolderResource;
|
||||
actions: UserAction[];
|
||||
breadcrumbs: {
|
||||
visibleItems: FolderPathItem[];
|
||||
hiddenItems: FolderPathItem[];
|
||||
};
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -27,18 +31,6 @@ const emit = defineEmits<{
|
||||
folderOpened: [{ folder: FolderResource }];
|
||||
}>();
|
||||
|
||||
const breadCrumbsItems = computed(() => {
|
||||
if (props.data.parentFolder) {
|
||||
return [
|
||||
{
|
||||
id: props.data.parentFolder.id,
|
||||
label: props.data.parentFolder.name,
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const projectIcon = computed<ProjectIcon>(() => {
|
||||
const defaultIcon: ProjectIcon = { type: 'icon', value: 'layer-group' };
|
||||
if (props.data.homeProject?.type === ProjectTypes.Personal) {
|
||||
@@ -109,7 +101,7 @@ const onBreadcrumbsItemClick = async (item: PathItem) => {
|
||||
<n8n-text
|
||||
size="small"
|
||||
color="text-light"
|
||||
:class="$style['info-cell']"
|
||||
:class="[$style['info-cell'], $style['info-cell--workflow-count']]"
|
||||
data-test-id="folder-card-workflow-count"
|
||||
>
|
||||
{{ data.workflowCount }} {{ i18n.baseText('generic.workflows') }}
|
||||
@@ -117,7 +109,7 @@ const onBreadcrumbsItemClick = async (item: PathItem) => {
|
||||
<n8n-text
|
||||
size="small"
|
||||
color="text-light"
|
||||
:class="$style['info-cell']"
|
||||
:class="[$style['info-cell'], $style['info-cell--updated']]"
|
||||
data-test-id="folder-card-last-updated"
|
||||
>
|
||||
{{ i18n.baseText('workerList.item.lastUpdated') }}
|
||||
@@ -126,7 +118,7 @@ const onBreadcrumbsItemClick = async (item: PathItem) => {
|
||||
<n8n-text
|
||||
size="small"
|
||||
color="text-light"
|
||||
:class="$style['info-cell']"
|
||||
:class="[$style['info-cell'], $style['info-cell--created']]"
|
||||
data-test-id="folder-card-created"
|
||||
>
|
||||
{{ i18n.baseText('workflows.item.created') }}
|
||||
@@ -136,26 +128,29 @@ const onBreadcrumbsItemClick = async (item: PathItem) => {
|
||||
</template>
|
||||
<template #append>
|
||||
<div :class="$style['card-actions']" @click.prevent>
|
||||
<n8n-breadcrumbs
|
||||
:items="breadCrumbsItems"
|
||||
:path-truncated="true"
|
||||
:show-border="true"
|
||||
:highlight-last-item="false"
|
||||
theme="small"
|
||||
data-test-id="folder-card-breadcrumbs"
|
||||
@item-selected="onBreadcrumbsItemClick"
|
||||
>
|
||||
<template v-if="data.homeProject" #prepend>
|
||||
<div :class="$style['home-project']">
|
||||
<n8n-link :to="`/projects/${data.homeProject.id}`">
|
||||
<ProjectIcon :icon="projectIcon" :border-less="true" size="mini" />
|
||||
<n8n-text size="small" :compact="true" :bold="true" color="text-base">
|
||||
{{ projectName }}
|
||||
</n8n-text>
|
||||
</n8n-link>
|
||||
</div>
|
||||
</template>
|
||||
</n8n-breadcrumbs>
|
||||
<div :class="$style.breadcrumbs">
|
||||
<n8n-breadcrumbs
|
||||
:items="breadcrumbs.visibleItems"
|
||||
:hidden-items="breadcrumbs.hiddenItems"
|
||||
:path-truncated="breadcrumbs.visibleItems[0]?.parentFolder"
|
||||
:show-border="true"
|
||||
:highlight-last-item="false"
|
||||
theme="small"
|
||||
data-test-id="folder-card-breadcrumbs"
|
||||
@item-selected="onBreadcrumbsItemClick"
|
||||
>
|
||||
<template v-if="data.homeProject" #prepend>
|
||||
<div :class="$style['home-project']">
|
||||
<n8n-link :to="`/projects/${data.homeProject.id}`">
|
||||
<ProjectIcon :icon="projectIcon" :border-less="true" size="mini" />
|
||||
<n8n-text size="small" :compact="true" :bold="true" color="text-base">
|
||||
{{ projectName }}
|
||||
</n8n-text>
|
||||
</n8n-link>
|
||||
</div>
|
||||
</template>
|
||||
</n8n-breadcrumbs>
|
||||
</div>
|
||||
<n8n-action-toggle
|
||||
v-if="actions.length"
|
||||
:actions="actions"
|
||||
@@ -214,4 +209,23 @@ const onBreadcrumbsItemClick = async (item: PathItem) => {
|
||||
gap: var(--spacing-3xs);
|
||||
color: var(--color-text-dark);
|
||||
}
|
||||
|
||||
@include mixins.breakpoint('sm-and-down') {
|
||||
.card {
|
||||
flex-wrap: wrap;
|
||||
|
||||
:global(.n8n-card-append) {
|
||||
width: 100%;
|
||||
margin-top: var(--spacing-3xs);
|
||||
padding-left: 40px;
|
||||
}
|
||||
.card-actions {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
.info-cell--created {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ProjectTypes } from '@/types/projects.types';
|
||||
import { VIEWS } from '@/constants';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { waitFor, within } from '@testing-library/vue';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
|
||||
const mockPush = vi.fn();
|
||||
vi.mock('vue-router', async () => {
|
||||
@@ -43,14 +44,17 @@ const renderComponent = createComponentRenderer(ProjectHeader, {
|
||||
|
||||
let route: ReturnType<typeof router.useRoute>;
|
||||
let projectsStore: ReturnType<typeof mockedStore<typeof useProjectsStore>>;
|
||||
let settingsStore: ReturnType<typeof mockedStore<typeof useSettingsStore>>;
|
||||
|
||||
describe('ProjectHeader', () => {
|
||||
beforeEach(() => {
|
||||
createTestingPinia();
|
||||
route = router.useRoute();
|
||||
projectsStore = mockedStore(useProjectsStore);
|
||||
settingsStore = mockedStore(useSettingsStore);
|
||||
|
||||
projectsStore.teamProjectsLimit = -1;
|
||||
settingsStore.settings.folders = { enabled: false };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import type { UserAction } from '@n8n/design-system';
|
||||
import { N8nButton, N8nTooltip } from '@n8n/design-system';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { type ProjectIcon, ProjectTypes } from '@/types/projects.types';
|
||||
@@ -10,12 +11,18 @@ import { getResourcePermissions } from '@/permissions';
|
||||
import { VIEWS } from '@/constants';
|
||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||
import ProjectCreateResource from '@/components/Projects/ProjectCreateResource.vue';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const i18n = useI18n();
|
||||
const projectsStore = useProjectsStore();
|
||||
const sourceControlStore = useSourceControlStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const emit = defineEmits<{
|
||||
createFolder: [];
|
||||
}>();
|
||||
|
||||
const headerIcon = computed((): ProjectIcon => {
|
||||
if (projectsStore.currentProject?.type === ProjectTypes.Personal) {
|
||||
@@ -49,31 +56,43 @@ const showSettings = computed(
|
||||
);
|
||||
|
||||
const homeProject = computed(() => projectsStore.currentProject ?? projectsStore.personalProject);
|
||||
const isFoldersFeatureEnabled = computed(() => settingsStore.settings.folders.enabled);
|
||||
|
||||
const ACTION_TYPES = {
|
||||
WORKFLOW: 'workflow',
|
||||
CREDENTIAL: 'credential',
|
||||
FOLDER: 'folder',
|
||||
} as const;
|
||||
type ActionTypes = (typeof ACTION_TYPES)[keyof typeof ACTION_TYPES];
|
||||
|
||||
const createWorkflowButton = computed(() => ({
|
||||
value: ACTION_TYPES.WORKFLOW,
|
||||
label: 'Create Workflow',
|
||||
label: i18n.baseText('projects.header.create.workflow'),
|
||||
icon: sourceControlStore.preferences.branchReadOnly ? 'lock' : undefined,
|
||||
size: 'mini' as const,
|
||||
disabled:
|
||||
sourceControlStore.preferences.branchReadOnly ||
|
||||
!getResourcePermissions(homeProject.value?.scopes).workflow.create,
|
||||
}));
|
||||
const menu = computed(() => [
|
||||
{
|
||||
value: ACTION_TYPES.CREDENTIAL,
|
||||
label: 'Create credential',
|
||||
disabled:
|
||||
sourceControlStore.preferences.branchReadOnly ||
|
||||
!getResourcePermissions(homeProject.value?.scopes).credential.create,
|
||||
},
|
||||
]);
|
||||
const menu = computed(() => {
|
||||
const items: UserAction[] = [
|
||||
{
|
||||
value: ACTION_TYPES.CREDENTIAL,
|
||||
label: i18n.baseText('projects.header.create.credential'),
|
||||
disabled:
|
||||
sourceControlStore.preferences.branchReadOnly ||
|
||||
!getResourcePermissions(homeProject.value?.scopes).credential.create,
|
||||
},
|
||||
];
|
||||
if (isFoldersFeatureEnabled.value) {
|
||||
items.push({
|
||||
value: ACTION_TYPES.FOLDER,
|
||||
label: i18n.baseText('projects.header.create.folder'),
|
||||
disabled: false,
|
||||
});
|
||||
}
|
||||
return items;
|
||||
});
|
||||
|
||||
const actions: Record<ActionTypes, (projectId: string) => void> = {
|
||||
[ACTION_TYPES.WORKFLOW]: (projectId: string) => {
|
||||
@@ -81,6 +100,7 @@ const actions: Record<ActionTypes, (projectId: string) => void> = {
|
||||
name: VIEWS.NEW_WORKFLOW,
|
||||
query: {
|
||||
projectId,
|
||||
parentFolderId: route.params.folderId as string,
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -93,6 +113,9 @@ const actions: Record<ActionTypes, (projectId: string) => void> = {
|
||||
},
|
||||
});
|
||||
},
|
||||
[ACTION_TYPES.FOLDER]: async () => {
|
||||
emit('createFolder');
|
||||
},
|
||||
} as const;
|
||||
|
||||
const onSelect = (action: string) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { IUser } from '@/Interface';
|
||||
import type { FolderPathItem, IUser } from '@/Interface';
|
||||
import {
|
||||
DUPLICATE_MODAL_KEY,
|
||||
MODAL_CONFIRM,
|
||||
@@ -39,6 +39,10 @@ const WORKFLOW_LIST_ITEM_ACTIONS = {
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data: WorkflowResource;
|
||||
breadcrumbs: {
|
||||
visibleItems: FolderPathItem[];
|
||||
hiddenItems: FolderPathItem[];
|
||||
};
|
||||
readOnly?: boolean;
|
||||
workflowListEventBus?: EventBus;
|
||||
}>(),
|
||||
@@ -115,18 +119,6 @@ const formattedCreatedAtDate = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
const breadCrumbsItems = computed(() => {
|
||||
if (props.data.parentFolder) {
|
||||
return [
|
||||
{
|
||||
id: props.data.parentFolder.id,
|
||||
label: props.data.parentFolder.name,
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const projectIcon = computed<CardProjectIcon>(() => {
|
||||
const defaultIcon: CardProjectIcon = { type: 'icon', value: 'layer-group' };
|
||||
if (props.data.homeProject?.type === ProjectTypes.Personal) {
|
||||
@@ -306,26 +298,28 @@ const emitWorkflowActiveToggle = (value: { id: string; active: boolean }) => {
|
||||
:resource-type-label="resourceTypeLabel"
|
||||
:personal-project="projectsStore.personalProject"
|
||||
/>
|
||||
<n8n-breadcrumbs
|
||||
v-else
|
||||
:items="breadCrumbsItems"
|
||||
:path-truncated="true"
|
||||
:show-border="true"
|
||||
:highlight-last-item="false"
|
||||
theme="small"
|
||||
data-test-id="folder-card-breadcrumbs"
|
||||
>
|
||||
<template v-if="data.homeProject" #prepend>
|
||||
<div :class="$style['home-project']">
|
||||
<n8n-link :to="`/projects/${data.homeProject.id}`">
|
||||
<ProjectIcon :icon="projectIcon" :border-less="true" size="mini" />
|
||||
<n8n-text size="small" :compact="true" :bold="true" color="text-base">{{
|
||||
projectName
|
||||
}}</n8n-text>
|
||||
</n8n-link>
|
||||
</div>
|
||||
</template>
|
||||
</n8n-breadcrumbs>
|
||||
<div v-else :class="$style.breadcrumbs">
|
||||
<n8n-breadcrumbs
|
||||
:items="breadcrumbs.visibleItems"
|
||||
:hidden-items="breadcrumbs.hiddenItems"
|
||||
:path-truncated="breadcrumbs.visibleItems[0]?.parentFolder"
|
||||
:show-border="true"
|
||||
:highlight-last-item="false"
|
||||
theme="small"
|
||||
data-test-id="folder-card-breadcrumbs"
|
||||
>
|
||||
<template v-if="data.homeProject" #prepend>
|
||||
<div :class="$style['home-project']">
|
||||
<n8n-link :to="`/projects/${data.homeProject.id}`">
|
||||
<ProjectIcon :icon="projectIcon" :border-less="true" size="mini" />
|
||||
<n8n-text size="small" :compact="true" :bold="true" color="text-base">{{
|
||||
projectName
|
||||
}}</n8n-text>
|
||||
</n8n-link>
|
||||
</div>
|
||||
</template>
|
||||
</n8n-breadcrumbs>
|
||||
</div>
|
||||
<WorkflowActivator
|
||||
class="mr-s"
|
||||
:workflow-active="data.active"
|
||||
@@ -405,8 +399,15 @@ const emitWorkflowActiveToggle = (value: { id: string; active: boolean }) => {
|
||||
padding: 0 var(--spacing-s) var(--spacing-s);
|
||||
}
|
||||
|
||||
.cardBadge {
|
||||
.cardBadge,
|
||||
.breadcrumbs {
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@include mixins.breakpoint('xs-only') {
|
||||
.breadcrumbs > div {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -555,22 +555,24 @@ const loadPaginationFromQueryString = async () => {
|
||||
/>
|
||||
</n8n-select>
|
||||
</div>
|
||||
<ResourceFiltersDropdown
|
||||
v-if="showFiltersDropdown"
|
||||
:keys="filterKeys"
|
||||
:reset="resetFilters"
|
||||
:model-value="filtersModel"
|
||||
:shareable="shareable"
|
||||
:just-icon="true"
|
||||
@update:model-value="onUpdateFilters"
|
||||
@update:filters-length="onUpdateFiltersLength"
|
||||
>
|
||||
<template #default="resourceFiltersSlotProps">
|
||||
<slot name="filters" v-bind="resourceFiltersSlotProps" />
|
||||
</template>
|
||||
</ResourceFiltersDropdown>
|
||||
<div :class="$style['sort-and-filter']">
|
||||
<ResourceFiltersDropdown
|
||||
v-if="showFiltersDropdown"
|
||||
:keys="filterKeys"
|
||||
:reset="resetFilters"
|
||||
:model-value="filtersModel"
|
||||
:shareable="shareable"
|
||||
:just-icon="true"
|
||||
@update:model-value="onUpdateFilters"
|
||||
@update:filters-length="onUpdateFiltersLength"
|
||||
>
|
||||
<template #default="resourceFiltersSlotProps">
|
||||
<slot name="filters" v-bind="resourceFiltersSlotProps" />
|
||||
</template>
|
||||
</ResourceFiltersDropdown>
|
||||
<slot name="add-button"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="add-button"></slot>
|
||||
</div>
|
||||
|
||||
<slot name="callout"></slot>
|
||||
@@ -680,19 +682,20 @@ const loadPaginationFromQueryString = async () => {
|
||||
justify-content: end;
|
||||
width: 100%;
|
||||
|
||||
@include mixins.breakpoint('xs-only') {
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-auto-flow: row;
|
||||
.sort-and-filter {
|
||||
display: flex;
|
||||
gap: var(--spacing-2xs);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
> *:last-child {
|
||||
grid-column: auto;
|
||||
}
|
||||
@include mixins.breakpoint('xs-only') {
|
||||
grid-auto-flow: row;
|
||||
grid-auto-columns: unset;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
// max-width: 240px;
|
||||
|
||||
@include mixins.breakpoint('sm-and-down') {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user