diff --git a/src/backend/core/api/viewsets/thread.py b/src/backend/core/api/viewsets/thread.py index 94c6f3bf..6111a2a0 100644 --- a/src/backend/core/api/viewsets/thread.py +++ b/src/backend/core/api/viewsets/thread.py @@ -275,8 +275,8 @@ class ThreadViewSet( es_filters[f"is_{field_name}"] = False # Get page parameters - page = self.paginator.get_page_number(request, self) - page_size = self.paginator.get_page_size(request) + page = int(self.paginator.get_page_number(request, self)) + page_size = int(self.paginator.get_page_size(request)) # Get search results from Elasticsearch results = search_threads( diff --git a/src/frontend/src/features/forms/components/search-input/_index.scss b/src/frontend/src/features/forms/components/search-input/_index.scss new file mode 100644 index 00000000..9f4fe209 --- /dev/null +++ b/src/frontend/src/features/forms/components/search-input/_index.scss @@ -0,0 +1,41 @@ +.search { + width: 100%; + display: flex; + align-items: center; + background-color: var(--c--theme--colors--greyscale-050); + border-radius: var(--c--theme--spacings--3xs); + border: 1px solid var(--c--theme--colors--greyscale-200); + max-width: 680px; +} + +.search:focus-within { + outline: 5px auto Highlight; + outline: 5px auto -webkit-focus-ring-color; + + > .search__input { + outline: none; + } +} + +.search__label { + padding-left: var(--c--theme--spacings--sm); + display: grid; + place-items: center; + cursor: text; +} + +.search__input { + appearance: none; + border: none; + background-color: transparent; + padding: var(--c--theme--spacings--sm); + flex: 1; + width: 100%; + height: 100%; +} + +@media screen and (max-width: 1024px) { + .search{ + max-width: 100%; + } +} \ No newline at end of file diff --git a/src/frontend/src/features/forms/components/search-input/index.tsx b/src/frontend/src/features/forms/components/search-input/index.tsx new file mode 100644 index 00000000..56e042c1 --- /dev/null +++ b/src/frontend/src/features/forms/components/search-input/index.tsx @@ -0,0 +1,60 @@ +import { useRouter } from "next/router"; +import { useTranslation } from "react-i18next"; +import { useSearchParams } from "next/navigation"; +import { useEffect, useState, useRef } from "react"; + +export const SearchInput = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + const [value, setValue] = useState(searchParams.get('search') || ''); + const { t } = useTranslation(); + const isUserTyping = useRef(false); + + /** + * Each time the user types, we update the URL with the new search query. + */ + const handleSearch = (event: React.ChangeEvent) => { + const query = event.target.value; + isUserTyping.current = true; + setValue(query); + + const url = new URL(router.asPath, 'http://localhost'); + if (query) { + url.searchParams.set('search', query); + } else { + url.searchParams.delete('search'); + } + + router.replace(url.pathname + url.search, undefined, { shallow: true }); + } + + /** + * Each time the URL changes, we update the search query + * except when the user is typing to prevent the cursor from jumping + * to the end of the input. + */ + useEffect(() => { + // Only update the value from searchParams if the user is not currently typing + if (!isUserTyping.current) { + setValue(searchParams.get('search') || ''); + } + isUserTyping.current = false; + }, [searchParams]); + + return ( +
+ + +
+ ); +} \ No newline at end of file diff --git a/src/frontend/src/features/i18n/translations.json b/src/frontend/src/features/i18n/translations.json index ad6b84e5..115c28a3 100644 --- a/src/frontend/src/features/i18n/translations.json +++ b/src/frontend/src/features/i18n/translations.json @@ -82,6 +82,9 @@ }, "message_editor": { "start_typing": "Start typing..." + }, + "search": { + "placeholder": "Search..." } } }, @@ -170,6 +173,9 @@ }, "message_editor": { "start_typing": "Commencer à écrire..." + }, + "search": { + "placeholder": "Rechercher..." } } } diff --git a/src/frontend/src/features/layouts/components/global/global-layout.scss b/src/frontend/src/features/layouts/components/global/global-layout.scss index 47faa3cf..5997826c 100644 --- a/src/frontend/src/features/layouts/components/global/global-layout.scss +++ b/src/frontend/src/features/layouts/components/global/global-layout.scss @@ -1,6 +1,6 @@ nav { width: 100%; - height: 52px; + height: var(--header-height); background-color: #fff; border: 1px grey solid; display: flex; diff --git a/src/frontend/src/features/layouts/components/header/_index.scss b/src/frontend/src/features/layouts/components/header/_index.scss new file mode 100644 index 00000000..e89dc004 --- /dev/null +++ b/src/frontend/src/features/layouts/components/header/_index.scss @@ -0,0 +1,50 @@ +.c__header { + --header-height: 52px; + display: flex; + background-color: var(--c--theme--colors--greyscale-000); + border-bottom: 1px solid var(--c--theme--colors--greyscale-200); + justify-content: space-between; + align-items: center; + height: var(--header-height); + min-height: var(--header-height); + max-height: var(--header-height); + padding: 0 1.125rem; + + @media screen and (max-width: 1024px) { + padding: 0 1rem; + } + + &__toggle-menu { + display: none; + + @media screen and (max-width: 1024px) { + display: block; + } + + &__icon { + color: var(--c--theme--colors--primary-800); + } + } + + &__left, + &__center, + &__right { + display: flex; + align-items: center; + } + + &__center { + background: red; + } + + &__right { + display: flex; + align-items: center; + + &__language-picker { + @media screen and (max-width: 1024px) { + display: none; + } + } + } +} \ No newline at end of file diff --git a/src/frontend/src/features/layouts/components/header/index.scss b/src/frontend/src/features/layouts/components/header/index.scss deleted file mode 100644 index 6c53064d..00000000 --- a/src/frontend/src/features/layouts/components/header/index.scss +++ /dev/null @@ -1,5 +0,0 @@ -.c__header-right { - display: flex; - align-items: center; - gap: 1rem; -} diff --git a/src/frontend/src/features/layouts/components/header/index.tsx b/src/frontend/src/features/layouts/components/header/index.tsx index aa3fc146..84f9f72b 100644 --- a/src/frontend/src/features/layouts/components/header/index.tsx +++ b/src/frontend/src/features/layouts/components/header/index.tsx @@ -18,7 +18,7 @@ export const HeaderRight = () => { const [isOpen, setIsOpen] = useState(false); const { t } = useTranslation(); return ( -
+ <> {user && ( { } iconPosition="right" > - {t("my_account")} + {t("my_account")} )} -
+ ); }; diff --git a/src/frontend/src/features/layouts/components/mailbox-panel/components/mailbox-list/index.tsx b/src/frontend/src/features/layouts/components/mailbox-panel/components/mailbox-list/index.tsx index 878ebc0d..784237fc 100644 --- a/src/frontend/src/features/layouts/components/mailbox-panel/components/mailbox-list/index.tsx +++ b/src/frontend/src/features/layouts/components/mailbox-panel/components/mailbox-list/index.tsx @@ -111,6 +111,7 @@ const FolderItem = ({ folder }: FolderItemProps) => { return ( div { font-size: var(--c--theme--font--sizes--h4); diff --git a/src/frontend/src/features/layouts/components/main/header/_index.scss b/src/frontend/src/features/layouts/components/main/header/_index.scss new file mode 100644 index 00000000..7732d29a --- /dev/null +++ b/src/frontend/src/features/layouts/components/main/header/_index.scss @@ -0,0 +1,58 @@ +.c__header { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-template-areas: "left center right"; + background-color: var(--c--theme--colors--greyscale-000); + border-bottom: 1px solid var(--c--theme--colors--greyscale-200); + height: var(--header-height); + min-height: var(--header-height); + max-height: var(--header-height); + padding: 0 1.125rem; + + @media screen and (max-width: 1024px) { + grid-template-columns: auto 1fr 1fr; + grid-template-rows: 1fr 1fr; + grid-template-areas: + "toggle left right" + "center center center"; + padding: 0 1rem; + } + + &__toggle-menu { + display: none; + grid-area: toggle; + + @media screen and (max-width: 1024px) { + display: block; + } + + &__icon { + color: var(--c--theme--colors--primary-800); + } + } + + &__left, + &__center, + &__right { + display: flex; + align-items: center; + } + + &__left { + grid-area: left; + } + + &__center { + grid-area: center; + } + + &__right { + grid-area: right; + justify-content: flex-end; + &__language-picker { + @media screen and (max-width: 1024px) { + display: none; + } + } + } +} diff --git a/src/frontend/src/features/layouts/components/main/header/index.tsx b/src/frontend/src/features/layouts/components/main/header/index.tsx new file mode 100644 index 00000000..47b783b3 --- /dev/null +++ b/src/frontend/src/features/layouts/components/main/header/index.tsx @@ -0,0 +1,52 @@ +import { SearchInput } from "@/features/forms/components/search-input"; +import { DropdownMenuOption, LanguagePicker } from "@gouvfr-lasuite/ui-kit"; + +import { Button, useCunningham } from "@openfun/cunningham-react"; + +export type HeaderProps = { + leftIcon?: React.ReactNode; + rightIcon?: React.ReactNode; + languages?: DropdownMenuOption[]; + onTogglePanel?: () => void; + isPanelOpen?: boolean; +}; + +export const Header = ({ + leftIcon, + rightIcon, + languages, + onTogglePanel, + isPanelOpen, +}: HeaderProps) => { + const { t } = useCunningham(); + return ( +
+
+
+
{leftIcon}
+
+ +
+
+ {languages && ( +
+ +
+ )} + + {rightIcon} +
+
+ ); +}; diff --git a/src/frontend/src/features/layouts/components/main/hooks/useControllableState.ts b/src/frontend/src/features/layouts/components/main/hooks/useControllableState.ts new file mode 100644 index 00000000..f0f2fa60 --- /dev/null +++ b/src/frontend/src/features/layouts/components/main/hooks/useControllableState.ts @@ -0,0 +1,42 @@ +import React, { useEffect } from "react"; + +// TODO: This hook is from Cunningham, it should be exporter from it. + +/** + * This hook is used to create a state that can be controlled by the parent. If not the state is handled internally. + * + * @param defaultValue if not controlled by the parent, this is the default value + * @param propsValue if controlled by the parent, this is the value + * @param propsCallback if controlled by the parent, this is the callback to call when the value changes + */ +export const useControllableState = ( + defaultValue: T, + propsValue?: T, + propsCallback?: (value: T) => void +): [T, (value: T) => void] => { + const [state, setState] = React.useState( + typeof propsValue === "undefined" ? defaultValue : propsValue + ); + + // Bottom-Up. + const onChange = (value: T) => { + if (propsCallback) { + propsCallback(value); + } else { + setState(value); + } + }; + + // Top-Down. + useEffect(() => { + if (!propsCallback) { + return; + } + if (typeof propsValue !== "undefined" && propsValue !== state) { + setState(propsValue); + } + + }, [propsValue]); + + return [state, onChange]; +}; diff --git a/src/frontend/src/features/layouts/components/main/hooks/useResponsive.tsx b/src/frontend/src/features/layouts/components/main/hooks/useResponsive.tsx new file mode 100644 index 00000000..1579ec4d --- /dev/null +++ b/src/frontend/src/features/layouts/components/main/hooks/useResponsive.tsx @@ -0,0 +1,30 @@ +import { useEffect, useState } from "react"; +import config from "@/../cunningham"; + +export const useResponsive = () => { + const [width, setWidth] = useState(window.innerWidth); + const mobile = parseInt( + config.themes.default.theme.breakpoints.mobile.replace("px", "") + ); + const tablet = parseInt( + config.themes.default.theme.breakpoints.tablet.replace("px", "") + ); + + const handleWindowSizeChange = () => { + setWidth(window.innerWidth); + }; + + useEffect(() => { + window.addEventListener("resize", handleWindowSizeChange); + + return () => { + window.removeEventListener("resize", handleWindowSizeChange); + }; + }, []); + + const isMobile = width <= mobile; + const isTablet = width <= tablet; + const isDesktop = width > tablet; + + return { isMobile, isTablet, isDesktop }; +}; diff --git a/src/frontend/src/features/layouts/components/main/index.tsx b/src/frontend/src/features/layouts/components/main/index.tsx index 66f469f0..f2f9aa10 100644 --- a/src/frontend/src/features/layouts/components/main/index.tsx +++ b/src/frontend/src/features/layouts/components/main/index.tsx @@ -1,4 +1,4 @@ -import { MainLayout as KitMainLayout } from "@gouvfr-lasuite/ui-kit"; +import { AppLayout } from "./layout"; import { Header, HeaderRight } from "../header"; import { MailboxPanel } from "@/features/layouts/components/mailbox-panel"; import { PropsWithChildren } from "react"; @@ -34,13 +34,15 @@ const MainLayoutContent = ({ children }: PropsWithChildren) => { ) : ( - } - icon={logo} - rightHeaderContent={} - > + <> + } + icon={logo} + rightHeaderContent={} + > {children} - + + ) } \ No newline at end of file diff --git a/src/frontend/src/features/layouts/components/main/layout/_index.scss b/src/frontend/src/features/layouts/components/main/layout/_index.scss new file mode 100644 index 00000000..1333a9fc --- /dev/null +++ b/src/frontend/src/features/layouts/components/main/layout/_index.scss @@ -0,0 +1,39 @@ +.c__main-layout { + display: flex; + flex-direction: column; + height: 100dvh; +} + +.c__main-layout__header { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; +} + +.c__main-layout__content { + width: 100%; + height: 100%; + display: flex; + margin-top: var(--header-height); + + &__left-panel { + height: calc(100dvh - var(--header-height)); + width: 300px; + border-right: 1px solid var(--c--theme--colors--greyscale-200); + @media screen and (max-width: 1024px) { + width: 100dvw; + } + } + &__center { + display: flex; + + &__children { + height: calc(100dvh - var(--header-height)); + width: 100%; + flex: 1; + overflow-y: auto; + } + } +} diff --git a/src/frontend/src/features/layouts/components/main/layout/index.tsx b/src/frontend/src/features/layouts/components/main/layout/index.tsx new file mode 100644 index 00000000..2f26e27f --- /dev/null +++ b/src/frontend/src/features/layouts/components/main/layout/index.tsx @@ -0,0 +1,158 @@ +import { PropsWithChildren, useEffect, useRef, useState } from "react"; +import { Header } from "../header"; +import { useResponsive } from "../hooks/useResponsive"; +import { + ImperativePanelHandle, + Panel, + PanelGroup, + PanelResizeHandle, +} from "react-resizable-panels"; +import { DropdownMenuOption,LeftPanel } from "@gouvfr-lasuite/ui-kit"; +import { RightPanel } from "../right-panel"; +import { useControllableState } from "../hooks/useControllableState"; +export type MainLayoutProps = { + icon?: React.ReactNode; + leftPanelContent?: React.ReactNode; + rightPanelContent?: React.ReactNode; + rightHeaderContent?: React.ReactNode; + languages?: DropdownMenuOption[]; + onToggleRightPanel?: () => void; + enableResize?: boolean; + rightPanelIsOpen?: boolean; + hideLeftPanelOnDesktop?: boolean; + isLeftPanelOpen?: boolean; + setIsLeftPanelOpen?: (isLeftPanelOpen: boolean) => void; +}; + +/** + * This component is a copy of the MainLayout component from our ui-kit. + * + * @TODO: Remove this and update the ui-kit to be able to render panels without header or + * add props to fully override the header. + */ +export const AppLayout = ({ + icon, + children, + hideLeftPanelOnDesktop = false, + leftPanelContent, + rightPanelContent, + rightHeaderContent, + + languages, + enableResize = false, + rightPanelIsOpen = false, + + ...props +}: PropsWithChildren) => { + const [isLeftPanelOpen, setIsLeftPanelOpen] = useControllableState( + false, + props.isLeftPanelOpen, + props.setIsLeftPanelOpen + ); + + const { isDesktop } = useResponsive(); + const ref = useRef(null); + + const [minPanelSize, setMinPanelSize] = useState( + calculateDefaultSize(300, isDesktop) + ); + const [maxPanelSize, setMaxPanelSize] = useState( + calculateDefaultSize(450, isDesktop) + ); + + const onTogglePanel = () => { + setIsLeftPanelOpen(!isLeftPanelOpen); + }; + + useEffect(() => { + const updatePanelSize = () => { + const min = Math.round(calculateDefaultSize(300, isDesktop)); + const max = Math.round( + Math.min(calculateDefaultSize(450, isDesktop), 40) + ); + setMinPanelSize(isDesktop ? min : 0); + if (enableResize) { + setMaxPanelSize(max); + } else { + setMaxPanelSize(min); + } + }; + + updatePanelSize(); + window.addEventListener("resize", () => { + updatePanelSize(); + }); + + return () => { + window.removeEventListener("resize", updatePanelSize); + }; + }, [isDesktop, enableResize]); + + // We need to have two different states for the left panel, we want to always keep the + // left panel mounted on mobile in order to show the animation when it opens or closes, instead + // of abruptly disappearing when closing the panel. + // On desktop, we want to hide the left panel when the prop is set to true, so we need to + // completely unmount it as it will never be visible. + const mountLeftPanel = isDesktop ? !hideLeftPanelOnDesktop : true; + const showLeftPanel = isDesktop ? !hideLeftPanelOnDesktop : isLeftPanelOpen; + + return ( +
+
+
+
+
+ + {mountLeftPanel && ( + <> + + {leftPanelContent} + + {isDesktop && ( + + )} + + )} + +
+
+ {children} +
+ + + {rightPanelContent} + +
+
+
+
+
+ ); +}; + +const calculateDefaultSize = (targetWidth: number, isDesktop: boolean) => { + if (!isDesktop) { + return 0; + } + + const windowWidth = window.innerWidth; + + return (targetWidth / windowWidth) * 100; +}; diff --git a/src/frontend/src/features/layouts/components/main/right-panel/_index.scss b/src/frontend/src/features/layouts/components/main/right-panel/_index.scss new file mode 100644 index 00000000..8a9dcd8e --- /dev/null +++ b/src/frontend/src/features/layouts/components/main/right-panel/_index.scss @@ -0,0 +1,27 @@ +.c__right-panel { + height: calc(100dvh - var(--header-height)); + width: 0; + border-left: 1px solid var(--c--theme--colors--greyscale-200); + transition: width 0.15s ease-in-out; + overflow-y: auto; + + &.open { + width: 300px; + } + } + + .c__right-panel__mobile { + z-index: 998; + width: 100dvw; + height: calc(100dvh - var(--header-height)); + overflow-y: auto; + position: fixed; + background-color: var(--c--theme--colors--greyscale-000); + transition: transform 0.15s ease-in-out; + transform: translateX(100dvw); + + &.open { + transform: translateX(0); + } + } + \ No newline at end of file diff --git a/src/frontend/src/features/layouts/components/main/right-panel/index.tsx b/src/frontend/src/features/layouts/components/main/right-panel/index.tsx new file mode 100644 index 00000000..a2d6ddec --- /dev/null +++ b/src/frontend/src/features/layouts/components/main/right-panel/index.tsx @@ -0,0 +1,25 @@ +import { useResponsive } from "../hooks/useResponsive"; +import clsx from "clsx"; +import { PropsWithChildren } from "react"; + +export type RightPanelProps = { + isOpen?: boolean; +}; +export const RightPanel = ({ + children, + isOpen, +}: PropsWithChildren) => { + const { isDesktop } = useResponsive(); + + if (!isDesktop) { + return ( +
+ {children} +
+ ); + } + + return ( +
{children}
+ ); +}; diff --git a/src/frontend/src/features/layouts/components/thread-view/_index.scss b/src/frontend/src/features/layouts/components/thread-view/_index.scss index a924611d..57d83714 100644 --- a/src/frontend/src/features/layouts/components/thread-view/_index.scss +++ b/src/frontend/src/features/layouts/components/thread-view/_index.scss @@ -1,9 +1,21 @@ .thread-view { + background-color: var(--c--theme--colors--greyscale-050); display: flex; flex-direction: column; max-height: 100%; } +@media screen and (max-width: 1024px) { + .thread-view { + position: fixed; + top: var(--header-height); + left: 0; + right: 0; + bottom: 0; + z-index: 900; + } +} + .thread-view--loading { display: flex; align-items: center; diff --git a/src/frontend/src/features/providers/mailbox.tsx b/src/frontend/src/features/providers/mailbox.tsx index 416d93c1..3eef764c 100644 --- a/src/frontend/src/features/providers/mailbox.tsx +++ b/src/frontend/src/features/providers/mailbox.tsx @@ -5,6 +5,7 @@ import { useRouter } from "next/router"; import usePrevious from "@/hooks/use-previous"; import { useSearchParams } from "next/navigation"; import { DEFAULT_FOLDERS } from "../layouts/components/mailbox-panel/components/mailbox-list"; +import { useDebounceCallback } from "@/hooks/use-debounce-callback"; type QueryState = { status: QueryStatus, @@ -94,6 +95,10 @@ export const MailboxProvider = ({ children }: PropsWithChildren) => { const queryClient = useQueryClient(); const router = useRouter(); const searchParams = useSearchParams(); + const previousSearchParams = usePrevious(searchParams); + const hasSearchParamsChanged = useMemo(() => { + return previousSearchParams?.toString() !== searchParams.toString(); + }, [previousSearchParams, searchParams]); const [selectedMailbox, setSelectedMailbox] = useState(null); const [selectedThread, setSelectedThread] = useState(null); const mailboxQuery = useMailboxesList({ @@ -103,11 +108,18 @@ export const MailboxProvider = ({ children }: PropsWithChildren) => { }); const previousUnreadMessagesCount = usePrevious(selectedMailbox?.count_unread_messages || 0); + const threadQueryKey = useMemo(() => { + const queryKey = ['threads', selectedMailbox?.id]; + if (searchParams.get('search')) { + return [...queryKey, 'search']; + } + return [...queryKey, searchParams.toString()]; + }, [selectedMailbox?.id, searchParams]); const threadsQuery = useThreadsListInfinite(undefined, { query: { enabled: !!selectedMailbox, initialPageParam: 1, - queryKey: ['threads', selectedMailbox?.id, searchParams.toString()], + queryKey: threadQueryKey, getNextPageParam: (lastPage, pages) => { return pages.length + 1; }, @@ -157,6 +169,11 @@ export const MailboxProvider = ({ children }: PropsWithChildren) => { await queryClient.invalidateQueries({ queryKey: ['messages', selectedThread.id] }); } } + const resetSearchQueryDebounced = useDebounceCallback(() => { + queryClient.resetQueries( + { predicate: ({ queryKey}) => queryKey.includes('search') }, + ); + }, 500); const invalidateThreadsStats = async () => { await queryClient.invalidateQueries({ queryKey: ['threads', 'stats', selectedMailbox?.id] }); @@ -167,7 +184,7 @@ export const MailboxProvider = ({ children }: PropsWithChildren) => { */ const unselectThread = () => { setSelectedThread(null); - if (router.query.threadId) { + if (selectedMailbox && router.query.threadId) { router.push(`/mailbox/${selectedMailbox!.id}?${searchParams}`); } } @@ -270,6 +287,13 @@ export const MailboxProvider = ({ children }: PropsWithChildren) => { } }, [selectedMailbox?.count_unread_messages]); + useEffect(() => { + if (searchParams.get('search') !== previousSearchParams?.get('search')) { + resetSearchQueryDebounced(); + } + unselectThread(); + }, [hasSearchParamsChanged]) + return {children} }; diff --git a/src/frontend/src/pages/_index.scss b/src/frontend/src/pages/_index.scss index 7f60f10f..be7272a9 100644 --- a/src/frontend/src/pages/_index.scss +++ b/src/frontend/src/pages/_index.scss @@ -4,8 +4,6 @@ $tablet: map.get($themes, "default", "theme", "breakpoints", "tablet"); .app__home { - --header-height: 52px; - .c__header { height: var(--header-height); min-height: var(--header-height); @@ -31,8 +29,6 @@ $tablet: map.get($themes, "default", "theme", "breakpoints", "tablet"); } @media (max-width: $tablet) { - --header-height: 52px; - .c__header { .drive__header__left { img:first-child { diff --git a/src/frontend/src/styles/globals.scss b/src/frontend/src/styles/globals.scss index c683adea..347f6eb3 100644 --- a/src/frontend/src/styles/globals.scss +++ b/src/frontend/src/styles/globals.scss @@ -1,5 +1,12 @@ :root { font-size: 14px; + --header-height: 52px; +} + +@media screen and (max-width: 1024px) { + :root { + --header-height: 104px; + } } body { @@ -108,4 +115,8 @@ body { .m-base { margin: var(--c--theme--spacings--base); -} \ No newline at end of file +} + +.text-nowrap { + white-space: nowrap; +} diff --git a/src/frontend/src/styles/main.scss b/src/frontend/src/styles/main.scss index 0aeb9da0..2383e34c 100644 --- a/src/frontend/src/styles/main.scss +++ b/src/frontend/src/styles/main.scss @@ -5,10 +5,14 @@ @use "./cunningham-tokens.css"; @use "./globals.scss"; +// UI Kit components fork +@use "./../features/layouts/components/main/right-panel"; +@use "./../features/layouts/components/main/header"; +@use "./../features/layouts/components/main/layout"; // Application Components @use "./../features/layouts/components/global/global-layout.scss"; @use "./../features/layouts/components/main"; -@use "./../features/layouts/components/header"; +// @use "./../features/layouts/components/header"; @use "./../features/ui/components/users"; @use "./../features/ui/components/breadcrumbs"; @use "./../features/ui/components/toaster"; @@ -26,7 +30,7 @@ @use "./../features/layouts/components/thread-view/components/message-reply-form"; @use "./../features/forms/components/message-form"; @use "./../features/forms/components/message-editor"; - +@use "./../features/forms/components/search-input"; // Application views @use "./../pages"; @use "./../pages/mailbox";