🎨(front) global layout modification (#21)
delete unused languages + modify scroll down + change source item + attachements
This commit is contained in:
+2
-1
@@ -8,10 +8,11 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- ✨(front) global layout UI
|
||||
- 🎨(front) global layout modification
|
||||
|
||||
### Changed
|
||||
|
||||
- ✨(front) global layout UI
|
||||
- ♻️(chat) rewrite backend using Pydantic AI SDK #4
|
||||
- 🗃️(chat) enforce messages stored JSON format #6
|
||||
- 🐛(chat) UI messages must have a unique identifier #6
|
||||
|
||||
@@ -134,9 +134,9 @@ class Base(Configuration):
|
||||
(
|
||||
("en-us", "English"),
|
||||
("fr-fr", "Français"),
|
||||
("de-de", "Deutsch"),
|
||||
("nl-nl", "Nederlands"),
|
||||
("es-es", "Español"),
|
||||
# ("de-de", "Deutsch"),
|
||||
# ("nl-nl", "Nederlands"),
|
||||
# ("es-es", "Español"),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -117,9 +117,9 @@ class Migration(migrations.Migration):
|
||||
choices=[
|
||||
("en-us", "English"),
|
||||
("fr-fr", "Français"),
|
||||
("de-de", "Deutsch"),
|
||||
("nl-nl", "Nederlands"),
|
||||
("es-es", "Español"),
|
||||
# ("de-de", "Deutsch"),
|
||||
# ("nl-nl", "Nederlands"),
|
||||
# ("es-es", "Español"),
|
||||
],
|
||||
default=None,
|
||||
help_text="The language in which the user wants to see the interface.",
|
||||
|
||||
@@ -51,9 +51,9 @@ def test_api_config(is_authenticated):
|
||||
"LANGUAGES": [
|
||||
["en-us", "English"],
|
||||
["fr-fr", "Français"],
|
||||
["de-de", "Deutsch"],
|
||||
["nl-nl", "Nederlands"],
|
||||
["es-es", "Español"],
|
||||
# ["de-de", "Deutsch"],
|
||||
# ["nl-nl", "Nederlands"],
|
||||
# ["es-es", "Español"],
|
||||
],
|
||||
"LANGUAGE_CODE": "en-us",
|
||||
"MEDIA_BASE_URL": "http://testserver/",
|
||||
|
||||
@@ -33,6 +33,7 @@ export const tokens = {
|
||||
'greyscale-700': '#3A3A3A',
|
||||
'greyscale-800': '#2A2A2A',
|
||||
'greyscale-900': '#242424',
|
||||
'greyscale-050': '#EEF1F4',
|
||||
'greyscale-000': '#fff',
|
||||
'primary-100': '#ECECFE',
|
||||
'primary-200': '#E3E3FD',
|
||||
@@ -77,7 +78,6 @@ export const tokens = {
|
||||
'primary-050': '#F5F5FE',
|
||||
'primary-150': '#F4F4FD',
|
||||
'greyscale-text': '#303C4B',
|
||||
'greyscale-050': '#F6F6F6',
|
||||
'greyscale-250': '#ddd',
|
||||
'greyscale-350': '#ddd',
|
||||
'greyscale-750': '#353535',
|
||||
|
||||
@@ -17,9 +17,16 @@ const fetchAPIAdapter = (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
|
||||
// Add force_web_search parameter if it's globally enabled
|
||||
if ((window as { globalForceWebSearch?: boolean }).globalForceWebSearch) {
|
||||
const urlObj = new URL(url);
|
||||
urlObj.searchParams.set('force_web_search', 'true');
|
||||
url = urlObj.toString();
|
||||
// For relative URLs, just append the parameter
|
||||
if (url.startsWith('http')) {
|
||||
const urlObj = new URL(url);
|
||||
urlObj.searchParams.set('force_web_search', 'true');
|
||||
url = urlObj.toString();
|
||||
} else {
|
||||
// For relative URLs, append the parameter manually
|
||||
const separator = url.includes('?') ? '&' : '?';
|
||||
url = `${url}${separator}force_web_search=true`;
|
||||
}
|
||||
}
|
||||
|
||||
return fetchAPI(url, init);
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { Box, BoxButton, Icon, Text } from '@/components';
|
||||
|
||||
// Define Attachment type locally (mirroring backend structure)
|
||||
export interface Attachment {
|
||||
name?: string;
|
||||
contentType?: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface AttachmentListProps {
|
||||
attachments: Attachment[];
|
||||
onRemove?: (index: number) => void;
|
||||
isReadOnly?: boolean;
|
||||
}
|
||||
|
||||
export const AttachmentList = ({
|
||||
attachments,
|
||||
onRemove,
|
||||
isReadOnly = false,
|
||||
}: AttachmentListProps) => {
|
||||
if (!attachments || attachments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
$direction="row"
|
||||
$gap="0.5rem"
|
||||
$width="100%"
|
||||
$css={`
|
||||
overflow-x: scroll;
|
||||
`}
|
||||
>
|
||||
{attachments.map((attachment, idx) => {
|
||||
const { name } = attachment;
|
||||
const removeAttachment = () => {
|
||||
if (onRemove) {
|
||||
onRemove(idx);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={(name || 'attachment') + idx}
|
||||
$direction="column"
|
||||
$align="center"
|
||||
>
|
||||
<Box
|
||||
$background="var(--c--theme--colors--greyscale-050)"
|
||||
$minWidth="200px"
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$padding="xs"
|
||||
$css={`
|
||||
border-radius: 4px;
|
||||
`}
|
||||
>
|
||||
{/* Extension du fichier */}
|
||||
<Box
|
||||
$background="var(--c--theme--colors--greyscale-200)"
|
||||
$width="24px"
|
||||
$height="24px"
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$margin={{ right: 'xs' }}
|
||||
$justify="center"
|
||||
$css={`
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
`}
|
||||
>
|
||||
<Text
|
||||
$color="var(--c--theme--colors--greyscale-700)"
|
||||
$weight="500"
|
||||
$css={`
|
||||
font-size: 8px;
|
||||
`}
|
||||
>
|
||||
{name?.split('.').pop()?.toUpperCase() || 'FILE'}
|
||||
</Text>
|
||||
</Box>
|
||||
<Text
|
||||
$size="sm"
|
||||
$color="var(--c--theme--colors--greyscale-850)"
|
||||
$css={`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: initial;
|
||||
display: -webkit-box;
|
||||
line-clamp: 1;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
flex: 1;
|
||||
`}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
{!isReadOnly && onRemove && (
|
||||
<BoxButton
|
||||
aria-label="Remove attachment"
|
||||
onClick={removeAttachment}
|
||||
>
|
||||
<Icon iconName="close" $theme="greyscale" $size="18px" />
|
||||
</BoxButton>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -19,6 +19,7 @@ import { Box, Icon, Loader, Text } from '@/components';
|
||||
import { useChat } from '@/features/chat/api/useChat';
|
||||
import { getConversation } from '@/features/chat/api/useConversation';
|
||||
import { useCreateChatConversation } from '@/features/chat/api/useCreateConversation';
|
||||
import { AttachmentList } from '@/features/chat/components/AttachmentList';
|
||||
import { InputChat } from '@/features/chat/components/InputChat';
|
||||
import { SourceItemList } from '@/features/chat/components/SourceItemList';
|
||||
import { ToolInvocationItem } from '@/features/chat/components/ToolInvocationItem';
|
||||
@@ -42,7 +43,7 @@ export const Chat = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const copyToClipboard = useClipboard();
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const { isMobile } = useResponsiveStore();
|
||||
|
||||
const streamProtocol = 'data'; // or 'text'
|
||||
const [forceWebSearch, setForceWebSearch] = useState(false);
|
||||
@@ -374,14 +375,7 @@ export const Chat = ({
|
||||
flex-basis: auto;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
animation: fade-in 0.3s ease-out forwards;
|
||||
`}
|
||||
>
|
||||
<Box
|
||||
@@ -396,8 +390,8 @@ export const Chat = ({
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
margin-bottom: 0;
|
||||
height: ${messages.length > 1 ? 'calc(100vh - 62px)' : '0'};
|
||||
max-height: ${messages.length > 1 ? 'calc(100vh - 62px)' : '0'};
|
||||
height: ${messages.length > 0 ? 'calc(100vh - 62px)' : '0'};
|
||||
max-height: ${messages.length > 0 ? 'calc(100vh - 62px)' : '0'};
|
||||
`}
|
||||
>
|
||||
{messages.length > 0 && (
|
||||
@@ -407,7 +401,7 @@ export const Chat = ({
|
||||
key={message.id}
|
||||
data-message-id={message.id}
|
||||
$css={`
|
||||
display: flex;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
padding-left: 12px;
|
||||
@@ -419,23 +413,45 @@ export const Chat = ({
|
||||
<Box
|
||||
$gap="2"
|
||||
$radius="8px"
|
||||
$maxWidth="100%"
|
||||
$padding={`${message.role === 'user' ? '12px' : '0'}`}
|
||||
$margin={{ vertical: 'base' }}
|
||||
$background={`${message.role === 'user' ? '#EEF1F4' : 'white'}`}
|
||||
>
|
||||
{/* Message content */}
|
||||
{message.content && (
|
||||
<Markdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeKatex]}
|
||||
components={{
|
||||
// Custom components for Markdown rendering
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
p: ({ node, ...props }) => <Text {...props} />,
|
||||
}}
|
||||
<Box
|
||||
$css={`
|
||||
opacity: 0;
|
||||
animation: fade-in 0.3s ease-in-out forwards;
|
||||
`}
|
||||
>
|
||||
{message.content}
|
||||
</Markdown>
|
||||
<Markdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeKatex]}
|
||||
components={{
|
||||
// Custom components for Markdown rendering
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
p: ({ node, ...props }) => <Text {...props} />,
|
||||
}}
|
||||
>
|
||||
{message.content}
|
||||
</Markdown>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Attachments section */}
|
||||
{message.experimental_attachments &&
|
||||
message.experimental_attachments.length > 0 && (
|
||||
<Box>
|
||||
<AttachmentList
|
||||
attachments={message.experimental_attachments}
|
||||
isReadOnly={true}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Reasoning and tool invocations */}
|
||||
<Box $direction="column" $gap="2">
|
||||
{message.parts
|
||||
?.filter(
|
||||
@@ -461,25 +477,6 @@ export const Chat = ({
|
||||
/>
|
||||
) : null,
|
||||
)}
|
||||
{/* Show attachments if present */}
|
||||
{message.experimental_attachments?.map(
|
||||
(attachment: Attachment, index: number) =>
|
||||
attachment.contentType?.includes('text/') ||
|
||||
attachment.contentType?.includes('image/') ? (
|
||||
<div
|
||||
key={`${message.id}-${index}`}
|
||||
style={{
|
||||
display: 'block',
|
||||
width: 'auto',
|
||||
fontSize: 12,
|
||||
borderRadius: 8,
|
||||
color: '#888',
|
||||
}}
|
||||
>
|
||||
{attachment.name}
|
||||
</div>
|
||||
) : null,
|
||||
)}
|
||||
</Box>
|
||||
{message.role !== 'user' && (
|
||||
<Box
|
||||
@@ -522,7 +519,7 @@ export const Chat = ({
|
||||
$variation="600"
|
||||
$size="16px"
|
||||
/>
|
||||
{isDesktop && (
|
||||
{!isMobile && (
|
||||
<Text $color="#626A80" $weight="500">
|
||||
{t('Copy')}
|
||||
</Text>
|
||||
@@ -564,7 +561,7 @@ export const Chat = ({
|
||||
$variation="600"
|
||||
$size="16px"
|
||||
/>
|
||||
{isDesktop && (
|
||||
{!isMobile && (
|
||||
<Text $color="#626A80" $weight="500">
|
||||
{t('Show sources')}
|
||||
</Text>
|
||||
@@ -607,6 +604,7 @@ export const Chat = ({
|
||||
position: relative;
|
||||
bottom: 20px;
|
||||
margin: auto;
|
||||
z-index: 1000;
|
||||
`}
|
||||
$gap="6px"
|
||||
$height="auto"
|
||||
|
||||
@@ -2,9 +2,10 @@ import { Button } from '@openfun/cunningham-react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, BoxButton, Icon, Text } from '@/components';
|
||||
import { Box, Icon, Text } from '@/components';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { AttachmentList } from './AttachmentList';
|
||||
import { ScrollDown } from './ScrollDown';
|
||||
import { SendButton } from './SendButton';
|
||||
|
||||
@@ -40,7 +41,7 @@ export const InputChat = ({
|
||||
const { t } = useTranslation();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [isDragActive, setIsDragActive] = useState(false);
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const { isDesktop, isMobile } = useResponsiveStore();
|
||||
const [currentSuggestionIndex, setCurrentSuggestionIndex] = useState(0);
|
||||
|
||||
const suggestions = [
|
||||
@@ -69,55 +70,28 @@ export const InputChat = ({
|
||||
width: 100%;
|
||||
padding: ${isDesktop ? '0' : '0 10px'};
|
||||
max-width: 750px;
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes fadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes slideOutToBottom {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
}
|
||||
`}
|
||||
>
|
||||
{/* Bouton de scroll vers le bas */}
|
||||
{messagesLength > 1 && containerRef && (
|
||||
<Box
|
||||
$css={`
|
||||
{messagesLength > 1 &&
|
||||
status !== 'streaming' &&
|
||||
containerRef &&
|
||||
onScrollToBottom && (
|
||||
<Box
|
||||
$css={`
|
||||
position: relative;
|
||||
bottom: 0;
|
||||
height: 0;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
max-width: 750px;
|
||||
opacity: ${onScrollToBottom ? '1' : '0'};
|
||||
transition: opacity 0.4s;
|
||||
`}
|
||||
>
|
||||
<ScrollDown
|
||||
onClick={onScrollToBottom || (() => {})}
|
||||
containerRef={containerRef}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
`}
|
||||
>
|
||||
<ScrollDown
|
||||
onClick={onScrollToBottom}
|
||||
containerRef={containerRef}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{/* Message de bienvenue */}
|
||||
{messagesLength === 0 && (
|
||||
<Box
|
||||
@@ -127,7 +101,7 @@ export const InputChat = ({
|
||||
$margin={{ horizontal: 'base', bottom: 'md', top: '0' }}
|
||||
$css={`
|
||||
opacity: 0;
|
||||
animation: fadeIn 0.2s cubic-bezier(1,0,0,1) 0.2s both;
|
||||
animation: fade-in 0.2s cubic-bezier(1,0,0,1) 0.2s both;
|
||||
`}
|
||||
>
|
||||
<Text as="h2" $size="xl" $weight="600" $margin={{ all: '0' }}>
|
||||
@@ -180,9 +154,9 @@ export const InputChat = ({
|
||||
messagesLength === 0
|
||||
? `
|
||||
opacity: 0;
|
||||
animation: fadeIn 0.4s cubic-bezier(1,0,0,1) 0s both;
|
||||
animation: fade-in 0.4s cubic-bezier(1,0,0,1) 0s both;
|
||||
`
|
||||
: 'animation: fadeIn 0.4s cubic-bezier(1,0,0,1) 0.4s both;'
|
||||
: 'animation: fade-in 0.4s cubic-bezier(1,0,0,1) 0.4s both;'
|
||||
}
|
||||
>
|
||||
<Box
|
||||
@@ -307,91 +281,26 @@ export const InputChat = ({
|
||||
{/*Aperçu des fichiers*/}
|
||||
{files && files.length > 0 && (
|
||||
<Box
|
||||
$direction="row"
|
||||
$gap="0.5rem"
|
||||
$width="100%"
|
||||
$css={`
|
||||
overflow-x: scroll;
|
||||
`}
|
||||
$margin={{ horizontal: '0', bottom: 'xs', top: 'xs' }}
|
||||
$padding={{ horizontal: 'base' }}
|
||||
>
|
||||
{Array.from(files).map((file, idx) => {
|
||||
const { type: _type, name } = file;
|
||||
const removeFile = () => {
|
||||
<AttachmentList
|
||||
attachments={Array.from(files).map((file) => ({
|
||||
name: file.name,
|
||||
contentType: file.type,
|
||||
url: URL.createObjectURL(file),
|
||||
}))}
|
||||
onRemove={(index) => {
|
||||
const dt = new DataTransfer();
|
||||
Array.from(files).forEach((f, i) => {
|
||||
if (i !== idx) {
|
||||
if (i !== index) {
|
||||
dt.items.add(f);
|
||||
}
|
||||
});
|
||||
setFiles(dt.files.length > 0 ? dt.files : null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={name + idx}
|
||||
$direction="column"
|
||||
$align="center"
|
||||
$gap="xs"
|
||||
>
|
||||
{/*{type.startsWith('image/') ? (
|
||||
<Image
|
||||
style={{ width: 96, borderRadius: 8 }}
|
||||
src={URL.createObjectURL(file)}
|
||||
alt={name}
|
||||
width={96}
|
||||
height={96}
|
||||
/>
|
||||
) : (
|
||||
<Box
|
||||
$background="var(--c--theme--colors--greyscale-100)"
|
||||
$width="64px"
|
||||
$height="80px"
|
||||
$radius="md"
|
||||
/>
|
||||
)}*/}
|
||||
<Box
|
||||
$background="var(--c--theme--colors--greyscale-050)"
|
||||
$width="200px"
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$padding="xs"
|
||||
$css={`
|
||||
border: 1px solid var(--c--theme--colors--greyscale-200);
|
||||
border-radius: 8px;
|
||||
|
||||
`}
|
||||
>
|
||||
<Text
|
||||
$size="sm"
|
||||
$color="var(--c--theme--colors--greyscale-850)"
|
||||
$css={`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: initial;
|
||||
display: -webkit-box;
|
||||
line-clamp: 1;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
`}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
<BoxButton
|
||||
aria-label="Remove file"
|
||||
onClick={removeFile}
|
||||
>
|
||||
<Icon
|
||||
iconName="close"
|
||||
$theme="greyscale"
|
||||
$size="18px"
|
||||
/>
|
||||
</BoxButton>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
}}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
@@ -416,19 +325,31 @@ export const InputChat = ({
|
||||
/>
|
||||
}
|
||||
>
|
||||
{isDesktop && <Text $weight="500">{t('Attach file')}</Text>}
|
||||
{!isMobile && <Text $weight="500">{t('Attach file')}</Text>}
|
||||
</Button>
|
||||
{onToggleWebSearch && (
|
||||
<Box
|
||||
$css={
|
||||
forceWebSearch
|
||||
? `
|
||||
$margin={{ left: '4px' }}
|
||||
$css={`
|
||||
${
|
||||
isMobile
|
||||
? `
|
||||
.research-web-button {
|
||||
padding-right: 8px !important;
|
||||
}
|
||||
`
|
||||
: ''
|
||||
}
|
||||
${
|
||||
forceWebSearch
|
||||
? `
|
||||
.c__button {
|
||||
background-color: var(--c--theme--colors--primary-100) !important;
|
||||
}
|
||||
`
|
||||
: ''
|
||||
}
|
||||
: ''
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
@@ -436,6 +357,7 @@ export const InputChat = ({
|
||||
onClick={onToggleWebSearch}
|
||||
aria-label={t('Research on the web')}
|
||||
color="tertiary-text"
|
||||
className="research-web-button"
|
||||
icon={
|
||||
<Icon
|
||||
iconName="language"
|
||||
@@ -445,14 +367,50 @@ export const InputChat = ({
|
||||
/>
|
||||
}
|
||||
>
|
||||
{isDesktop && (
|
||||
{!isMobile && (
|
||||
<Text
|
||||
$color={forceWebSearch ? 'primary' : 'greyscale'}
|
||||
$theme={forceWebSearch ? 'primary' : 'greyscale'}
|
||||
$weight="500"
|
||||
>
|
||||
{t('Research on the web')}
|
||||
</Text>
|
||||
)}
|
||||
{isMobile && forceWebSearch && (
|
||||
<Box
|
||||
$direction="row"
|
||||
$align="space-between"
|
||||
$gap="xs"
|
||||
$css={`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
`}
|
||||
>
|
||||
<Text
|
||||
$theme="primary"
|
||||
$weight="500"
|
||||
$css={`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`}
|
||||
>
|
||||
{t('Web')}
|
||||
</Text>
|
||||
<Icon
|
||||
iconName="close"
|
||||
$variation="800"
|
||||
$theme="primary"
|
||||
$size="md"
|
||||
$css={`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
padding-left: 4px;
|
||||
`}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -42,16 +42,7 @@ export const ScrollDown: React.FC<ScrollDownProps> = ({
|
||||
z-index: 1000;
|
||||
opacity: ${isVisible ? '1' : '0'};
|
||||
transition: opacity 0.3s ease;
|
||||
animation: fadeIn 0.3s ease;
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
pointer-events: ${isVisible ? 'auto' : 'none'};
|
||||
`}
|
||||
>
|
||||
<Button
|
||||
|
||||
@@ -24,18 +24,7 @@ export const SourceItemList: React.FC<SourceItemListProps> = ({ parts }) => {
|
||||
margin-top: 0.5rem;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
animation: slideInFade 0.3s ease-out forwards;
|
||||
|
||||
@keyframes slideInFade {
|
||||
from {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
animation: fade-in 0.3s ease-out forwards;
|
||||
`}
|
||||
>
|
||||
{parts.map((part) => (
|
||||
|
||||
@@ -4,7 +4,6 @@ import { css } from 'styled-components';
|
||||
import { Box } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Header } from '@/features/header';
|
||||
import { HEADER_HEIGHT } from '@/features/header/conf';
|
||||
import { LeftPanel } from '@/features/left-panel';
|
||||
import { useLeftPanelStore } from '@/features/left-panel/stores';
|
||||
import { MAIN_LAYOUT_ID } from '@/layouts/conf';
|
||||
@@ -22,6 +21,8 @@ export function MainLayout({
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
const { togglePanel: _togglePanel, isPanelOpen } = useLeftPanelStore();
|
||||
|
||||
const HEADER_HEIGHT = `${isDesktop ? '65' : '52'}`;
|
||||
|
||||
return (
|
||||
<Box className="--docs--main-layout">
|
||||
<Box
|
||||
|
||||
@@ -98,3 +98,13 @@ ul a {
|
||||
ul a:hover {
|
||||
color: var(--c--theme--colors--primary-800);
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ export const CONFIG = {
|
||||
LANGUAGES: [
|
||||
['en-us', 'English'],
|
||||
['fr-fr', 'Français'],
|
||||
['de-de', 'Deutsch'],
|
||||
['nl-nl', 'Nederlands'],
|
||||
['es-es', 'Español'],
|
||||
// ['de-de', 'Deutsch'],
|
||||
// ['nl-nl', 'Nederlands'],
|
||||
// ['es-es', 'Español'],
|
||||
],
|
||||
LANGUAGE_CODE: 'en-us',
|
||||
POSTHOG_KEY: {},
|
||||
|
||||
@@ -80,10 +80,10 @@ export const TestLanguage = {
|
||||
label: 'Français',
|
||||
expectedLocale: ['fr-fr'],
|
||||
},
|
||||
German: {
|
||||
label: 'Deutsch',
|
||||
expectedLocale: ['de-de'],
|
||||
},
|
||||
// German: {
|
||||
// label: 'Deutsch',
|
||||
// expectedLocale: ['de-de'],
|
||||
// },
|
||||
} as const;
|
||||
|
||||
type TestLanguageKey = keyof typeof TestLanguage;
|
||||
|
||||
Reference in New Issue
Block a user