Compare commits

...

17 Commits

Author SHA1 Message Date
Nathan Panchout 2b2a579d9f wip 2025-03-24 17:15:14 +01:00
Nathan Panchout 615ab564cc fixup! (frontend) refactor and theme token update 2025-03-24 09:00:59 +01:00
Nathan Panchout aadd6d9ec3 fixup! (frontend) added subpage management and document tree features 2025-03-24 08:55:38 +01:00
Nathan Panchout 0a6502a77d (frontend) added subpage management and document tree features
New components were created to manage subpages in the document tree,
including the ability to add, reorder, and view subpages. Tests were
added to verify the functionality of these features. Additionally, API
changes were made to manage the creation and retrieval of document
children.
2025-03-24 08:55:36 +01:00
Nathan Panchout a32ee20249 (Frontend) Added drag-and-drop functionality for document management
Added a new feature for moving documents within the user interface via
drag-and-drop. This includes the creation of Draggable and Droppable
components, as well as tests to verify document creation and movement
behavior. Changes have also been made to document types to include user
roles and child management capabilities.
2025-03-24 08:54:03 +01:00
Nathan Panchout 48db42f385 (frontend) refactor and theme token update
The configuration file has been simplified by importing configurations
from @gouvfr-lasuite/ui-kit . Colors and components have been updated to
reflect the new values. Additionally, adjustments have been made to
global styles, including the addition of styles for Material icons. Form
components have also been modified to incorporate the new style
properties.
2025-03-24 08:54:01 +01:00
Nathan Panchout 05b14b2948 (frontend) updated dependencies and added new packages
Added several new dependencies to the `package.json` file, including
`@dnd-kit/core`, `@dnd-kit/modifiers`, `@fontsource/material-icons`, and
`@gouvfr-lasuite/ui-kit`.
2025-03-24 08:53:04 +01:00
Nathan Panchout 12f4a72f5e 🐛(back) keep info if document has deleted children
With the soft delete feature, relying on the is_leaf method from the
treebeard is not accurate anymore. To determine if a node is a leaf, it
checks if the number of numchild is equal to 0. But a node can have soft
deleted children, then numchild is equal to 0, but it is not a leaf
because if we want to add a child we have to look for the last child to
compute a correct path. Otherwise we will have an error saying that the
path already exists.
2025-03-24 08:51:42 +01:00
Bastien Guerry ef7cc67387 📄(legal) Require contributors to sign a DCO
Contributors are required to sign off their commits: this confirms
that they have read and accepted https://developercertificate.org.
2025-03-23 09:57:35 +01:00
Sylvain Zimmer a8529e434a 🐛(media) fix compatibility with Scaleway Object Storage
Some providers with S3-compatible APIs have slightly different
implementations. In this case, Scaleway didn't accept version_id=""
and has a different version ID scheme. This was tested successfully
and should remain compatible with any other provider.
2025-03-22 18:00:43 +01:00
Manuel Raynaud f8203a1766 🚨(back) lint code with ruff 0.11.2
New Ruff rule (C420) detects code that should be linted. We apply this
new rule on our code.
2025-03-22 10:28:48 +01:00
renovate[bot] ce8b98e256 ⬆️(dependencies) update python dependencies 2025-03-22 10:28:48 +01:00
Anthony LC 4243519eee 🔥(frontend) remove Marianne font
Marianne font is now part of the UI kit.
We can remove it from the project.
2025-03-21 17:49:06 +01:00
Nathan Panchout 1abf529891 (frontend) refactor and theme token update
The configuration file has been simplified by importing configurations
from @gouvfr-lasuite/ui-kit . Colors and components have been updated to
reflect the new values. Additionally, adjustments have been made to
global styles, including the addition of styles for Material icons. Form
components have also been modified to incorporate the new style
properties.
2025-03-21 17:49:06 +01:00
Nathan Panchout 69ca4af539 (frontend) updated dependencies and added new packages
Added several new dependencies to the `package.json` file, including
`@dnd-kit/core`, `@dnd-kit/modifiers`, `@fontsource/material-icons`, and
`@gouvfr-lasuite/ui-kit`.
2025-03-21 17:49:06 +01:00
Anthony LC 14b2adedfb 🔖(minor) release 2.6.0
Added:
- 📝(doc) add publiccode.yml

Changed
- 🚸(frontend) ctrl+k modal not when editor is focused

Fixed:
- 🐛(back) allow only images to be used with
  the cors-proxy
- 🐛(backend) stop returning inactive users
  on the list endpoint
- 🔒️(backend) require at least 5 characters
  to search for users
- 🔒️(back) throttle user list endpoint
- 🔒️(back) remove pagination and limit to
   5 for user list endpoint
2025-03-21 17:07:26 +01:00
Anthony LC a7edb382a7 🩹(frontent) change selector to block cmd+k
Multiple ctrl+k could open the search modal, we
change the selector, now if the toolbar is displayed
we don't open the search modal.
2025-03-21 17:07:26 +01:00
93 changed files with 2985 additions and 2315 deletions
+13 -2
View File
@@ -10,7 +10,17 @@ and this project adheres to
## Added
- 📝(doc) add publiccode.yml
- 📄(legal) Require contributors to sign a DCO #779
## Changed
- ♻️(frontend) Integrate UI kit #783
## [2.6.0] - 2025-03-21
## Added
- 📝(doc) add publiccode.yml #770
## Changed
@@ -485,7 +495,8 @@ and this project adheres to
- ✨(frontend) Coming Soon page (#67)
- 🚀 Impress, project to manage your documents easily and collaboratively.
[unreleased]: https://github.com/numerique-gouv/impress/compare/v2.5.0...main
[unreleased]: https://github.com/numerique-gouv/impress/compare/v2.6.0...main
[v2.5.0]: https://github.com/numerique-gouv/impress/releases/v2.6.0
[v2.5.0]: https://github.com/numerique-gouv/impress/releases/v2.5.0
[v2.4.0]: https://github.com/numerique-gouv/impress/releases/v2.4.0
[v2.3.0]: https://github.com/numerique-gouv/impress/releases/v2.3.0
+2
View File
@@ -4,6 +4,8 @@ Thank you for taking the time to contribute! Please follow these guidelines to e
To get started with the project, please refer to the [README.md](https://github.com/suitenumerique/docs/blob/main/README.md) for detailed instructions.
Contributors are required to sign off their commits with `git commit --sign-off`: this confirms that they have read and accepted the [Developer's Certificate of Origin 1.1](https://developercertificate.org/).
Please also check out our [dev handbook](https://suitenumerique.gitbook.io/handbook) to learn our best practices.
## Help us with translations
-2
View File
@@ -93,7 +93,6 @@ build: cache ?= --no-cache
build: ## build the project containers
@$(MAKE) build-backend cache=$(cache)
@$(MAKE) build-yjs-provider cache=$(cache)
@$(MAKE) build-frontend cache=$(cache)
.PHONY: build
build-backend: cache ?=
@@ -128,7 +127,6 @@ run-backend: ## Start only the backend application and all needed services
run: ## start the wsgi (production) and development server
run:
@$(MAKE) run-backend
@$(COMPOSE) up --force-recreate -d frontend
.PHONY: run
status: ## an alias for "docker compose ps"
+1 -1
View File
@@ -61,4 +61,4 @@ COLLABORATION_SERVER_SECRET=my-secret
COLLABORATION_WS_URL=ws://localhost:8083/collaboration/ws/
# Frontend
FRONTEND_THEME=dsfr
FRONTEND_THEME=default
+1 -1
View File
@@ -943,7 +943,7 @@ class DocumentViewSet(
@drf.decorators.action(
detail=True,
methods=["get", "delete"],
url_path="versions/(?P<version_id>[0-9a-f-]{36})",
url_path="versions/(?P<version_id>[0-9a-z-]+)",
)
# pylint: disable=unused-argument
def versions_detail(self, request, pk, version_id, *args, **kwargs):
@@ -0,0 +1,17 @@
# Generated by Django 5.1.7 on 2025-03-14 14:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0019_alter_user_language_default_to_null"),
]
operations = [
migrations.AddField(
model_name="document",
name="has_deleted_children",
field=models.BooleanField(default=False),
),
]
+17 -5
View File
@@ -96,7 +96,7 @@ class LinkReachChoices(models.TextChoices):
"""
# If no ancestors, return all options
if not ancestors_links:
return {reach: LinkRoleChoices.values for reach in cls.values}
return dict.fromkeys(cls.values, LinkRoleChoices.values)
# Initialize result with all possible reaches and role options as sets
result = {reach: set(LinkRoleChoices.values) for reach in cls.values}
@@ -486,6 +486,7 @@ class Document(MP_Node, BaseModel):
)
deleted_at = models.DateTimeField(null=True, blank=True)
ancestors_deleted_at = models.DateTimeField(null=True, blank=True)
has_deleted_children = models.BooleanField(default=False)
_content = None
@@ -546,6 +547,12 @@ class Document(MP_Node, BaseModel):
content_file = ContentFile(bytes_content)
default_storage.save(file_key, content_file)
def is_leaf(self):
"""
:returns: True if the node is has no children
"""
return not self.has_deleted_children and self.numchild == 0
@property
def key_base(self):
"""Key base of the location where the document is stored in object storage."""
@@ -582,9 +589,13 @@ class Document(MP_Node, BaseModel):
def get_content_response(self, version_id=""):
"""Get the content in a specific version of the document"""
return default_storage.connection.meta.client.get_object(
Bucket=default_storage.bucket_name, Key=self.file_key, VersionId=version_id
)
params = {
"Bucket": default_storage.bucket_name,
"Key": self.file_key,
}
if version_id:
params["VersionId"] = version_id
return default_storage.connection.meta.client.get_object(**params)
def get_versions_slice(self, from_version_id="", min_datetime=None, page_size=None):
"""Get document versions from object storage with pagination and starting conditions"""
@@ -899,7 +910,8 @@ class Document(MP_Node, BaseModel):
if self.depth > 1:
self._meta.model.objects.filter(pk=self.get_parent().pk).update(
numchild=models.F("numchild") - 1
numchild=models.F("numchild") - 1,
has_deleted_children=True,
)
# Mark all descendants as soft deleted
@@ -1297,3 +1297,47 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
def test_models_documents_get_select_options(ancestors_links, select_options):
"""Validate that the "get_select_options" method operates as expected."""
assert models.LinkReachChoices.get_select_options(ancestors_links) == select_options
def test_models_documents_children_create_after_sibling_deletion():
"""
It should be possible to create a new child after all children have been deleted.
"""
root = factories.DocumentFactory()
assert root.numchild == 0
assert root.has_deleted_children is False
assert root.is_leaf() is True
child1 = factories.DocumentFactory(parent=root)
child2 = factories.DocumentFactory(parent=root)
root.refresh_from_db()
assert root.numchild == 2
assert root.has_deleted_children is False
assert root.is_leaf() is False
child1.soft_delete()
child2.soft_delete()
root.refresh_from_db()
assert root.numchild == 0
assert root.has_deleted_children is True
assert root.is_leaf() is False
factories.DocumentFactory(parent=root)
root.refresh_from_db()
assert root.numchild == 1
assert root.has_deleted_children is True
assert root.is_leaf() is False
def test_models_documents_has_deleted_children():
"""
A document should have its has_deleted_children attribute set to True if one of its children
has been solf deleted no matter if numchild is 0 or not.
"""
root = factories.DocumentFactory()
child = factories.DocumentFactory(parent=root)
assert root.has_deleted_children is False
child.soft_delete()
root.refresh_from_db()
assert root.has_deleted_children is True
+11 -11
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "impress"
version = "2.5.0"
version = "2.6.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -25,7 +25,7 @@ license = { file = "LICENSE" }
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"boto3==1.37.5",
"boto3==1.37.18",
"Brotli==1.1.0",
"celery[redis]==5.4.0",
"django-configurations==2.5.1",
@@ -48,12 +48,12 @@ dependencies = [
"jsonschema==4.23.0",
"markdown==3.7",
"nested-multipart-parser==1.5.0",
"openai==1.65.2",
"psycopg[binary]==3.2.5",
"openai==1.68.2",
"psycopg[binary]==3.2.6",
"PyJWT==2.10.1",
"python-magic==0.4.27",
"requests==2.32.3",
"sentry-sdk==2.22.0",
"sentry-sdk==2.24.0",
"url-normalize==1.4.3",
"whitenoise==6.9.0",
"mozilla-django-oidc==4.0.1",
@@ -72,18 +72,18 @@ dev = [
"drf-spectacular-sidecar==2025.3.1",
"freezegun==1.5.1",
"ipdb==0.13.13",
"ipython==9.0.1",
"pyfakefs==5.7.4",
"ipython==9.0.2",
"pyfakefs==5.8.0",
"pylint-django==2.6.1",
"pylint==3.3.4",
"pylint==3.3.6",
"pytest-cov==6.0.0",
"pytest-django==4.10.0",
"pytest==8.3.5",
"pytest-icdiff==0.9",
"pytest-xdist==3.6.1",
"responses==0.25.6",
"ruff==0.9.9",
"types-requests==2.32.0.20250301",
"responses==0.25.7",
"ruff==0.11.2",
"types-requests==2.32.0.20250306",
]
[tool.setuptools]
@@ -200,6 +200,22 @@ export const mockedDocument = async (page: Page, json: object) => {
});
};
export const mockedListDocs = async (page: Page, data: object[] = []) => {
await page.route('**/documents/**/', async (route) => {
const request = route.request();
if (request.method().includes('GET') && request.url().includes('page=')) {
await route.fulfill({
json: {
count: data.length,
next: null,
previous: null,
results: data,
},
});
}
});
};
export const mockedInvitations = async (page: Page, json?: object) => {
await page.route('**/invitations/**/', async (route) => {
const request = route.request();
@@ -8,7 +8,7 @@ const config = {
CRISP_WEBSITE_ID: null,
COLLABORATION_WS_URL: 'ws://localhost:8083/collaboration/ws/',
ENVIRONMENT: 'development',
FRONTEND_THEME: 'dsfr',
FRONTEND_THEME: 'default',
MEDIA_BASE_URL: 'http://localhost:8083',
LANGUAGES: [
['en-us', 'English'],
@@ -159,7 +159,7 @@ test.describe('Config: Not loggued', () => {
expect(response.ok()).toBeTruthy();
const jsonResponse = await response.json();
expect(jsonResponse.FRONTEND_THEME).toStrictEqual('dsfr');
expect(jsonResponse.FRONTEND_THEME).toStrictEqual('default');
const footer = page.locator('footer').first();
// alt 'Gouvernement Logo' comes from the theme
@@ -0,0 +1,309 @@
import { expect, test } from '@playwright/test';
import { createDoc, mockedListDocs } from './common';
test.describe('Doc grid dnd', () => {
test('it creates a doc', async ({ page, browserName }) => {
await page.goto('/');
const header = page.locator('header').first();
await createDoc(page, 'Draggable doc', browserName, 1);
await header.locator('h2').getByText('Docs').click();
await createDoc(page, 'Droppable doc', browserName, 1);
await header.locator('h2').getByText('Docs').click();
const response = await page.waitForResponse(
(response) =>
response.url().endsWith('documents/?page=1') &&
response.status() === 200,
);
const responseJson = await response.json();
const items = responseJson.results;
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid).toBeVisible();
await expect(page.getByTestId('grid-loader')).toBeHidden();
const draggableElement = page.getByTestId(`draggable-doc-${items[1].id}`);
const dropZone = page.getByTestId(`droppable-doc-${items[0].id}`);
await expect(draggableElement).toBeVisible();
await expect(dropZone).toBeVisible();
// Obtenir les positions des éléments
const draggableBoundingBox = await draggableElement.boundingBox();
const dropZoneBoundingBox = await dropZone.boundingBox();
expect(draggableBoundingBox).toBeDefined();
expect(dropZoneBoundingBox).toBeDefined();
// eslint-disable-next-line playwright/no-conditional-in-test
if (!draggableBoundingBox || !dropZoneBoundingBox) {
throw new Error('Impossible de déterminer la position des éléments');
}
await page.mouse.move(
draggableBoundingBox.x + draggableBoundingBox.width / 2,
draggableBoundingBox.y + draggableBoundingBox.height / 2,
);
await page.mouse.down();
// Déplacer vers la zone cible
await page.mouse.move(
dropZoneBoundingBox.x + dropZoneBoundingBox.width / 2,
dropZoneBoundingBox.y + dropZoneBoundingBox.height / 2,
{ steps: 10 }, // Rendre le mouvement plus fluide
);
const dragOverlay = page.getByTestId('drag-doc-overlay');
await expect(dragOverlay).toBeVisible();
await expect(dragOverlay).toHaveText(items[1].title as string);
await page.mouse.up();
await expect(dragOverlay).toBeHidden();
});
test('it checks cant drop when we have not the minimum role', async ({
page,
}) => {
await mockedListDocs(page, data);
await page.goto('/');
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid).toBeVisible();
await expect(page.getByTestId('grid-loader')).toBeHidden();
const canDropAndDrag = page.getByTestId('droppable-doc-can-drop-and-drag');
const noDropAndNoDrag = page.getByTestId(
'droppable-doc-no-drop-and-no-drag',
);
await expect(canDropAndDrag).toBeVisible();
await expect(noDropAndNoDrag).toBeVisible();
const canDropAndDragBoundigBox = await canDropAndDrag.boundingBox();
const noDropAndNoDragBoundigBox = await noDropAndNoDrag.boundingBox();
if (!canDropAndDragBoundigBox || !noDropAndNoDragBoundigBox) {
throw new Error('Impossible de déterminer la position des éléments');
}
await page.mouse.move(
canDropAndDragBoundigBox.x + canDropAndDragBoundigBox.width / 2,
canDropAndDragBoundigBox.y + canDropAndDragBoundigBox.height / 2,
);
await page.mouse.down();
await page.mouse.move(
noDropAndNoDragBoundigBox.x + noDropAndNoDragBoundigBox.width / 2,
noDropAndNoDragBoundigBox.y + noDropAndNoDragBoundigBox.height / 2,
{ steps: 10 },
);
const dragOverlay = page.getByTestId('drag-doc-overlay');
await expect(dragOverlay).toBeVisible();
await expect(dragOverlay).toHaveText(
'You must be at least the editor of the target document',
);
await page.mouse.up();
});
test('it checks cant drag when we have not the minimum role', async ({
page,
}) => {
await mockedListDocs(page, data);
await page.goto('/');
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid).toBeVisible();
await expect(page.getByTestId('grid-loader')).toBeHidden();
const canDropAndDrag = page.getByTestId('droppable-doc-can-drop-and-drag');
const noDropAndNoDrag = page.getByTestId(
'droppable-doc-no-drop-and-no-drag',
);
await expect(canDropAndDrag).toBeVisible();
await expect(noDropAndNoDrag).toBeVisible();
const canDropAndDragBoundigBox = await canDropAndDrag.boundingBox();
const noDropAndNoDragBoundigBox = await noDropAndNoDrag.boundingBox();
if (!canDropAndDragBoundigBox || !noDropAndNoDragBoundigBox) {
throw new Error('Impossible de déterminer la position des éléments');
}
await page.mouse.move(
noDropAndNoDragBoundigBox.x + noDropAndNoDragBoundigBox.width / 2,
noDropAndNoDragBoundigBox.y + noDropAndNoDragBoundigBox.height / 2,
);
await page.mouse.down();
await page.mouse.move(
canDropAndDragBoundigBox.x + canDropAndDragBoundigBox.width / 2,
canDropAndDragBoundigBox.y + canDropAndDragBoundigBox.height / 2,
{ steps: 10 },
);
const dragOverlay = page.getByTestId('drag-doc-overlay');
await expect(dragOverlay).toBeVisible();
await expect(dragOverlay).toHaveText(
'You must be the owner to move the document',
);
await page.mouse.up();
});
});
const data = [
{
id: 'can-drop-and-drag',
abilities: {
accesses_manage: true,
accesses_view: true,
ai_transform: true,
ai_translate: true,
attachment_upload: true,
children_list: true,
children_create: true,
collaboration_auth: true,
descendants: true,
destroy: true,
favorite: true,
link_configuration: true,
invite_owner: true,
move: true,
partial_update: true,
restore: true,
retrieve: true,
media_auth: true,
link_select_options: {
restricted: ['reader', 'editor'],
authenticated: ['reader', 'editor'],
public: ['reader', 'editor'],
},
tree: true,
update: true,
versions_destroy: true,
versions_list: true,
versions_retrieve: true,
},
created_at: '2025-03-14T14:45:22.527221Z',
creator: 'bc6895e0-8f6d-4b00-827d-c143aa6b2ecb',
depth: 1,
excerpt: null,
is_favorite: false,
link_role: 'reader',
link_reach: 'restricted',
nb_accesses_ancestors: 1,
nb_accesses_direct: 1,
numchild: 5,
path: '000000o',
title: 'Can drop and drag',
updated_at: '2025-03-14T14:45:27.699542Z',
user_roles: ['owner'],
},
{
id: 'can-only-drop',
title: 'Can only drop',
abilities: {
accesses_manage: true,
accesses_view: true,
ai_transform: true,
ai_translate: true,
attachment_upload: true,
children_list: true,
children_create: true,
collaboration_auth: true,
descendants: true,
destroy: true,
favorite: true,
link_configuration: true,
invite_owner: true,
move: true,
partial_update: true,
restore: true,
retrieve: true,
media_auth: true,
link_select_options: {
restricted: ['reader', 'editor'],
authenticated: ['reader', 'editor'],
public: ['reader', 'editor'],
},
tree: true,
update: true,
versions_destroy: true,
versions_list: true,
versions_retrieve: true,
},
created_at: '2025-03-14T14:45:22.527221Z',
creator: 'bc6895e0-8f6d-4b00-827d-c143aa6b2ecb',
depth: 1,
excerpt: null,
is_favorite: false,
link_role: 'reader',
link_reach: 'restricted',
nb_accesses_ancestors: 1,
nb_accesses_direct: 1,
numchild: 5,
path: '000000o',
updated_at: '2025-03-14T14:45:27.699542Z',
user_roles: ['editor'],
},
{
id: 'no-drop-and-no-drag',
abilities: {
accesses_manage: false,
accesses_view: true,
ai_transform: false,
ai_translate: false,
attachment_upload: false,
children_list: true,
children_create: false,
collaboration_auth: true,
descendants: true,
destroy: false,
favorite: true,
link_configuration: false,
invite_owner: false,
move: false,
partial_update: false,
restore: false,
retrieve: true,
media_auth: true,
link_select_options: {
restricted: ['reader', 'editor'],
authenticated: ['reader', 'editor'],
public: ['reader', 'editor'],
},
tree: true,
update: false,
versions_destroy: false,
versions_list: true,
versions_retrieve: true,
},
created_at: '2025-03-14T14:44:16.032773Z',
creator: '9264f420-f018-4bd6-96ae-4788f41af56d',
depth: 1,
excerpt: null,
is_favorite: false,
link_role: 'reader',
link_reach: 'restricted',
nb_accesses_ancestors: 14,
nb_accesses_direct: 14,
numchild: 0,
path: '000000l',
title: 'No drop and no drag',
updated_at: '2025-03-14T14:44:16.032774Z',
user_roles: ['reader'],
},
];
@@ -0,0 +1,149 @@
/* eslint-disable playwright/no-conditional-in-test */
import { expect, test } from '@playwright/test';
import { createDoc } from './common';
test.describe('Doc Tree', () => {
test('create new sub pages', async ({ page, browserName }) => {
await page.goto('/');
await createDoc(page, 'doc-tree-content', browserName, 1);
const addButton = page.getByRole('button', { name: 'New page' });
const docTree = page.getByTestId('doc-tree');
await expect(addButton).toBeVisible();
// Attendre et intercepter la requête POST pour créer une nouvelle page
const responsePromise = page.waitForResponse(
(response) =>
response.url().includes('/documents/') &&
response.url().includes('/children/') &&
response.request().method() === 'POST',
);
await addButton.click();
const response = await responsePromise;
expect(response.ok()).toBeTruthy();
const subPageJson = await response.json();
await expect(docTree).toBeVisible();
const subPageItem = docTree
.getByTestId(`doc-sub-page-item-${subPageJson.id}`)
.first();
await expect(subPageItem).toBeVisible();
await subPageItem.click();
const input = page.getByRole('textbox', { name: 'doc title input' });
await input.click();
await input.fill('Test');
await input.press('Enter');
await expect(subPageItem.getByText('Test')).toBeVisible();
await page.reload();
await expect(subPageItem.getByText('Test')).toBeVisible();
});
test('check the reorder of sub pages', async ({ page, browserName }) => {
await page.goto('/');
await createDoc(page, 'doc-tree-content', browserName, 1);
const addButton = page.getByRole('button', { name: 'New page' });
await expect(addButton).toBeVisible();
const docTree = page.getByTestId('doc-tree');
// Create first sub page
const firstResponsePromise = page.waitForResponse(
(response) =>
response.url().includes('/documents/') &&
response.url().includes('/children/') &&
response.request().method() === 'POST',
);
await addButton.click();
const firstResponse = await firstResponsePromise;
expect(firstResponse.ok()).toBeTruthy();
const secondResponsePromise = page.waitForResponse(
(response) =>
response.url().includes('/documents/') &&
response.url().includes('/children/') &&
response.request().method() === 'POST',
);
// Create second sub page
await addButton.click();
const secondResponse = await secondResponsePromise;
expect(secondResponse.ok()).toBeTruthy();
const secondSubPageJson = await secondResponse.json();
const firstSubPageJson = await firstResponse.json();
const firstSubPageItem = docTree
.getByTestId(`doc-sub-page-item-${firstSubPageJson.id}`)
.first();
const secondSubPageItem = docTree
.getByTestId(`doc-sub-page-item-${secondSubPageJson.id}`)
.first();
// check that the sub pages are visible in the tree
await expect(firstSubPageItem).toBeVisible();
await expect(secondSubPageItem).toBeVisible();
// get the bounding boxes of the sub pages
const firstSubPageBoundingBox = await firstSubPageItem.boundingBox();
const secondSubPageBoundingBox = await secondSubPageItem.boundingBox();
expect(firstSubPageBoundingBox).toBeDefined();
expect(secondSubPageBoundingBox).toBeDefined();
if (!firstSubPageBoundingBox || !secondSubPageBoundingBox) {
throw new Error('Impossible de déterminer la position des éléments');
}
// move the first sub page to the second position
await page.mouse.move(
firstSubPageBoundingBox.x + firstSubPageBoundingBox.width / 2,
firstSubPageBoundingBox.y + firstSubPageBoundingBox.height / 2,
);
await page.mouse.down();
await page.mouse.move(
secondSubPageBoundingBox.x + secondSubPageBoundingBox.width / 2,
secondSubPageBoundingBox.y + secondSubPageBoundingBox.height + 4,
{ steps: 10 },
);
await page.mouse.up();
// check that the sub pages are visible in the tree
await expect(firstSubPageItem).toBeVisible();
await expect(secondSubPageItem).toBeVisible();
// reload the page
await page.reload();
// check that the sub pages are visible in the tree
await expect(firstSubPageItem).toBeVisible();
await expect(secondSubPageItem).toBeVisible();
// Check the position of the sub pages
const allSubPageItems = await docTree
.getByTestId(/^doc-sub-page-item/)
.all();
expect(allSubPageItems.length).toBe(2);
// Vérifier que le premier élément a l'ID de la deuxième sous-page après le drag and drop
await expect(allSubPageItems[0]).toHaveAttribute(
'data-testid',
`doc-sub-page-item-${secondSubPageJson.id}`,
);
// Vérifier que le deuxième élément a l'ID de la première sous-page après le drag and drop
await expect(allSubPageItems[1]).toHaveAttribute(
'data-testid',
`doc-sub-page-item-${firstSubPageJson.id}`,
);
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "app-e2e",
"version": "2.5.0",
"version": "2.6.0",
"private": true,
"scripts": {
"lint": "eslint . --ext .ts",
+53 -487
View File
@@ -1,492 +1,58 @@
const config = {
themes: {
default: {
theme: {
colors: {
'card-border': '#ededed',
'primary-bg': '#FAFAFA',
'primary-action': '#1212FF',
'primary-050': '#F5F5FE',
'primary-100': '#EDF5FA',
'primary-150': '#E5EEFA',
'primary-950': '#1B1B35',
'info-150': '#E5EEFA',
'greyscale-000': '#fff',
'greyscale-1000': '#161616',
'blue-400': '#7AB1E8',
'blue-500': '#417DC4',
'blue-600': '#3558A2',
'brown-400': '#E6BE92',
'brown-500': '#BD987A',
'brown-600': '#745B47',
'cyan-400': '#34BAB5',
'cyan-500': '#009099',
'cyan-600': '#006A6F',
'gold-400': '#FFCA00',
'gold-500': '#C3992A',
'gold-600': '#695240',
'green-400': '#34CB6A',
'green-500': '#00A95F',
'green-600': '#297254',
'olive-400': '#99C221',
'olive-500': '#68A532',
'olive-600': '#447049',
'orange-400': '#FF732C',
'orange-500': '#E4794A',
'orange-600': '#755348',
'pink-400': '#FFB7AE',
'pink-500': '#E18B76',
'pink-600': '#8D533E',
'purple-400': '#CE70CC',
'purple-500': '#A558A0',
'purple-600': '#6E445A',
'yellow-400': '#D8C634',
'yellow-500': '#B7A73F',
'yellow-600': '#66673D',
},
font: {
sizes: {
xs: '0.75rem',
sm: '0.875rem',
md: '1rem',
lg: '1.125rem',
ml: '0.938rem',
xl: '1.25rem',
t: '0.6875rem',
s: '0.75rem',
h1: '2rem',
h2: '1.75rem',
h3: '1.5rem',
h4: '1.375rem',
h5: '1.25rem',
h6: '1.125rem',
'xl-alt': '5rem',
'lg-alt': '4.5rem',
'md-alt': '4rem',
'sm-alt': '3.5rem',
'xs-alt': '3rem',
},
weights: {
thin: 100,
extrabold: 800,
black: 900,
},
},
spacings: {
'0': '0',
none: '0',
auto: 'auto',
bx: '2.2rem',
full: '100%',
'4xs': '0.125rem',
'3xs': '0.25rem',
'2xs': '0.375rem',
xs: '0.5rem',
sm: '0.75rem',
base: '1rem',
md: '1.5rem',
lg: '2rem',
xl: '2.5rem',
xxl: '3rem',
xxxl: '3.5rem',
'4xl': '4rem',
'5xl': '4.5rem',
'6xl': '6rem',
'7xl': '7.5rem',
},
breakpoints: {
xxs: '320px',
xs: '480px',
},
logo: {
src: '',
widthHeader: '',
widthFooter: '',
alt: '',
},
},
components: {
datagrid: {
header: {
weight: 'var(--c--theme--font--weights--extrabold)',
size: 'var(--c--theme--font--sizes--ml)',
},
cell: {
color: 'var(--c--theme--colors--primary-500)',
size: 'var(--c--theme--font--sizes--ml)',
},
},
'forms-checkbox': {
'background-color': {
hover: '#055fd214',
},
color: 'var(--c--theme--colors--primary-500)',
'font-size': 'var(--c--theme--font--sizes--ml)',
},
'forms-datepicker': {
'border-color': 'var(--c--theme--colors--primary-500)',
'value-color': 'var(--c--theme--colors--primary-500)',
'border-radius': {
hover: 'var(--c--components--forms-datepicker--border-radius)',
focus: 'var(--c--components--forms-datepicker--border-radius)',
},
},
'forms-field': {
color: 'var(--c--theme--colors--primary-500)',
'value-color': 'var(--c--theme--colors--primary-500)',
width: 'auto',
},
'forms-input': {
'value-color': 'var(--c--theme--colors--primary-500)',
'border-color': 'var(--c--theme--colors--primary-500)',
color: {
error: 'var(--c--theme--colors--danger-500)',
'error-hover': 'var(--c--theme--colors--danger-500)',
'box-shadow-error-hover': 'var(--c--theme--colors--danger-500)',
},
},
'forms-labelledbox': {
'label-color': {
small: 'var(--c--theme--colors--primary-500)',
'small-disabled': 'var(--c--theme--colors--greyscale-400)',
big: {
disabled: 'var(--c--theme--colors--greyscale-400)',
},
},
},
'forms-select': {
'border-color': 'var(--c--theme--colors--primary-500)',
'border-color-disabled-hover':
'var(--c--theme--colors--greyscale-200)',
'border-radius': {
hover: 'var(--c--components--forms-select--border-radius)',
focus: 'var(--c--components--forms-select--border-radius)',
},
'font-size': 'var(--c--theme--font--sizes--ml)',
'menu-background-color': '#fff',
'item-background-color': {
hover: 'var(--c--theme--colors--primary-300)',
},
},
'forms-switch': {
'accent-color': 'var(--c--theme--colors--primary-400)',
},
'forms-textarea': {
'border-color': 'var(--c--components--forms-textarea--border-color)',
'border-color-hover':
'var(--c--components--forms-textarea--border-color)',
'border-radius': {
hover: 'var(--c--components--forms-textarea--border-radius)',
focus: 'var(--c--components--forms-textarea--border-radius)',
},
color: 'var(--c--theme--colors--primary-500)',
disabled: {
'border-color-hover': 'var(--c--theme--colors--greyscale-200)',
},
},
modal: {
'background-color': '#fff',
},
button: {
'border-radius': {
active: 'var(--c--components--button--border-radius)',
},
'medium-height': 'auto',
'small-height': 'auto',
success: {
color: 'white',
'color-disabled': 'white',
'color-hover': 'white',
background: {
color: 'var(--c--theme--colors--success-600)',
'color-disabled': 'var(--c--theme--colors--greyscale-300)',
'color-hover': 'var(--c--theme--colors--success-800)',
},
},
danger: {
'color-hover': 'white',
background: {
color: 'var(--c--theme--colors--danger-600)',
'color-hover': '#FF2725',
'color-disabled': 'var(--c--theme--colors--danger-100)',
},
},
primary: {
color: 'var(--c--theme--colors--primary-text)',
'color-active': 'var(--c--theme--colors--primary-text)',
background: {
color: 'var(--c--theme--colors--primary-400)',
'color-active': 'var(--c--theme--colors--primary-500)',
},
border: {
'color-active': 'transparent',
},
},
secondary: {
color: 'var(--c--theme--colors--primary-500)',
'color-hover': 'var(--c--theme--colors--primary-text)',
background: {
color: 'white',
'color-hover': 'var(--c--theme--colors--primary-700)',
},
border: {
color: 'var(--c--theme--colors--greyscale-300)',
},
},
tertiary: {
color: 'var(--c--theme--colors--primary-text)',
'color-disabled': 'var(--c--theme--colors--greyscale-600)',
background: {
color: 'var(--c--theme--colors--primary-100)',
'color-hover': 'var(--c--theme--colors--primary-300)',
'color-active': 'var(--c--theme--colors--primary-100)',
'color-disabled': 'var(--c--theme--colors--greyscale-200)',
},
},
disabled: {
color: 'white',
background: {
color: '#b3cef0',
},
},
},
'la-gauffre': {
activated: false,
},
'home-proconnect': {
activated: false,
},
},
import { cunninghamConfig } from '@gouvfr-lasuite/ui-kit';
const tokens = {
...cunninghamConfig,
};
const customColors = {
'primary-action': '#1212FF',
'primary-bg': '#FAFAFA',
'blue-400': '#7AB1E8',
'blue-500': '#417DC4',
'blue-600': '#3558A2',
'brown-400': '#E6BE92',
'brown-500': '#BD987A',
'brown-600': '#745B47',
'cyan-400': '#34BAB5',
'cyan-500': '#009099',
'cyan-600': '#006A6F',
'gold-400': '#FFCA00',
'gold-500': '#C3992A',
'gold-600': '#695240',
'green-400': '#34CB6A',
'green-500': '#00A95F',
'green-600': '#297254',
'olive-400': '#99C221',
'olive-500': '#68A532',
'olive-600': '#447049',
'orange-400': '#FF732C',
'orange-500': '#E4794A',
'orange-600': '#755348',
'pink-400': '#FFB7AE',
'pink-500': '#E18B76',
'pink-600': '#8D533E',
'purple-400': '#CE70CC',
'purple-500': '#A558A0',
'purple-600': '#6E445A',
'yellow-400': '#D8C634',
'yellow-500': '#B7A73F',
'yellow-600': '#66673D',
};
tokens.themes.default.theme.colors = {
...tokens.themes.default.theme.colors,
...customColors,
};
tokens.themes.default.components = {
...tokens.themes.default.components,
...{
'la-gauffre': {
activated: true,
},
dsfr: {
theme: {
colors: {
'card-border': '#E5E5E5',
'primary-text': '#000091',
'primary-100': '#ECECFE',
'primary-150': '#F4F4FD',
'primary-200': '#E3E3FD',
'primary-300': '#CACAFB',
'primary-400': '#8585F6',
'primary-500': '#6A6AF4',
'primary-600': '#313178',
'primary-700': '#272747',
'primary-800': '#000091',
'primary-900': '#21213F',
'secondary-text': '#fff',
'secondary-100': '#fee9ea',
'secondary-200': '#fedfdf',
'secondary-300': '#fdbfbf',
'secondary-400': '#e1020f',
'secondary-500': '#c91a1f',
'secondary-600': '#5e2b2b',
'secondary-700': '#3b2424',
'secondary-800': '#341f1f',
'secondary-900': '#2b1919',
'greyscale-text': '#303C4B',
'greyscale-000': '#fff',
'greyscale-050': '#F6F6F6',
'greyscale-100': '#eee',
'greyscale-200': '#E5E5E5',
'greyscale-250': '#ddd',
'greyscale-300': '#CECECE',
'greyscale-350': '#ddd',
'greyscale-400': '#929292',
'greyscale-500': '#7C7C7C',
'greyscale-600': '#666666',
'greyscale-700': '#3A3A3A',
'greyscale-750': '#353535',
'greyscale-800': '#2A2A2A',
'greyscale-900': '#242424',
'greyscale-950': '#1E1E1E',
'greyscale-1000': '#161616',
'success-text': '#1f8d49',
'success-100': '#dffee6',
'success-200': '#b8fec9',
'success-300': '#88fdaa',
'success-400': '#3bea7e',
'success-500': '#1f8d49',
'success-600': '#18753c',
'success-700': '#204129',
'success-800': '#1e2e22',
'success-900': '#19281d',
'info-text': '#0078f3',
'info-100': '#E8EDFF',
'info-200': '#DDE5FF',
'info-300': '#BCCDFF',
'info-400': '#518FFF',
'info-500': '#0078F3',
'info-600': '#0063CB',
'info-700': '#273961',
'info-800': '#222A3F',
'info-900': '#1D2437',
'warning-text': '#d64d00',
'warning-100': '#fff4f3',
'warning-200': '#ffe9e6',
'warning-300': '#ffded9',
'warning-400': '#ffbeb4',
'warning-500': '#d64d00',
'warning-600': '#b34000',
'warning-700': '#5e2c21',
'warning-800': '#3e241e',
'warning-900': '#361e19',
'danger-text': '#FFF',
'danger-100': '#FFE9E9',
'danger-200': '#FFDDDD',
'danger-300': '#FFBDBD',
'danger-400': '#FF5655',
'danger-500': '#F60700',
'danger-600': '#CE0500',
'danger-700': '#642626',
'danger-800': '#412121',
'danger-900': '#391C1C',
},
font: {
families: {
accent: 'Marianne',
base: 'Marianne',
},
},
logo: {
src: '/assets/logo-gouv.svg',
widthHeader: '110px',
widthFooter: '220px',
alt: 'Gouvernement Logo',
},
},
components: {
alert: {
'border-radius': '0',
error: {
'background-color': 'var(--c--theme--colors--danger-100)',
'border-left-color': 'var(--c--theme--colors--danger-400)',
close: {
color: 'white',
'background-color': 'var(--c--theme--colors--danger-400)',
'background-color-hover': 'var(--c--theme--colors--danger-600)',
},
},
},
modal: {
'width-small': '342px',
},
button: {
'medium-height': '40px',
'medium-text-height': '40px',
'border-radius': '4px',
primary: {
background: {
color: 'var(--c--theme--colors--primary-text)',
'color-hover': '#1212ff',
'color-active': '#2323ff',
},
color: '#fff',
'color-hover': '#fff',
'color-active': '#fff',
},
'primary-text': {
background: {
'color-hover': 'var(--c--theme--colors--primary-100)',
'color-active': 'var(--c--theme--colors--primary-100)',
},
'color-hover': 'var(--c--theme--colors--primary-text)',
color: 'var(--c--theme--colors--primary-800)',
},
secondary: {
background: {
'color-hover': '#F6F6F6',
'color-active': '#EDEDED',
},
border: {
color: 'var(--c--theme--colors--greyscale-300)',
'color-hover': 'var(--c--theme--colors--greyscale-300)',
},
color: 'var(--c--theme--colors--primary-text)',
},
'tertiary-text': {
background: {
'color-hover': 'var(--c--theme--colors--greyscale-100)',
},
'color-hover': 'var(--c--theme--colors--primary-text)',
color: 'var(--c--theme--colors--primary-600)',
},
},
datagrid: {
header: {
color: 'var(--c--theme--colors--primary-text)',
size: 'var(--c--theme--font--sizes--s)',
},
body: {
'background-color': 'transparent',
'background-color-hover': '#F4F4FD',
},
pagination: {
'background-color': 'transparent',
'background-color-active': 'var(--c--theme--colors--primary-300)',
'border-color': 'var(--c--theme--colors--primary-400)',
},
},
'forms-checkbox': {
'border-radius': '0',
color: 'var(--c--theme--colors--primary-text)',
text: {
color: 'var(--c--theme--colors--greyscale-text)',
size: 'var(--c--theme--font--sizes--t)',
},
},
'forms-datepicker': {
'border-radius': '0',
},
'forms-fileuploader': {
'border-radius': '0',
},
'forms-field': {
color: 'var(--c--theme--colors--primary-text)',
'footer-font-size': 'var(--c--theme--font--sizes--t)',
'footer-color': 'var(--c--theme--colors--greyscale-text)',
},
'forms-input': {
'border-radius': '4px',
'background-color': '#fff',
'border-color': 'var(--c--theme--colors--primary-text)',
'box-shadow-color': 'var(--c--theme--colors--primary-text)',
'value-color': 'var(--c--theme--colors--primary-text)',
'font-size': '14px',
},
'forms-labelledbox': {
'label-color': {
big: 'var(--c--theme--colors--primary-text)',
},
},
'forms-radio': {
'accent-color': 'var(--c--theme--colors--primary-600)',
},
'forms-select': {
'item-font-size': '14px',
'border-radius': '4px',
'border-radius-hover': '4px',
'background-color': '#fff',
'border-color': 'var(--c--theme--colors--primary-text)',
'border-color-hover': 'var(--c--theme--colors--primary-text)',
'box-shadow-color': 'var(--c--theme--colors--primary-text)',
},
'forms-switch': {
'handle-border-radius': '2px',
'rail-border-radius': '4px',
'accent-color': 'var(--c--theme--colors--primary-text)',
},
'forms-textarea': {
'border-radius': '0',
},
'la-gauffre': {
activated: true,
},
'home-proconnect': {
activated: true,
},
},
'home-proconnect': {
activated: true,
},
},
};
export default config;
export default tokens;
+7 -1
View File
@@ -1,6 +1,6 @@
{
"name": "app-impress",
"version": "2.5.0",
"version": "2.6.0",
"private": true,
"scripts": {
"dev": "next dev",
@@ -21,13 +21,18 @@
"@blocknote/react": "0.23.2-hotfix.0",
"@blocknote/xl-docx-exporter": "0.23.2-hotfix.0",
"@blocknote/xl-pdf-exporter": "0.23.2-hotfix.0",
"@dnd-kit/core": "6.3.1",
"@dnd-kit/modifiers": "9.0.0",
"@fontsource/material-icons": "5.2.5",
"@gouvfr-lasuite/integration": "1.0.2",
"@gouvfr-lasuite/ui-kit": "/Users/melde/Documents/societes/melde/clients/dinum/design-system",
"@hocuspocus/provider": "2.15.2",
"@openfun/cunningham-react": "3.0.0",
"@react-pdf/renderer": "4.1.6",
"@sentry/nextjs": "9.3.0",
"@tanstack/react-query": "5.67.1",
"canvg": "4.0.3",
"clsx": "2.1.1",
"cmdk": "1.0.4",
"crisp-sdk-web": "1.0.25",
"docx": "9.1.1",
@@ -44,6 +49,7 @@
"react-i18next": "15.4.1",
"react-intersection-observer": "9.15.1",
"react-select": "5.10.1",
"react-stately": "3.36.1",
"styled-components": "6.1.15",
"use-debounce": "10.0.4",
"y-protocols": "1.0.6",
@@ -1,101 +0,0 @@
@font-face {
font-family: Marianne;
src:
url('Marianne-Thin.woff2') format('woff2'),
url('Marianne-Thin.woff') format('woff');
font-weight: 100;
}
@font-face {
font-family: Marianne;
src:
url('Marianne-Thin_Italic.woff2') format('woff2'),
url('Marianne-Thin_Italic.woff') format('woff');
font-weight: 100;
font-style: italic;
}
@font-face {
font-family: Marianne;
src:
url('Marianne-Light.woff2') format('woff2'),
url('Marianne-Light.woff') format('woff');
font-weight: 300;
}
@font-face {
font-family: Marianne;
src:
url('Marianne-Light_Italic.woff2') format('woff2'),
url('Marianne-Light_Italic.woff') format('woff');
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: Marianne;
src:
url('Marianne-Regular.woff2') format('woff2'),
url('Marianne-Regular.woff') format('woff');
font-weight: 400;
}
@font-face {
font-family: Marianne;
src:
url('Marianne-Regular_Italic.woff2') format('woff2'),
url('Marianne-Regular_Italic.woff') format('woff');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: Marianne;
src:
url('Marianne-Medium.woff2') format('woff2'),
url('Marianne-Medium.woff') format('woff');
font-weight: 500;
}
@font-face {
font-family: Marianne;
src:
url('Marianne-Medium_Italic.woff2') format('woff2'),
url('Marianne-Medium_Italic.woff') format('woff');
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: Marianne;
src:
url('Marianne-Bold.woff2') format('woff2'),
url('Marianne-Bold.woff') format('woff');
font-weight: 700;
}
@font-face {
font-family: Marianne;
src:
url('Marianne-Bold_Italic.woff2') format('woff2'),
url('Marianne-Bold_Italic.woff') format('woff');
font-weight: 700;
font-style: italic;
}
@font-face {
font-family: Marianne;
src:
url('Marianne-ExtraBold.woff2') format('woff2'),
url('Marianne-ExtraBold.woff') format('woff');
font-weight: 800;
}
@font-face {
font-family: Marianne;
src:
url('Marianne-ExtraBold_Italic.woff2') format('woff2'),
url('Marianne-ExtraBold_Italic.woff') format('woff');
font-weight: 800;
font-style: italic;
}
@@ -8,6 +8,7 @@ export type DropdownMenuOption = {
icon?: string;
label: string;
testId?: string;
value?: string;
callback?: () => void | Promise<unknown>;
danger?: boolean;
isSelected?: boolean;
@@ -23,6 +24,8 @@ export type DropdownMenuProps = {
buttonCss?: BoxProps['$css'];
disabled?: boolean;
topMessage?: string;
selectedValues?: string[];
afterOpenChange?: (isOpen: boolean) => void;
};
export const DropdownMenu = ({
@@ -34,6 +37,8 @@ export const DropdownMenu = ({
buttonCss,
label,
topMessage,
afterOpenChange,
selectedValues,
}: PropsWithChildren<DropdownMenuProps>) => {
const theme = useCunninghamTheme();
const spacings = theme.spacingsTokens();
@@ -43,6 +48,7 @@ export const DropdownMenu = ({
const onOpenChange = (isOpen: boolean) => {
setIsOpen(isOpen);
afterOpenChange?.(isOpen);
};
if (disabled) {
@@ -161,7 +167,8 @@ export const DropdownMenu = ({
{option.label}
</Text>
</Box>
{option.isSelected && (
{(option.isSelected ||
selectedValues?.includes(option.value ?? '')) && (
<Icon iconName="check" $size="20px" $theme="greyscale" />
)}
</BoxButton>
@@ -8,7 +8,7 @@ type IconProps = TextType & {
};
export const Icon = ({ iconName, ...textProps }: IconProps) => {
return (
<Text $isMaterialIcon {...textProps}>
<Text $isMaterialIcon={textProps.$isMaterialIcon ?? true} {...textProps}>
{iconName}
</Text>
);
@@ -27,7 +27,7 @@ export const IconBG = ({ iconName, ...textProps }: IconBGProps) => {
$size="36px"
$theme="primary"
$variation="600"
$background={colorsTokens()['primary-bg']}
$background={colorsTokens()['greyscale-000']}
$css={`
border: 1px solid ${colorsTokens()['primary-200']};
user-select: none;
@@ -1,3 +1,4 @@
import clsx from 'clsx';
import { CSSProperties, ComponentPropsWithRef, forwardRef } from 'react';
import styled from 'styled-components';
@@ -11,7 +12,7 @@ type TextSizes = keyof typeof sizes;
export interface TextProps extends BoxProps {
as?: 'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
$elipsis?: boolean;
$isMaterialIcon?: boolean;
$isMaterialIcon?: boolean | 'filled';
$weight?: CSSProperties['fontWeight'];
$textAlign?: CSSProperties['textAlign'];
$size?: TextSizes | (string & {});
@@ -58,13 +59,20 @@ export const TextStyled = styled(Box)<TextProps>`
const Text = forwardRef<HTMLElement, ComponentPropsWithRef<typeof TextStyled>>(
({ className, $isMaterialIcon, ...props }, ref) => {
const isFilled = $isMaterialIcon === 'filled';
const isMaterialIcon =
typeof $isMaterialIcon === 'boolean' && $isMaterialIcon;
return (
<TextStyled
ref={ref}
as="span"
$theme="greyscale"
$variation="text"
className={`${className || ''}${$isMaterialIcon ? ' material-icons' : ''}`}
className={clsx(className || '', {
'material-icons': isMaterialIcon,
'material-icons-filled': isFilled,
})}
{...props}
/>
);
@@ -0,0 +1,63 @@
import { css } from 'styled-components';
import { Box } from '../Box';
import { DropdownMenu, DropdownMenuOption } from '../DropdownMenu';
import { Icon } from '../Icon';
import { Text } from '../Text';
export type FilterDropdownProps = {
options: DropdownMenuOption[];
selectedValue?: string;
};
export const FilterDropdown = ({
options,
selectedValue,
}: FilterDropdownProps) => {
const selectedOption = options.find(
(option) => option.value === selectedValue,
);
if (options.length === 0) {
return null;
}
return (
<DropdownMenu
selectedValues={selectedValue ? [selectedValue] : undefined}
options={options}
>
<Box
$css={css`
border: 1px solid
${selectedOption
? 'var(--c--theme--colors--primary-500)'
: 'var(--c--theme--colors--greyscale-250)'};
border-radius: 4px;
background-color: ${selectedOption
? 'var(--c--theme--colors--primary-100)'
: 'var(--c--theme--colors--greyscale-000)'};
gap: var(--c--theme--spacings--2xs);
padding: var(--c--theme--spacings--2xs) var(--c--theme--spacings--xs);
`}
color="secondary"
$direction="row"
$align="center"
>
<Text
$weight={400}
$variation={selectedOption ? '800' : '600'}
$theme={selectedOption ? 'primary' : 'greyscale'}
>
{selectedOption?.label ?? options[0].label}
</Text>
<Icon
$size="16px"
iconName="keyboard_arrow_down"
$variation={selectedOption ? '800' : '600'}
$theme={selectedOption ? 'primary' : 'greyscale'}
/>
</Box>
</DropdownMenu>
);
};
@@ -57,6 +57,9 @@ export const QuickSearchInput = ({
/* eslint-disable-next-line jsx-a11y/no-autofocus */
autoFocus={true}
aria-label={t('Quick search input')}
onClick={(e) => {
e.stopPropagation();
}}
value={inputValue}
role="combobox"
placeholder={placeholder ?? t('Search')}
@@ -1,9 +1,9 @@
import { useCunninghamTheme } from '../useCunninghamTheme';
describe('<useCunninghamTheme />', () => {
it('has the dsfr logo correctly set', () => {
it('has the logo correctly set', () => {
const { themeTokens, setTheme } = useCunninghamTheme.getState();
setTheme('dsfr');
setTheme('default');
const logo = themeTokens().logo;
expect(logo?.src).toBe('/assets/logo-gouv.svg');
expect(logo?.widthHeader).toBe('110px');
@@ -25,4 +25,11 @@
--c--components--forms-select--value-color--disabled: var(
--c--theme--colors--greyscale-400
);
/**
* Button
**/
--c--components--button--border-radius--active: var(
--c--components--button--border-radius
);
}
@@ -1,633 +0,0 @@
@import url('@openfun/cunningham-react/icons');
@import url('@openfun/cunningham-react/style');
@import url('@openfun/cunningham-react/fonts');
@import url('./cunningham-tokens.css');
@import url('./cunningham-custom-tokens.css');
@import url('../assets/fonts/Marianne/Marianne-font.css');
.c__input,
.c__field,
.c__select,
.c__datagrid {
font-family: var(--c--theme--font--families--base);
}
.c__field {
line-height: initial;
}
.c__field .c__field__footer {
padding: 2px 0 0;
font-size: var(--c--components--forms-field--footer-font-size);
color: var(--c--components--forms-field--footer-color);
}
.labelled-box label {
color: var(--c--theme--colors--primary-text);
}
.labelled-box--disabled label {
color: var(--c--components--forms-labelledbox--label-color--small-disabled);
}
.c__field :not(.c__textarea__wrapper, div) .labelled-box label.placeholder {
top: 50%;
transform: translateY(-50%);
}
/**
* Input
* TextArea
*/
.c__input__wrapper,
.c__textarea__wrapper {
transition: all var(--c--theme--transitions--duration)
var(--c--theme--transitions--ease-out);
}
.c__input__wrapper:has(input[readonly]),
.c__input__wrapper:has(input[readonly]) * {
cursor: default;
}
.c__textarea__wrapper:has(input.border-none),
.c__textarea__wrapper:has(input.border-none) *,
.c__input__wrapper:has(input.border-none),
.c__input__wrapper:has(input.border-none) * {
border: none;
}
.c__input__wrapper:hover,
.c__textarea__wrapper:hover {
box-shadow: var(--c--components--forms-input--box-shadow-color) 0 0 0 2px;
}
.c__textarea__wrapper--disabled:hover,
.c__input__wrapper--disabled:hover,
.c__input__wrapper:hover:has(input[readonly]) {
box-shadow: var(--c--theme--colors--primary-500) 0 0 0 0;
}
.c__input__wrapper--disabled {
color: var(--c--components--forms-input--value-color--disabled);
}
.c__input__wrapper .labelled-box__label.placeholder {
cursor: inherit;
}
.c__input__wrapper .c__input,
.c__textarea__wrapper .c__textarea {
width: 100%;
}
.c__input__wrapper--disabled .c__input {
color: var(--c--components--forms-input--value-color--disabled);
}
.c__input__wrapper--error .c__input {
color: var(--c--components--forms-input--color--error);
}
.c__input__wrapper--error:not(.c__input__wrapper--disabled):hover {
border-color: var(--c--components--forms-input--border--color-error-hover);
color: var(--c--components--forms-input--color--error-hover);
}
.c__input__wrapper--error:hover {
box-shadow: var(--c--components--forms-input--color--box-shadow-error-hover) 0
0 0 2px;
}
.c__input__wrapper--error:not(.c__input__wrapper--disabled):hover label {
color: var(--c--components--forms-input--border--color-error-hover);
}
input:-webkit-autofill,
input:-webkit-autofill:focus {
transition:
background-color 0s 600000s,
color 0s 600000s;
}
.c__textarea__wrapper .c__textarea {
color: var(--c--components--forms-textarea--color);
}
.c__textarea__wrapper:hover {
border-color: var(--c--components--forms-textarea--border-color-hover);
}
.c__textarea__wrapper--disabled:hover {
border-color: var(
--c--components--forms-textarea--disabled--border-color-hover
);
}
/**
* Select
*/
.c_select__no_border .c__select .c__select__wrapper,
.c_select__no_border .c__select .c__select__wrapper:hover,
.c_select__no_border
.c__select:not(.c__select--disabled)
.c__select__wrapper:hover {
border: none;
box-shadow: none;
}
.c__select__wrapper {
transition: all var(--c--theme--transitions--duration)
var(--c--theme--transitions--ease-out);
min-height: var(--c--components--forms-select--height);
height: auto;
}
.c__select:not(.c__select--disabled) .c__select__wrapper:hover {
box-shadow: var(--c--components--forms-input--box-shadow-color) 0 0 0 2px;
}
.c__select__wrapper:hover {
border-radius: var(--c--components--forms-select--border-radius-hover);
border-color: var(--c--components--forms-select--border-color-hover);
}
.c__select--disabled .c__select__wrapper:hover {
border-color: var(--c--components--forms-select--border-color-disabled-hover);
}
.c__select--disabled .c__select__wrapper label,
.c__select--disabled .c__select__wrapper input,
.c__select--disabled .c__select__wrapper {
cursor: not-allowed;
}
.c__select__menu__item {
transition: all var(--c--theme--transitions--duration)
var(--c--theme--transitions--ease-out);
}
.c__select--disabled .c__select__wrapper label,
.c__select--disabled .c__select__wrapper input,
.c_select__no_bg .c__select__wrapper {
background: none;
}
.c__select__wrapper:focus-within .labelled-box--disabled label {
color: var(--c--components--forms-labelledbox--label-color--small-disabled);
}
.c__select__wrapper .labelled-box {
display: flex;
gap: 0.6rem;
flex-direction: column;
align-items: flex-start;
}
.c__select__wrapper .labelled-box .labelled-box__children {
padding: unset;
padding-right: 5rem;
}
.c__select__wrapper .labelled-box .c__select__inner__actions {
right: 0;
top: 50%;
position: absolute;
}
.c__select__wrapper label {
position: relative;
padding-right: 5rem;
max-width: none;
}
.c__select__wrapper .c__select__inner__actions__open:focus {
outline: none;
}
.c__select__wrapper .labelled-box__label.c__offscreen {
display: none;
}
/**
* DataGrid
*/
.c__datagrid__table__container {
overflow: auto;
}
.c__datagrid__table__container > table th .c__datagrid__header {
color: var(--c--components--datagrid--header--color);
font-weight: var(--c--components--datagrid--header--weight);
font-size: var(--c--components--datagrid--header--size);
padding-block: 2rem;
}
.c__datagrid__table__container > table tbody tr {
border: none;
border-top: 1px var(--c--theme--colors--greyscale-100) solid;
border-bottom: 1px var(--c--theme--colors--greyscale-100) solid;
}
.c__datagrid__table__container > table tbody {
background-color: var(--c--components--datagrid--body--background-color);
}
.c__datagrid__table__container > table tbody tr:hover {
background-color: var(
--c--components--datagrid--body--background-color-hover
);
}
.c__datagrid__table__container > table {
table-layout: auto;
}
.c__datagrid__table__container > table td {
white-space: break-spaces;
}
.c__datagrid__table__container > table th:first-child,
.c__datagrid__table__container > table td:first-child {
padding-left: 2rem;
}
.c__datagrid > .c__pagination {
padding-inline: 1rem;
justify-content: flex-end;
}
.c__pagination__list {
gap: 3px;
border-radius: 4px;
background: var(--c--components--datagrid--pagination--background-color);
border-color: var(--c--components--datagrid--pagination--border-color);
}
.c__pagination__list .c__button--tertiary-text.c__button--active {
background-color: var(
--c--components--datagrid--pagination--background-color-active
);
color: var(--c--theme--colors--greyscale-800);
}
.c__pagination__list .c__button--tertiary-text:disabled {
display: none;
}
@media (width <= 380px) {
.c__datagrid > .c__pagination {
flex-direction: column;
align-items: center;
gap: 1rem;
}
}
/**
* Date picker
*/
.c__popover.c__popover--borderless {
z-index: 3;
}
.c__date-picker__wrapper {
transition: all var(--c--theme--transitions--duration)
var(--c--theme--transitions--ease-out);
}
.c__date-picker:not(.c__date-picker--disabled):hover .c__date-picker__wrapper {
box-shadow: var(--c--theme--colors--primary-500) 0 0 0 2px;
}
.c__date-picker.c__date-picker--invalid:not(.c__date-picker--disabled):hover
.c__date-picker__wrapper {
box-shadow: var(--c--theme--colors--danger-300) 0 0 0 2px;
}
.c__date-picker__wrapper button[aria-label='Clear date'],
.c__date-picker.c__date-picker--invalid .c__date-picker__wrapper * {
color: var(--c--theme--colors--danger-300);
}
/**
* Checkbox
*/
.c__checkbox:focus-within {
border-color: transparent;
background-color: transparent;
}
.c__checkbox {
transition: all 0.8s ease-in-out;
}
.c__checkbox .c__field__text {
color: var(--c--components--forms-checkbox--text--color);
font-size: var(--c--components--forms-checkbox--text--size);
}
.c__checkbox.c__checkbox--disabled .c__field__text {
color: var(--c--theme--colors--greyscale-600);
}
.c__switch.c__checkbox--disabled .c__switch__rail {
cursor: not-allowed;
}
.c__checkbox.c__checkbox--disabled .c__checkbox__label {
color: var(--c--theme--colors--greyscale-400);
}
/**
* Button
*/
.c__button {
text-decoration: none;
}
.c__button:hover.c__button-no-bg,
.c__button.c__button-no-bg,
.c__button:disabled.c__button-no-bg {
background-color: transparent;
}
.c__button--nano {
padding: 0 var(--c--theme--spacings--3xs);
gap: var(--c--theme--spacings--3xs);
}
.c__button--nano.c__button--icon-only {
width: auto;
}
.c__button--nano.c__button--icon-only.c__button--full-width {
width: 100%;
}
.c__button--medium {
height: auto;
min-height: var(--c--components--button--medium-height);
}
.c__button--small {
padding: 0.6rem 0.75rem;
}
.c__button--with-icon--right {
padding: 0.7rem var(--c--theme--spacings--t) 0.7rem
var(--c--theme--spacings--s);
}
.c__button--primary {
background-color: var(--c--components--button--primary--background--color);
color: var(--c--components--button--primary--color);
}
.c__button--primary:hover {
background-color: var(
--c--components--button--primary--background--color-hover
);
color: var(--c--components--button--primary--color-hover);
}
.c__button--primary:active,
.c__button--primary.c__button--active {
background-color: var(
--c--components--button--primary--background--color-active
);
color: var(--c--components--button--primary--color-active);
border-color: var(--c--components--button--primary--border--color-active);
}
.c__button--primary-text:active,
.c__button--primary-text.c__button--active {
border: none;
background-color: var(
--c--components--button--primary-text--background--color-active
);
}
.c__button--primary-text {
color: var(--c--components--button--primary-text--color);
}
.c__button--primary-text:hover,
.c__button--primary-text:focus-visible {
background-color: var(
--c--components--button--primary-text--background--color-hover
);
color: var(--c--components--button--primary-text--color-hover);
}
.c__button:disabled {
background-color: var(--c--components--button--disabled--background--color);
color: var(--c--components--button--disabled--color);
}
.c__button--success {
background-color: var(--c--components--button--success--background--color);
color: var(--c--components--button--success--color);
}
.c__button--success:hover,
.c__button--success:focus-visible {
background-color: var(
--c--components--button--success--background--color-hover
);
color: var(--c--components--button--success--color-hover);
}
.c__button--success:disabled {
background-color: var(
--c--components--button--success--background--color-disabled
);
color: var(--c--components--button--success--color-disabled);
}
.c__button--secondary {
background-color: var(--c--components--button--secondary--background--color);
color: var(--c--components--button--secondary--color);
border: 1px solid var(--c--components--button--secondary--border--color);
}
.c__button--secondary:hover,
.c__button--secondary:focus-visible {
background-color: var(
--c--components--button--secondary--background--color-hover
);
color: var(--c--components--button--secondary--color-hover);
border: 1px solid var(--c--components--button--secondary--border--color-hover);
}
.c__button--tertiary {
background-color: var(--c--components--button--tertiary--background--color);
color: var(--c--components--button--tertiary--color);
border: none;
}
.c__button--tertiary:hover,
.c__button--tertiary:focus-visible {
background-color: var(
--c--components--button--tertiary--background--color-hover
);
color: var(--c--components--button--tertiary--color);
}
.c__button--tertiary:active {
background-color: var(
--c--components--button--tertiary--background--color-active
);
color: var(--c--components--button--tertiary--color-active);
}
.c__button--tertiary:disabled {
background-color: var(
--c--components--button--tertiary--background--color-disabled
);
color: var(--c--components--button--tertiary--color-disabled);
}
.c__button--tertiary-text {
border: none;
color: var(--c--components--button--tertiary-text--color);
}
.c__button--tertiary-text:hover,
.c__button--tertiary-text:focus-visible {
background-color: var(
--c--components--button--tertiary-text--background--color-hover
);
color: var(--c--components--button--tertiary-text--color-hover);
}
.c__button--tertiary-text:disabled {
background-color: var(
--c--components--button--tertiary-text--background--color-disabled
);
color: var(--c--components--button--tertiary-text--color-disabled);
}
.c__button--danger {
background-color: var(--c--components--button--danger--background--color);
}
.c__button--danger:hover,
.c__button--danger:focus-visible {
background-color: var(
--c--components--button--danger--background--color-hover
);
color: var(--c--components--button--danger--color-hover);
}
.c__button--danger:disabled {
background-color: var(
--c--components--button--danger--background--color-disabled
);
}
/**
* Modal
*/
.c__modal__backdrop {
z-index: 1000;
}
.c__modal__close .c__button--tertiary-text:hover,
.c__modal__close .c__button--tertiary-text:focus-visible {
box-shadow: none;
}
.c__modal__close button {
padding: 0;
font-size: 88px;
width: 28px !important;
height: 28px;
}
.c__modal__close button .material-icons {
padding: 0;
font-size: 24px;
color: var(--c--theme--colors--greyscale-600);
}
.c__modal__close .c__button {
padding: 0 !important;
top: -0.65rem;
right: -0.65rem;
}
.c__modal--full .c__modal__content {
overflow-y: auto;
}
.c__modal__title {
padding: 0;
font-size: 1.125rem;
margin-bottom: var(--c--theme--spacings--2xs);
}
@media screen and (width <= 420px) {
.c__modal__scroller {
padding: 0.7rem;
}
.c__modal__title h2 {
font-size: 1rem;
}
}
@media (width <= 576px) {
.c__modal__footer--sided {
gap: 0.5rem;
flex-direction: column-reverse;
}
}
.c__modal__scroller:has(.noPadding) {
padding: 0 !important;
.c__modal__close .c__button {
right: 5px;
top: 5px;
padding: 1.5rem 1rem;
}
.c__modal__title {
font-size: var(--c--theme--font--sizes--xs);
padding: var(--c--theme--spacings--base);
margin-bottom: 0;
}
}
/**
* Toast
*/
.c__toast__container {
z-index: 10000;
}
/**
* Tooltip
*/
.c__tooltip {
padding: 4px 6px;
}
/**
* Alert
*/
.c__alert--error {
background-color: var(--c--components--alert--error--background-color);
border-left-color: var(--c--components--alert--error--border-left-color);
}
.c__alert--error .c__button--tertiary {
background-color: var(--c--components--alert--error--close--background-color);
color: var(--c--components--alert--error--close--color);
}
.c__alert.c__alert--error .c__button--tertiary:hover {
background-color: var(
--c--components--alert--error--close--background-color-hover
);
}
@@ -1,82 +1,85 @@
:root {
--c--theme--colors--secondary-text: var(--c--theme--colors--greyscale-700);
--c--theme--colors--secondary-100: #f2f7fc;
--c--theme--colors--secondary-200: #ebf3fa;
--c--theme--colors--secondary-300: #e2eef8;
--c--theme--colors--secondary-400: #ddeaf7;
--c--theme--colors--secondary-500: #d4e5f5;
--c--theme--colors--secondary-600: #c1d0df;
--c--theme--colors--secondary-700: #97a3ae;
--c--theme--colors--secondary-800: #757e87;
--c--theme--colors--secondary-900: #596067;
--c--theme--colors--info-text: var(--c--theme--colors--greyscale-000);
--c--theme--colors--info-100: #ebf2fc;
--c--theme--colors--info-200: #8cb5ea;
--c--theme--colors--info-300: #5894e1;
--c--theme--colors--info-400: #377fdb;
--c--theme--colors--info-500: #055fd2;
--c--theme--colors--info-600: #0556bf;
--c--theme--colors--info-700: #044395;
--c--theme--colors--info-800: #033474;
--c--theme--colors--info-900: #022858;
--c--theme--colors--greyscale-100: #fafafb;
--c--theme--colors--greyscale-200: #f3f4f4;
--c--theme--colors--greyscale-300: #e7e8ea;
--c--theme--colors--greyscale-400: #c2c6ca;
--c--theme--colors--greyscale-500: #9ea3aa;
--c--theme--colors--greyscale-600: #79818a;
--c--theme--colors--greyscale-700: #555f6b;
--c--theme--colors--greyscale-800: #303c4b;
--c--theme--colors--greyscale-900: #0c1a2b;
--c--theme--colors--secondary-text: #fff;
--c--theme--colors--secondary-100: #fee9ea;
--c--theme--colors--secondary-200: #fedfdf;
--c--theme--colors--secondary-300: #fdbfbf;
--c--theme--colors--secondary-400: #e1020f;
--c--theme--colors--secondary-500: #c91a1f;
--c--theme--colors--secondary-600: #5e2b2b;
--c--theme--colors--secondary-700: #3b2424;
--c--theme--colors--secondary-800: #341f1f;
--c--theme--colors--secondary-900: #2b1919;
--c--theme--colors--info-text: #0078f3;
--c--theme--colors--info-100: #e8edff;
--c--theme--colors--info-200: #dde5ff;
--c--theme--colors--info-300: #bccdff;
--c--theme--colors--info-400: #518fff;
--c--theme--colors--info-500: #0078f3;
--c--theme--colors--info-600: #0063cb;
--c--theme--colors--info-700: #273961;
--c--theme--colors--info-800: #222a3f;
--c--theme--colors--info-900: #1d2437;
--c--theme--colors--greyscale-100: #eee;
--c--theme--colors--greyscale-200: #e5e5e5;
--c--theme--colors--greyscale-300: #cecece;
--c--theme--colors--greyscale-400: #929292;
--c--theme--colors--greyscale-500: #7c7c7c;
--c--theme--colors--greyscale-600: #666;
--c--theme--colors--greyscale-700: #3a3a3a;
--c--theme--colors--greyscale-800: #2a2a2a;
--c--theme--colors--greyscale-900: #242424;
--c--theme--colors--greyscale-000: #fff;
--c--theme--colors--primary-100: #edf5fa;
--c--theme--colors--primary-200: #8cb5ea;
--c--theme--colors--primary-300: #5894e1;
--c--theme--colors--primary-400: #377fdb;
--c--theme--colors--primary-500: #055fd2;
--c--theme--colors--primary-600: #0556bf;
--c--theme--colors--primary-700: #044395;
--c--theme--colors--primary-800: #033474;
--c--theme--colors--primary-900: #022858;
--c--theme--colors--success-100: #effcd3;
--c--theme--colors--success-200: #dbfaa9;
--c--theme--colors--success-300: #bef27c;
--c--theme--colors--success-400: #a0e659;
--c--theme--colors--success-500: #76d628;
--c--theme--colors--success-600: #5ab81d;
--c--theme--colors--success-700: #419a14;
--c--theme--colors--success-800: #2c7c0c;
--c--theme--colors--success-900: #1d6607;
--c--theme--colors--warning-100: #fff8cd;
--c--theme--colors--warning-200: #ffef9b;
--c--theme--colors--warning-300: #ffe469;
--c--theme--colors--warning-400: #ffda43;
--c--theme--colors--warning-500: #ffc805;
--c--theme--colors--warning-600: #dba603;
--c--theme--colors--warning-700: #b78702;
--c--theme--colors--warning-800: #936901;
--c--theme--colors--warning-900: #7a5400;
--c--theme--colors--danger-100: #f4b0b0;
--c--theme--colors--danger-200: #ee8a8a;
--c--theme--colors--danger-300: #e65454;
--c--theme--colors--danger-400: #e13333;
--c--theme--colors--danger-500: #da0000;
--c--theme--colors--danger-600: #c60000;
--c--theme--colors--danger-700: #9b0000;
--c--theme--colors--danger-800: #780000;
--c--theme--colors--danger-900: #5c0000;
--c--theme--colors--primary-text: var(--c--theme--colors--greyscale-000);
--c--theme--colors--success-text: var(--c--theme--colors--greyscale-000);
--c--theme--colors--warning-text: var(--c--theme--colors--greyscale-000);
--c--theme--colors--danger-text: var(--c--theme--colors--greyscale-000);
--c--theme--colors--card-border: #ededed;
--c--theme--colors--primary-bg: #fafafa;
--c--theme--colors--primary-action: #1212ff;
--c--theme--colors--primary-100: #ececfe;
--c--theme--colors--primary-200: #e3e3fd;
--c--theme--colors--primary-300: #cacafb;
--c--theme--colors--primary-400: #8585f6;
--c--theme--colors--primary-500: #6a6af4;
--c--theme--colors--primary-600: #313178;
--c--theme--colors--primary-700: #272747;
--c--theme--colors--primary-800: #000091;
--c--theme--colors--primary-900: #21213f;
--c--theme--colors--success-100: #dffee6;
--c--theme--colors--success-200: #b8fec9;
--c--theme--colors--success-300: #88fdaa;
--c--theme--colors--success-400: #3bea7e;
--c--theme--colors--success-500: #1f8d49;
--c--theme--colors--success-600: #18753c;
--c--theme--colors--success-700: #204129;
--c--theme--colors--success-800: #1e2e22;
--c--theme--colors--success-900: #19281d;
--c--theme--colors--warning-100: #fff4f3;
--c--theme--colors--warning-200: #ffe9e6;
--c--theme--colors--warning-300: #ffded9;
--c--theme--colors--warning-400: #ffbeb4;
--c--theme--colors--warning-500: #d64d00;
--c--theme--colors--warning-600: #b34000;
--c--theme--colors--warning-700: #5e2c21;
--c--theme--colors--warning-800: #3e241e;
--c--theme--colors--warning-900: #361e19;
--c--theme--colors--danger-100: #ffe9e9;
--c--theme--colors--danger-200: #fdd;
--c--theme--colors--danger-300: #ffbdbd;
--c--theme--colors--danger-400: #ff5655;
--c--theme--colors--danger-500: #f60700;
--c--theme--colors--danger-600: #ce0500;
--c--theme--colors--danger-700: #642626;
--c--theme--colors--danger-800: #412121;
--c--theme--colors--danger-900: #391c1c;
--c--theme--colors--primary-text: #000091;
--c--theme--colors--success-text: #1f8d49;
--c--theme--colors--warning-text: #d64d00;
--c--theme--colors--danger-text: #fff;
--c--theme--colors--primary-050: #f5f5fe;
--c--theme--colors--primary-150: #e5eefa;
--c--theme--colors--primary-950: #1b1b35;
--c--theme--colors--info-150: #e5eefa;
--c--theme--colors--primary-150: #f4f4fd;
--c--theme--colors--greyscale-text: #303c4b;
--c--theme--colors--greyscale-050: #f6f6f6;
--c--theme--colors--greyscale-250: #ddd;
--c--theme--colors--greyscale-350: #ddd;
--c--theme--colors--greyscale-750: #353535;
--c--theme--colors--greyscale-950: #1e1e1e;
--c--theme--colors--greyscale-1000: #161616;
--c--theme--colors--primary-action: #1212ff;
--c--theme--colors--primary-bg: #fafafa;
--c--theme--colors--blue-400: #7ab1e8;
--c--theme--colors--blue-500: #417dc4;
--c--theme--colors--blue-600: #3558a2;
@@ -135,8 +138,8 @@
--c--theme--font--weights--bold: 600;
--c--theme--font--weights--extrabold: 800;
--c--theme--font--weights--black: 900;
--c--theme--font--families--base: 'Roboto Flex Variable', sans-serif;
--c--theme--font--families--accent: 'Roboto Flex Variable', sans-serif;
--c--theme--font--families--base: marianne;
--c--theme--font--families--accent: marianne;
--c--theme--font--letterspacings--h1: normal;
--c--theme--font--letterspacings--h2: normal;
--c--theme--font--letterspacings--h3: normal;
@@ -182,170 +185,271 @@
--c--theme--breakpoints--xl: 1200px;
--c--theme--breakpoints--xxl: 1400px;
--c--theme--breakpoints--xxs: 320px;
--c--theme--logo--src: ;
--c--theme--logo--widthheader: ;
--c--theme--logo--widthfooter: ;
--c--theme--logo--alt: ;
--c--components--datagrid--header--weight: var(
--c--theme--font--weights--extrabold
);
--c--components--datagrid--header--size: var(--c--theme--font--sizes--ml);
--c--components--datagrid--cell--color: var(--c--theme--colors--primary-500);
--c--components--datagrid--cell--size: var(--c--theme--font--sizes--ml);
--c--components--forms-checkbox--background-color--hover: #055fd214;
--c--components--forms-checkbox--color: var(--c--theme--colors--primary-500);
--c--components--forms-checkbox--font-size: var(--c--theme--font--sizes--ml);
--c--components--forms-datepicker--border-color: var(
--c--theme--colors--primary-500
);
--c--components--forms-datepicker--value-color: var(
--c--theme--colors--primary-500
);
--c--components--forms-datepicker--border-radius--hover: var(
--c--components--forms-datepicker--border-radius
);
--c--components--forms-datepicker--border-radius--focus: var(
--c--components--forms-datepicker--border-radius
);
--c--components--forms-field--color: var(--c--theme--colors--primary-500);
--c--components--forms-field--value-color: var(
--c--theme--colors--primary-500
);
--c--components--forms-field--width: auto;
--c--components--forms-input--value-color: var(
--c--theme--colors--primary-500
);
--c--components--forms-input--border-color: var(
--c--theme--colors--primary-500
);
--c--components--forms-input--color--error: var(
--c--theme--colors--danger-500
);
--c--components--forms-input--color--error-hover: var(
--c--theme--colors--danger-500
);
--c--components--forms-input--color--box-shadow-error-hover: var(
--c--theme--colors--danger-500
);
--c--components--forms-labelledbox--label-color--small: var(
--c--theme--colors--primary-500
);
--c--components--forms-labelledbox--label-color--small-disabled: var(
--c--theme--colors--greyscale-400
);
--c--components--forms-labelledbox--label-color--big--disabled: var(
--c--theme--colors--greyscale-400
);
--c--components--forms-select--border-color: var(
--c--theme--colors--primary-500
);
--c--components--forms-select--border-color-disabled-hover: var(
--c--theme--colors--greyscale-200
);
--c--components--forms-select--border-radius--hover: var(
--c--components--forms-select--border-radius
);
--c--components--forms-select--border-radius--focus: var(
--c--components--forms-select--border-radius
);
--c--components--forms-select--font-size: var(--c--theme--font--sizes--ml);
--c--components--forms-select--menu-background-color: #fff;
--c--components--forms-select--item-background-color--hover: var(
--c--theme--colors--primary-300
);
--c--components--forms-switch--accent-color: var(
--c--theme--colors--primary-400
);
--c--components--forms-textarea--border-color: var(
--c--components--forms-textarea--border-color
);
--c--components--forms-textarea--border-color-hover: var(
--c--components--forms-textarea--border-color
);
--c--components--forms-textarea--border-radius--hover: var(
--c--components--forms-textarea--border-radius
);
--c--components--forms-textarea--border-radius--focus: var(
--c--components--forms-textarea--border-radius
);
--c--components--forms-textarea--color: var(--c--theme--colors--primary-500);
--c--components--forms-textarea--disabled--border-color-hover: var(
--c--theme--colors--greyscale-200
);
--c--components--modal--background-color: #fff;
--c--components--button--border-radius--active: var(
--c--components--button--border-radius
);
--c--components--button--medium-height: auto;
--c--components--button--small-height: auto;
--c--components--button--success--color: white;
--c--components--button--success--color-disabled: white;
--c--components--button--success--color-hover: white;
--c--components--button--success--background--color: var(
--c--theme--colors--success-600
);
--c--components--button--success--background--color-disabled: var(
--c--theme--colors--greyscale-300
);
--c--components--button--success--background--color-hover: var(
--c--theme--colors--success-800
);
--c--components--button--danger--color-hover: white;
--c--components--button--danger--background--color: var(
--c--theme--colors--danger-600
);
--c--components--button--danger--background--color-hover: #ff2725;
--c--components--button--danger--background--color-disabled: var(
--c--theme--colors--danger-100
);
--c--components--button--primary--color: var(
--c--theme--colors--primary-text
);
--c--components--button--primary--color-active: var(
--c--theme--colors--primary-text
);
--c--theme--breakpoints--mobile: 768px;
--c--theme--breakpoints--tablet: 1024px;
--c--theme--logo--src: /assets/logo-gouv.svg;
--c--theme--logo--widthheader: 110px;
--c--theme--logo--widthfooter: 220px;
--c--theme--logo--alt: gouvernement logo;
--c--components--modal--width-small: 342px;
--c--components--button--medium-height: 40px;
--c--components--button--medium-text-height: 40px;
--c--components--button--border-radius: 4px;
--c--components--button--small-height: 26px;
--c--components--button--primary--background--color: var(
--c--theme--colors--primary-400
);
--c--components--button--primary--background--color-active: var(
--c--theme--colors--primary-500
);
--c--components--button--primary--border--color-active: transparent;
--c--components--button--secondary--color: var(
--c--theme--colors--primary-500
);
--c--components--button--secondary--color-hover: var(
--c--theme--colors--primary-text
);
--c--components--button--secondary--background--color: white;
--c--components--button--secondary--background--color-hover: var(
--c--theme--colors--primary-700
--c--components--button--primary--background--color-hover: #1212ff;
--c--components--button--primary--background--color-active: #2323ff;
--c--components--button--primary--background--color-disabled: var(
--c--theme--colors--greyscale-100
);
--c--components--button--primary--color: #fff;
--c--components--button--primary--color-hover: #fff;
--c--components--button--primary--color-active: #fff;
--c--components--button--primary--color-focus-visible: #fff;
--c--components--button--primary--disabled: var(
--c--theme--colors--greyscale-500
);
--c--components--button--primary-text--background--color: var(
--c--theme--colors--primary-text
);
--c--components--button--primary-text--background--color-hover: var(
--c--theme--colors--greyscale-100
);
--c--components--button--primary-text--background--color-active: var(
--c--theme--colors--primary-100
);
--c--components--button--primary-text--background--color-focus-visible: #fff;
--c--components--button--primary-text--background--color-disabled: var(
--c--theme--colors--greyscale-000
);
--c--components--button--primary-text--color: var(
--c--theme--colors--primary-800
);
--c--components--button--primary-text--color-hover: var(
--c--theme--colors--primary-800
);
--c--components--button--primary-text--disabled: var(
--c--theme--colors--greyscale-400
);
--c--components--button--secondary--background--color-hover: #f6f6f6;
--c--components--button--secondary--background--color-active: #ededed;
--c--components--button--secondary--background--color-focus-visible: var(
--c--theme--colors--greyscale-000
);
--c--components--button--secondary--background--disabled: var(
--c--theme--colors--greyscale-000
);
--c--components--button--secondary--color: var(
--c--theme--colors--primary-800
);
--c--components--button--secondary--border--color: var(
--c--theme--colors--greyscale-300
);
--c--components--button--tertiary--color: var(
--c--theme--colors--primary-text
--c--components--button--secondary--border--color-hover: var(
--c--theme--colors--greyscale-300
);
--c--components--button--tertiary--color-disabled: var(
--c--theme--colors--greyscale-600
--c--components--button--secondary--border--color-disabled: var(
--c--theme--colors--greyscale-300
);
--c--components--button--secondary--disabled: var(
--c--theme--colors--greyscale-400
);
--c--components--button--tertiary--background--color: var(
--c--theme--colors--primary-100
);
--c--components--button--tertiary--background--color-focus-visible: var(
--c--theme--colors--primary-100
);
--c--components--button--tertiary--background--color-hover: var(
--c--theme--colors--primary-300
);
--c--components--button--tertiary--background--color-active: var(
--c--theme--colors--primary-100
--c--theme--colors--primary-300
);
--c--components--button--tertiary--background--color-disabled: var(
--c--components--button--tertiary--background--disabled: var(
--c--theme--colors--primary-050
);
--c--components--button--tertiary--color: var(
--c--theme--colors--primary-800
);
--c--components--button--tertiary--disabled: var(
--c--theme--colors--primary-300
);
--c--components--button--tertiary-text--background--color-hover: var(
--c--theme--colors--greyscale-100
);
--c--components--button--tertiary-text--color-hover: var(
--c--theme--colors--primary-text
);
--c--components--button--tertiary-text--color: var(
--c--theme--colors--primary-600
);
--c--components--button--danger--color-hover: white;
--c--components--button--danger--background--color: var(
--c--theme--colors--danger-600
);
--c--components--button--danger--background--color-hover: #ff2725;
--c--components--button--danger--background--color-focus-visible: var(
--c--theme--colors--danger-600
);
--c--components--button--danger--background--color-disabled: var(
--c--theme--colors--greyscale-100
);
--c--components--button--danger--color-disabled: var(
--c--theme--colors--greyscale-400
);
--c--components--datagrid--header--color: var(
--c--theme--colors--greyscale-600
);
--c--components--datagrid--header--size: 12px;
--c--components--datagrid--header--weight: 500;
--c--components--datagrid--body--background-color-hover: var(
--c--theme--colors--greyscale-100
);
--c--components--forms-checkbox--border-radius: 4px;
--c--components--forms-checkbox--border-color: var(
--c--theme--colors--primary-800
);
--c--components--forms-checkbox--background-color--hover: var(
--c--theme--colors--greyscale-100
);
--c--components--forms-checkbox--border--color-disabled: var(
--c--theme--colors--greyscale-200
);
--c--components--button--disabled--color: white;
--c--components--button--disabled--background--color: #b3cef0;
--c--components--la-gauffre--activated: false;
--c--components--home-proconnect--activated: false;
--c--components--forms-checkbox--border--color: var(
--c--theme--colors--primary-800
);
--c--components--forms-checkbox--background--disabled: var(
--c--theme--colors--greyscale-200
);
--c--components--forms-checkbox--background--enable: var(
--c--theme--colors--primary-800
);
--c--components--forms-checkbox--check--disabled: var(
--c--theme--colors--greyscale-300
);
--c--components--forms-checkbox--check--enable: var(
--c--theme--colors--greyscale-000
);
--c--components--forms-checkbox--color: var(--c--theme--colors--primary-text);
--c--components--forms-checkbox--label--color: var(
--c--theme--colors--greyscale-1000
);
--c--components--forms-checkbox--label--size: var(
--c--theme--font--sizes--sm
);
--c--components--forms-checkbox--label--weight: 500;
--c--components--forms-checkbox--text--color: var(
--c--theme--colors--greyscale-600
);
--c--components--forms-checkbox--text--size: var(--c--theme--font--sizes--s);
--c--components--forms-checkbox--text--weight: 400;
--c--components--forms-checkbox--text--color-disabled: var(
--c--theme--colors--greyscale-300
);
--c--components--forms-labelledbox--label-color--small: var(
--c--theme--colors--greyscale-950
);
--c--components--forms-labelledbox--label-color--small--disabled: var(
--c--theme--colors--greyscale-300
);
--c--components--forms-labelledbox--label-color--big: var(
--c--theme--colors--greyscale-950
);
--c--components--forms-labelledbox--label-color--big--disabled: var(
--c--theme--colors--greyscale-300
);
--c--components--forms-radio--border-color: var(
--c--theme--colors--primary-800
);
--c--components--forms-radio--background-color: var(
--c--theme--colors--greyscale-000
);
--c--components--forms-radio--accent-color: var(
--c--theme--colors--primary-800
);
--c--components--forms-radio--accent-color-disabled: var(
--c--theme--colors--greyscale-300
);
--c--components--forms-switch--border--color-disabled: var(
--c--theme--colors--greyscale-300
);
--c--components--forms-switch--border--color: var(
--c--theme--colors--primary-800
);
--c--components--forms-switch--handle-background-color: white;
--c--components--forms-switch--handle-background-color--disabled: var(
--c--theme--colors--greyscale-000
);
--c--components--forms-switch--rail-background-color--disabled: var(
--c--theme--colors--greyscale-000
);
--c--components--forms-switch--accent-color: var(
--c--theme--colors--primary-800
);
--c--components--forms-textarea--label-color--focus: var(
--c--theme--colors--greyscale-1000
);
--c--components--forms-textarea--border-radius: 4px;
--c--components--forms-textarea--border-color: var(
--c--theme--colors--greyscale-400
);
--c--components--forms-textarea--box-shadow--color--hover: var(
--c--theme--colors--greyscale-400
);
--c--components--forms-textarea--box-shadow--color--focus: var(
--c--theme--colors--primary-800
);
--c--components--forms-textarea--value-color: var(
--c--theme--colors--greyscale-950
);
--c--components--forms-textarea--value-color--disabled: var(
--c--theme--colors--greyscale-300
);
--c--components--forms-textarea--font-size: 14px;
--c--components--forms-input--label-color--focus: var(
--c--theme--colors--greyscale-1000
);
--c--components--forms-input--border-radius: 4px;
--c--components--forms-input--border-color: var(
--c--theme--colors--greyscale-400
);
--c--components--forms-input--box-shadow--color--hover: var(
--c--theme--colors--greyscale-400
);
--c--components--forms-input--box-shadow--color--focus: var(
--c--theme--colors--primary-800
);
--c--components--forms-input--value-color: var(
--c--theme--colors--greyscale-950
);
--c--components--forms-input--value-color--disabled: var(
--c--theme--colors--greyscale-300
);
--c--components--forms-input--font-size: 14px;
--c--components--forms-select--label-color--focus: var(
--c--theme--colors--greyscale-1000
);
--c--components--forms-select--item-font-size: 14px;
--c--components--forms-select--border-radius: 4px;
--c--components--forms-select--border-radius-hover: 4px;
--c--components--forms-select--border-color: var(
--c--theme--colors--greyscale-400
);
--c--components--forms-select--box-shadow--color--hover: var(
--c--theme--colors--greyscale-400
);
--c--components--forms-select--box-shadow--color--focus: var(
--c--theme--colors--primary-800
);
--c--components--forms-select--value-color: var(
--c--theme--colors--greyscale-950
);
--c--components--forms-select--font-size: 14px;
--c--components--la-gauffre--activated: true;
--c--components--home-proconnect--activated: true;
}
.cunningham-theme--dark {
@@ -397,219 +501,6 @@
--c--theme--colors--danger-900: #9d6666;
}
.cunningham-theme--dsfr {
--c--theme--colors--card-border: #e5e5e5;
--c--theme--colors--primary-text: #000091;
--c--theme--colors--primary-100: #ececfe;
--c--theme--colors--primary-150: #f4f4fd;
--c--theme--colors--primary-200: #e3e3fd;
--c--theme--colors--primary-300: #cacafb;
--c--theme--colors--primary-400: #8585f6;
--c--theme--colors--primary-500: #6a6af4;
--c--theme--colors--primary-600: #313178;
--c--theme--colors--primary-700: #272747;
--c--theme--colors--primary-800: #000091;
--c--theme--colors--primary-900: #21213f;
--c--theme--colors--secondary-text: #fff;
--c--theme--colors--secondary-100: #fee9ea;
--c--theme--colors--secondary-200: #fedfdf;
--c--theme--colors--secondary-300: #fdbfbf;
--c--theme--colors--secondary-400: #e1020f;
--c--theme--colors--secondary-500: #c91a1f;
--c--theme--colors--secondary-600: #5e2b2b;
--c--theme--colors--secondary-700: #3b2424;
--c--theme--colors--secondary-800: #341f1f;
--c--theme--colors--secondary-900: #2b1919;
--c--theme--colors--greyscale-text: #303c4b;
--c--theme--colors--greyscale-000: #fff;
--c--theme--colors--greyscale-050: #f6f6f6;
--c--theme--colors--greyscale-100: #eee;
--c--theme--colors--greyscale-200: #e5e5e5;
--c--theme--colors--greyscale-250: #ddd;
--c--theme--colors--greyscale-300: #cecece;
--c--theme--colors--greyscale-350: #ddd;
--c--theme--colors--greyscale-400: #929292;
--c--theme--colors--greyscale-500: #7c7c7c;
--c--theme--colors--greyscale-600: #666;
--c--theme--colors--greyscale-700: #3a3a3a;
--c--theme--colors--greyscale-750: #353535;
--c--theme--colors--greyscale-800: #2a2a2a;
--c--theme--colors--greyscale-900: #242424;
--c--theme--colors--greyscale-950: #1e1e1e;
--c--theme--colors--greyscale-1000: #161616;
--c--theme--colors--success-text: #1f8d49;
--c--theme--colors--success-100: #dffee6;
--c--theme--colors--success-200: #b8fec9;
--c--theme--colors--success-300: #88fdaa;
--c--theme--colors--success-400: #3bea7e;
--c--theme--colors--success-500: #1f8d49;
--c--theme--colors--success-600: #18753c;
--c--theme--colors--success-700: #204129;
--c--theme--colors--success-800: #1e2e22;
--c--theme--colors--success-900: #19281d;
--c--theme--colors--info-text: #0078f3;
--c--theme--colors--info-100: #e8edff;
--c--theme--colors--info-200: #dde5ff;
--c--theme--colors--info-300: #bccdff;
--c--theme--colors--info-400: #518fff;
--c--theme--colors--info-500: #0078f3;
--c--theme--colors--info-600: #0063cb;
--c--theme--colors--info-700: #273961;
--c--theme--colors--info-800: #222a3f;
--c--theme--colors--info-900: #1d2437;
--c--theme--colors--warning-text: #d64d00;
--c--theme--colors--warning-100: #fff4f3;
--c--theme--colors--warning-200: #ffe9e6;
--c--theme--colors--warning-300: #ffded9;
--c--theme--colors--warning-400: #ffbeb4;
--c--theme--colors--warning-500: #d64d00;
--c--theme--colors--warning-600: #b34000;
--c--theme--colors--warning-700: #5e2c21;
--c--theme--colors--warning-800: #3e241e;
--c--theme--colors--warning-900: #361e19;
--c--theme--colors--danger-text: #fff;
--c--theme--colors--danger-100: #ffe9e9;
--c--theme--colors--danger-200: #fdd;
--c--theme--colors--danger-300: #ffbdbd;
--c--theme--colors--danger-400: #ff5655;
--c--theme--colors--danger-500: #f60700;
--c--theme--colors--danger-600: #ce0500;
--c--theme--colors--danger-700: #642626;
--c--theme--colors--danger-800: #412121;
--c--theme--colors--danger-900: #391c1c;
--c--theme--font--families--accent: marianne;
--c--theme--font--families--base: marianne;
--c--theme--logo--src: /assets/logo-gouv.svg;
--c--theme--logo--widthHeader: 110px;
--c--theme--logo--widthFooter: 220px;
--c--theme--logo--alt: gouvernement logo;
--c--components--alert--border-radius: 0;
--c--components--alert--error--background-color: var(
--c--theme--colors--danger-100
);
--c--components--alert--error--border-left-color: var(
--c--theme--colors--danger-400
);
--c--components--alert--error--close--color: white;
--c--components--alert--error--close--background-color: var(
--c--theme--colors--danger-400
);
--c--components--alert--error--close--background-color-hover: var(
--c--theme--colors--danger-600
);
--c--components--modal--width-small: 342px;
--c--components--button--medium-height: 40px;
--c--components--button--medium-text-height: 40px;
--c--components--button--border-radius: 4px;
--c--components--button--primary--background--color: var(
--c--theme--colors--primary-text
);
--c--components--button--primary--background--color-hover: #1212ff;
--c--components--button--primary--background--color-active: #2323ff;
--c--components--button--primary--color: #fff;
--c--components--button--primary--color-hover: #fff;
--c--components--button--primary--color-active: #fff;
--c--components--button--primary-text--background--color-hover: var(
--c--theme--colors--primary-100
);
--c--components--button--primary-text--background--color-active: var(
--c--theme--colors--primary-100
);
--c--components--button--primary-text--color-hover: var(
--c--theme--colors--primary-text
);
--c--components--button--primary-text--color: var(
--c--theme--colors--primary-800
);
--c--components--button--secondary--background--color-hover: #f6f6f6;
--c--components--button--secondary--background--color-active: #ededed;
--c--components--button--secondary--border--color: var(
--c--theme--colors--greyscale-300
);
--c--components--button--secondary--border--color-hover: var(
--c--theme--colors--greyscale-300
);
--c--components--button--secondary--color: var(
--c--theme--colors--primary-text
);
--c--components--button--tertiary-text--background--color-hover: var(
--c--theme--colors--greyscale-100
);
--c--components--button--tertiary-text--color-hover: var(
--c--theme--colors--primary-text
);
--c--components--button--tertiary-text--color: var(
--c--theme--colors--primary-600
);
--c--components--datagrid--header--color: var(
--c--theme--colors--primary-text
);
--c--components--datagrid--header--size: var(--c--theme--font--sizes--s);
--c--components--datagrid--body--background-color: transparent;
--c--components--datagrid--body--background-color-hover: #f4f4fd;
--c--components--datagrid--pagination--background-color: transparent;
--c--components--datagrid--pagination--background-color-active: var(
--c--theme--colors--primary-300
);
--c--components--datagrid--pagination--border-color: var(
--c--theme--colors--primary-400
);
--c--components--forms-checkbox--border-radius: 0;
--c--components--forms-checkbox--color: var(--c--theme--colors--primary-text);
--c--components--forms-checkbox--text--color: var(
--c--theme--colors--greyscale-text
);
--c--components--forms-checkbox--text--size: var(--c--theme--font--sizes--t);
--c--components--forms-datepicker--border-radius: 0;
--c--components--forms-fileuploader--border-radius: 0;
--c--components--forms-field--color: var(--c--theme--colors--primary-text);
--c--components--forms-field--footer-font-size: var(
--c--theme--font--sizes--t
);
--c--components--forms-field--footer-color: var(
--c--theme--colors--greyscale-text
);
--c--components--forms-input--border-radius: 4px;
--c--components--forms-input--background-color: #fff;
--c--components--forms-input--border-color: var(
--c--theme--colors--primary-text
);
--c--components--forms-input--box-shadow-color: var(
--c--theme--colors--primary-text
);
--c--components--forms-input--value-color: var(
--c--theme--colors--primary-text
);
--c--components--forms-input--font-size: 14px;
--c--components--forms-labelledbox--label-color--big: var(
--c--theme--colors--primary-text
);
--c--components--forms-radio--accent-color: var(
--c--theme--colors--primary-600
);
--c--components--forms-select--item-font-size: 14px;
--c--components--forms-select--border-radius: 4px;
--c--components--forms-select--border-radius-hover: 4px;
--c--components--forms-select--background-color: #fff;
--c--components--forms-select--border-color: var(
--c--theme--colors--primary-text
);
--c--components--forms-select--border-color-hover: var(
--c--theme--colors--primary-text
);
--c--components--forms-select--box-shadow-color: var(
--c--theme--colors--primary-text
);
--c--components--forms-switch--handle-border-radius: 2px;
--c--components--forms-switch--rail-border-radius: 4px;
--c--components--forms-switch--accent-color: var(
--c--theme--colors--primary-text
);
--c--components--forms-textarea--border-radius: 0;
--c--components--la-gauffre--activated: true;
--c--components--home-proconnect--activated: true;
}
.clr-secondary-text {
color: var(--c--theme--colors--secondary-text);
}
@@ -890,18 +781,6 @@
color: var(--c--theme--colors--danger-text);
}
.clr-card-border {
color: var(--c--theme--colors--card-border);
}
.clr-primary-bg {
color: var(--c--theme--colors--primary-bg);
}
.clr-primary-action {
color: var(--c--theme--colors--primary-action);
}
.clr-primary-050 {
color: var(--c--theme--colors--primary-050);
}
@@ -910,18 +789,42 @@
color: var(--c--theme--colors--primary-150);
}
.clr-primary-950 {
color: var(--c--theme--colors--primary-950);
.clr-greyscale-text {
color: var(--c--theme--colors--greyscale-text);
}
.clr-info-150 {
color: var(--c--theme--colors--info-150);
.clr-greyscale-050 {
color: var(--c--theme--colors--greyscale-050);
}
.clr-greyscale-250 {
color: var(--c--theme--colors--greyscale-250);
}
.clr-greyscale-350 {
color: var(--c--theme--colors--greyscale-350);
}
.clr-greyscale-750 {
color: var(--c--theme--colors--greyscale-750);
}
.clr-greyscale-950 {
color: var(--c--theme--colors--greyscale-950);
}
.clr-greyscale-1000 {
color: var(--c--theme--colors--greyscale-1000);
}
.clr-primary-action {
color: var(--c--theme--colors--primary-action);
}
.clr-primary-bg {
color: var(--c--theme--colors--primary-bg);
}
.clr-blue-400 {
color: var(--c--theme--colors--blue-400);
}
@@ -1322,18 +1225,6 @@
background-color: var(--c--theme--colors--danger-text);
}
.bg-card-border {
background-color: var(--c--theme--colors--card-border);
}
.bg-primary-bg {
background-color: var(--c--theme--colors--primary-bg);
}
.bg-primary-action {
background-color: var(--c--theme--colors--primary-action);
}
.bg-primary-050 {
background-color: var(--c--theme--colors--primary-050);
}
@@ -1342,18 +1233,42 @@
background-color: var(--c--theme--colors--primary-150);
}
.bg-primary-950 {
background-color: var(--c--theme--colors--primary-950);
.bg-greyscale-text {
background-color: var(--c--theme--colors--greyscale-text);
}
.bg-info-150 {
background-color: var(--c--theme--colors--info-150);
.bg-greyscale-050 {
background-color: var(--c--theme--colors--greyscale-050);
}
.bg-greyscale-250 {
background-color: var(--c--theme--colors--greyscale-250);
}
.bg-greyscale-350 {
background-color: var(--c--theme--colors--greyscale-350);
}
.bg-greyscale-750 {
background-color: var(--c--theme--colors--greyscale-750);
}
.bg-greyscale-950 {
background-color: var(--c--theme--colors--greyscale-950);
}
.bg-greyscale-1000 {
background-color: var(--c--theme--colors--greyscale-1000);
}
.bg-primary-action {
background-color: var(--c--theme--colors--primary-action);
}
.bg-primary-bg {
background-color: var(--c--theme--colors--primary-bg);
}
.bg-blue-400 {
background-color: var(--c--theme--colors--blue-400);
}
@@ -3,84 +3,87 @@ export const tokens = {
default: {
theme: {
colors: {
'secondary-text': '#555F6B',
'secondary-100': '#F2F7FC',
'secondary-200': '#EBF3FA',
'secondary-300': '#E2EEF8',
'secondary-400': '#DDEAF7',
'secondary-500': '#D4E5F5',
'secondary-600': '#C1D0DF',
'secondary-700': '#97A3AE',
'secondary-800': '#757E87',
'secondary-900': '#596067',
'info-text': '#fff',
'info-100': '#EBF2FC',
'info-200': '#8CB5EA',
'info-300': '#5894E1',
'info-400': '#377FDB',
'info-500': '#055FD2',
'info-600': '#0556BF',
'info-700': '#044395',
'info-800': '#033474',
'info-900': '#022858',
'greyscale-100': '#FAFAFB',
'greyscale-200': '#F3F4F4',
'greyscale-300': '#E7E8EA',
'greyscale-400': '#C2C6CA',
'greyscale-500': '#9EA3AA',
'greyscale-600': '#79818A',
'greyscale-700': '#555F6B',
'greyscale-800': '#303C4B',
'greyscale-900': '#0C1A2B',
'secondary-text': '#fff',
'secondary-100': '#fee9ea',
'secondary-200': '#fedfdf',
'secondary-300': '#fdbfbf',
'secondary-400': '#e1020f',
'secondary-500': '#c91a1f',
'secondary-600': '#5e2b2b',
'secondary-700': '#3b2424',
'secondary-800': '#341f1f',
'secondary-900': '#2b1919',
'info-text': '#0078f3',
'info-100': '#E8EDFF',
'info-200': '#DDE5FF',
'info-300': '#BCCDFF',
'info-400': '#518FFF',
'info-500': '#0078F3',
'info-600': '#0063CB',
'info-700': '#273961',
'info-800': '#222A3F',
'info-900': '#1D2437',
'greyscale-100': '#eee',
'greyscale-200': '#E5E5E5',
'greyscale-300': '#CECECE',
'greyscale-400': '#929292',
'greyscale-500': '#7C7C7C',
'greyscale-600': '#666666',
'greyscale-700': '#3A3A3A',
'greyscale-800': '#2A2A2A',
'greyscale-900': '#242424',
'greyscale-000': '#fff',
'primary-100': '#EDF5FA',
'primary-200': '#8CB5EA',
'primary-300': '#5894E1',
'primary-400': '#377FDB',
'primary-500': '#055FD2',
'primary-600': '#0556BF',
'primary-700': '#044395',
'primary-800': '#033474',
'primary-900': '#022858',
'success-100': '#EFFCD3',
'success-200': '#DBFAA9',
'success-300': '#BEF27C',
'success-400': '#A0E659',
'success-500': '#76D628',
'success-600': '#5AB81D',
'success-700': '#419A14',
'success-800': '#2C7C0C',
'success-900': '#1D6607',
'warning-100': '#FFF8CD',
'warning-200': '#FFEF9B',
'warning-300': '#FFE469',
'warning-400': '#FFDA43',
'warning-500': '#FFC805',
'warning-600': '#DBA603',
'warning-700': '#B78702',
'warning-800': '#936901',
'warning-900': '#7A5400',
'danger-100': '#F4B0B0',
'danger-200': '#EE8A8A',
'danger-300': '#E65454',
'danger-400': '#E13333',
'danger-500': '#DA0000',
'danger-600': '#C60000',
'danger-700': '#9B0000',
'danger-800': '#780000',
'danger-900': '#5C0000',
'primary-text': '#fff',
'success-text': '#fff',
'warning-text': '#fff',
'danger-text': '#fff',
'card-border': '#ededed',
'primary-bg': '#FAFAFA',
'primary-action': '#1212FF',
'primary-100': '#ECECFE',
'primary-200': '#E3E3FD',
'primary-300': '#CACAFB',
'primary-400': '#8585F6',
'primary-500': '#6A6AF4',
'primary-600': '#313178',
'primary-700': '#272747',
'primary-800': '#000091',
'primary-900': '#21213F',
'success-100': '#dffee6',
'success-200': '#b8fec9',
'success-300': '#88fdaa',
'success-400': '#3bea7e',
'success-500': '#1f8d49',
'success-600': '#18753c',
'success-700': '#204129',
'success-800': '#1e2e22',
'success-900': '#19281d',
'warning-100': '#fff4f3',
'warning-200': '#ffe9e6',
'warning-300': '#ffded9',
'warning-400': '#ffbeb4',
'warning-500': '#d64d00',
'warning-600': '#b34000',
'warning-700': '#5e2c21',
'warning-800': '#3e241e',
'warning-900': '#361e19',
'danger-100': '#FFE9E9',
'danger-200': '#FFDDDD',
'danger-300': '#FFBDBD',
'danger-400': '#FF5655',
'danger-500': '#F60700',
'danger-600': '#CE0500',
'danger-700': '#642626',
'danger-800': '#412121',
'danger-900': '#391C1C',
'primary-text': '#000091',
'success-text': '#1f8d49',
'warning-text': '#d64d00',
'danger-text': '#FFF',
'primary-050': '#F5F5FE',
'primary-150': '#E5EEFA',
'primary-950': '#1B1B35',
'info-150': '#E5EEFA',
'primary-150': '#F4F4FD',
'greyscale-text': '#303C4B',
'greyscale-050': '#F6F6F6',
'greyscale-250': '#ddd',
'greyscale-350': '#ddd',
'greyscale-750': '#353535',
'greyscale-950': '#1E1E1E',
'greyscale-1000': '#161616',
'primary-action': '#1212FF',
'primary-bg': '#FAFAFA',
'blue-400': '#7AB1E8',
'blue-500': '#417DC4',
'blue-600': '#3558A2',
@@ -145,10 +148,7 @@ export const tokens = {
extrabold: 800,
black: 900,
},
families: {
base: '"Roboto Flex Variable", sans-serif',
accent: '"Roboto Flex Variable", sans-serif',
},
families: { base: 'Marianne', accent: 'Marianne' },
letterSpacings: {
h1: 'normal',
h2: 'normal',
@@ -202,141 +202,164 @@ export const tokens = {
xl: '1200px',
xxl: '1400px',
xxs: '320px',
mobile: '768px',
tablet: '1024px',
},
logo: {
src: '/assets/logo-gouv.svg',
widthHeader: '110px',
widthFooter: '220px',
alt: 'Gouvernement Logo',
},
logo: { src: '', widthHeader: '', widthFooter: '', alt: '' },
},
components: {
datagrid: {
header: {
weight: 'var(--c--theme--font--weights--extrabold)',
size: 'var(--c--theme--font--sizes--ml)',
},
cell: {
color: 'var(--c--theme--colors--primary-500)',
size: 'var(--c--theme--font--sizes--ml)',
},
},
'forms-checkbox': {
'background-color': { hover: '#055fd214' },
color: 'var(--c--theme--colors--primary-500)',
'font-size': 'var(--c--theme--font--sizes--ml)',
},
'forms-datepicker': {
'border-color': 'var(--c--theme--colors--primary-500)',
'value-color': 'var(--c--theme--colors--primary-500)',
'border-radius': {
hover: 'var(--c--components--forms-datepicker--border-radius)',
focus: 'var(--c--components--forms-datepicker--border-radius)',
},
},
'forms-field': {
color: 'var(--c--theme--colors--primary-500)',
'value-color': 'var(--c--theme--colors--primary-500)',
width: 'auto',
},
'forms-input': {
'value-color': 'var(--c--theme--colors--primary-500)',
'border-color': 'var(--c--theme--colors--primary-500)',
color: {
error: 'var(--c--theme--colors--danger-500)',
'error-hover': 'var(--c--theme--colors--danger-500)',
'box-shadow-error-hover': 'var(--c--theme--colors--danger-500)',
},
},
'forms-labelledbox': {
'label-color': {
small: 'var(--c--theme--colors--primary-500)',
'small-disabled': 'var(--c--theme--colors--greyscale-400)',
big: { disabled: 'var(--c--theme--colors--greyscale-400)' },
},
},
'forms-select': {
'border-color': 'var(--c--theme--colors--primary-500)',
'border-color-disabled-hover':
'var(--c--theme--colors--greyscale-200)',
'border-radius': {
hover: 'var(--c--components--forms-select--border-radius)',
focus: 'var(--c--components--forms-select--border-radius)',
},
'font-size': 'var(--c--theme--font--sizes--ml)',
'menu-background-color': '#fff',
'item-background-color': {
hover: 'var(--c--theme--colors--primary-300)',
},
},
'forms-switch': {
'accent-color': 'var(--c--theme--colors--primary-400)',
},
'forms-textarea': {
'border-color': 'var(--c--components--forms-textarea--border-color)',
'border-color-hover':
'var(--c--components--forms-textarea--border-color)',
'border-radius': {
hover: 'var(--c--components--forms-textarea--border-radius)',
focus: 'var(--c--components--forms-textarea--border-radius)',
},
color: 'var(--c--theme--colors--primary-500)',
disabled: {
'border-color-hover': 'var(--c--theme--colors--greyscale-200)',
},
},
modal: { 'background-color': '#fff' },
modal: { 'width-small': '342px' },
button: {
'border-radius': {
active: 'var(--c--components--button--border-radius)',
'medium-height': '40px',
'medium-text-height': '40px',
'border-radius': '4px',
'small-height': '26px',
primary: {
'background--color': 'var(--c--theme--colors--primary-text)',
'background--color-hover': '#1212ff',
'background--color-active': '#2323ff',
'background--color-disabled':
'var(--c--theme--colors--greyscale-100)',
color: '#fff',
'color-hover': '#fff',
'color-active': '#fff',
'color-focus-visible': '#fff',
disabled: 'var(--c--theme--colors--greyscale-500)',
},
'medium-height': 'auto',
'small-height': 'auto',
success: {
color: 'white',
'color-disabled': 'white',
'color-hover': 'white',
background: {
color: 'var(--c--theme--colors--success-600)',
'color-disabled': 'var(--c--theme--colors--greyscale-300)',
'color-hover': 'var(--c--theme--colors--success-800)',
},
'primary-text': {
'background--color': 'var(--c--theme--colors--primary-text)',
'background--color-hover': 'var(--c--theme--colors--greyscale-100)',
'background--color-active': 'var(--c--theme--colors--primary-100)',
'background--color-focus-visible': '#fff',
'background--color-disabled':
'var(--c--theme--colors--greyscale-000)',
color: 'var(--c--theme--colors--primary-800)',
'color-hover': 'var(--c--theme--colors--primary-800)',
disabled: 'var(--c--theme--colors--greyscale-400)',
},
secondary: {
'background--color-hover': '#F6F6F6',
'background--color-active': '#EDEDED',
'background--color-focus-visible':
'var(--c--theme--colors--greyscale-000)',
'background--disabled': 'var(--c--theme--colors--greyscale-000)',
color: 'var(--c--theme--colors--primary-800)',
'border--color': 'var(--c--theme--colors--greyscale-300)',
'border--color-hover': 'var(--c--theme--colors--greyscale-300)',
'border--color-disabled': 'var(--c--theme--colors--greyscale-300)',
disabled: 'var(--c--theme--colors--greyscale-400)',
},
tertiary: {
'background--color': 'var(--c--theme--colors--primary-100)',
'background--color-focus-visible':
'var(--c--theme--colors--primary-100)',
'background--color-hover': 'var(--c--theme--colors--primary-300)',
'background--color-active': 'var(--c--theme--colors--primary-300)',
'background--disabled': 'var(--c--theme--colors--primary-050)',
color: 'var(--c--theme--colors--primary-800)',
disabled: 'var(--c--theme--colors--primary-300)',
},
'tertiary-text': {
'background--color-hover': 'var(--c--theme--colors--greyscale-100)',
'color-hover': 'var(--c--theme--colors--primary-text)',
color: 'var(--c--theme--colors--primary-600)',
},
danger: {
'color-hover': 'white',
background: {
color: 'var(--c--theme--colors--danger-600)',
'color-hover': '#FF2725',
'color-disabled': 'var(--c--theme--colors--danger-100)',
},
'background--color': 'var(--c--theme--colors--danger-600)',
'background--color-hover': '#FF2725',
'background--color-focus-visible':
'var(--c--theme--colors--danger-600)',
'background--color-disabled':
'var(--c--theme--colors--greyscale-100)',
'color-disabled': 'var(--c--theme--colors--greyscale-400)',
},
primary: {
color: 'var(--c--theme--colors--primary-text)',
'color-active': 'var(--c--theme--colors--primary-text)',
background: {
color: 'var(--c--theme--colors--primary-400)',
'color-active': 'var(--c--theme--colors--primary-500)',
},
border: { 'color-active': 'transparent' },
},
secondary: {
color: 'var(--c--theme--colors--primary-500)',
'color-hover': 'var(--c--theme--colors--primary-text)',
background: {
color: 'white',
'color-hover': 'var(--c--theme--colors--primary-700)',
},
border: { color: 'var(--c--theme--colors--greyscale-300)' },
},
tertiary: {
color: 'var(--c--theme--colors--primary-text)',
'color-disabled': 'var(--c--theme--colors--greyscale-600)',
background: {
color: 'var(--c--theme--colors--primary-100)',
'color-hover': 'var(--c--theme--colors--primary-300)',
'color-active': 'var(--c--theme--colors--primary-100)',
'color-disabled': 'var(--c--theme--colors--greyscale-200)',
},
},
disabled: { color: 'white', background: { color: '#b3cef0' } },
},
'la-gauffre': { activated: false },
'home-proconnect': { activated: false },
datagrid: {
'header--color': '#666666',
'header--size': '12px',
'header--weight': '500',
'body--background-color-hover': '#eee',
},
'forms-checkbox': {
'border-radius': '4px',
'border-color': 'var(--c--theme--colors--primary-800)',
'background-color--hover': 'var(--c--theme--colors--greyscale-100)',
'border--color-disabled': 'var(--c--theme--colors--greyscale-200)',
'border--color': 'var(--c--theme--colors--primary-800)',
'background--disabled': 'var(--c--theme--colors--greyscale-200)',
'background--enable': 'var(--c--theme--colors--primary-800)',
'check--disabled': 'var(--c--theme--colors--greyscale-300)',
'check--enable': 'var(--c--theme--colors--greyscale-000)',
color: 'var(--c--theme--colors--primary-text)',
'label--color': 'var(--c--theme--colors--greyscale-1000)',
'label--size': 'var(--c--theme--font--sizes--sm)',
'label--weight': '500',
'text--color': 'var(--c--theme--colors--greyscale-600)',
'text--size': 'var(--c--theme--font--sizes--s)',
'text--weight': '400',
'text--color-disabled': 'var(--c--theme--colors--greyscale-300)',
},
'forms-labelledbox': {
'label-color--small': '#1E1E1E',
'label-color--small--disabled': '#CECECE',
'label-color--big': '#1E1E1E',
'label-color--big--disabled': '#CECECE',
},
'forms-radio': {
'border-color': 'var(--c--theme--colors--primary-800)',
'background-color': 'var(--c--theme--colors--greyscale-000)',
'accent-color': 'var(--c--theme--colors--primary-800)',
'accent-color-disabled': 'var(--c--theme--colors--greyscale-300)',
},
'forms-switch': {
'border--color-disabled': 'var(--c--theme--colors--greyscale-300)',
'border--color': 'var(--c--theme--colors--primary-800)',
'handle-background-color': 'white',
'handle-background-color--disabled':
'var(--c--theme--colors--greyscale-000)',
'rail-background-color--disabled':
'var(--c--theme--colors--greyscale-000)',
'accent-color': 'var(--c--theme--colors--primary-800)',
},
'forms-textarea': {
'label-color--focus': '#161616',
'border-radius': '4px',
'border-color': '#929292',
'box-shadow--color--hover': '#929292',
'box-shadow--color--focus': '#000091',
'value-color': '#1E1E1E',
'value-color--disabled': '#CECECE',
'font-size': '14px',
},
'forms-input': {
'label-color--focus': '#161616',
'border-radius': '4px',
'border-color': '#929292',
'box-shadow--color--hover': '#929292',
'box-shadow--color--focus': '#000091',
'value-color': '#1E1E1E',
'value-color--disabled': '#CECECE',
'font-size': '14px',
},
'forms-select': {
'label-color--focus': '#161616',
'item-font-size': '14px',
'border-radius': '4px',
'border-radius-hover': '4px',
'border-color': '#929292',
'box-shadow--color--hover': '#929292',
'box-shadow--color--focus': '#000091',
'value-color': '#1E1E1E',
'font-size': '14px',
},
'la-gauffre': { activated: true },
'home-proconnect': { activated: true },
},
},
dark: {
@@ -391,211 +414,5 @@ export const tokens = {
},
},
},
dsfr: {
theme: {
colors: {
'card-border': '#E5E5E5',
'primary-text': '#000091',
'primary-100': '#ECECFE',
'primary-150': '#F4F4FD',
'primary-200': '#E3E3FD',
'primary-300': '#CACAFB',
'primary-400': '#8585F6',
'primary-500': '#6A6AF4',
'primary-600': '#313178',
'primary-700': '#272747',
'primary-800': '#000091',
'primary-900': '#21213F',
'secondary-text': '#fff',
'secondary-100': '#fee9ea',
'secondary-200': '#fedfdf',
'secondary-300': '#fdbfbf',
'secondary-400': '#e1020f',
'secondary-500': '#c91a1f',
'secondary-600': '#5e2b2b',
'secondary-700': '#3b2424',
'secondary-800': '#341f1f',
'secondary-900': '#2b1919',
'greyscale-text': '#303C4B',
'greyscale-000': '#fff',
'greyscale-050': '#F6F6F6',
'greyscale-100': '#eee',
'greyscale-200': '#E5E5E5',
'greyscale-250': '#ddd',
'greyscale-300': '#CECECE',
'greyscale-350': '#ddd',
'greyscale-400': '#929292',
'greyscale-500': '#7C7C7C',
'greyscale-600': '#666666',
'greyscale-700': '#3A3A3A',
'greyscale-750': '#353535',
'greyscale-800': '#2A2A2A',
'greyscale-900': '#242424',
'greyscale-950': '#1E1E1E',
'greyscale-1000': '#161616',
'success-text': '#1f8d49',
'success-100': '#dffee6',
'success-200': '#b8fec9',
'success-300': '#88fdaa',
'success-400': '#3bea7e',
'success-500': '#1f8d49',
'success-600': '#18753c',
'success-700': '#204129',
'success-800': '#1e2e22',
'success-900': '#19281d',
'info-text': '#0078f3',
'info-100': '#E8EDFF',
'info-200': '#DDE5FF',
'info-300': '#BCCDFF',
'info-400': '#518FFF',
'info-500': '#0078F3',
'info-600': '#0063CB',
'info-700': '#273961',
'info-800': '#222A3F',
'info-900': '#1D2437',
'warning-text': '#d64d00',
'warning-100': '#fff4f3',
'warning-200': '#ffe9e6',
'warning-300': '#ffded9',
'warning-400': '#ffbeb4',
'warning-500': '#d64d00',
'warning-600': '#b34000',
'warning-700': '#5e2c21',
'warning-800': '#3e241e',
'warning-900': '#361e19',
'danger-text': '#FFF',
'danger-100': '#FFE9E9',
'danger-200': '#FFDDDD',
'danger-300': '#FFBDBD',
'danger-400': '#FF5655',
'danger-500': '#F60700',
'danger-600': '#CE0500',
'danger-700': '#642626',
'danger-800': '#412121',
'danger-900': '#391C1C',
},
font: { families: { accent: 'Marianne', base: 'Marianne' } },
logo: {
src: '/assets/logo-gouv.svg',
widthHeader: '110px',
widthFooter: '220px',
alt: 'Gouvernement Logo',
},
},
components: {
alert: {
'border-radius': '0',
error: {
'background-color': 'var(--c--theme--colors--danger-100)',
'border-left-color': 'var(--c--theme--colors--danger-400)',
close: {
color: 'white',
'background-color': 'var(--c--theme--colors--danger-400)',
'background-color-hover': 'var(--c--theme--colors--danger-600)',
},
},
},
modal: { 'width-small': '342px' },
button: {
'medium-height': '40px',
'medium-text-height': '40px',
'border-radius': '4px',
primary: {
background: {
color: 'var(--c--theme--colors--primary-text)',
'color-hover': '#1212ff',
'color-active': '#2323ff',
},
color: '#fff',
'color-hover': '#fff',
'color-active': '#fff',
},
'primary-text': {
background: {
'color-hover': 'var(--c--theme--colors--primary-100)',
'color-active': 'var(--c--theme--colors--primary-100)',
},
'color-hover': 'var(--c--theme--colors--primary-text)',
color: 'var(--c--theme--colors--primary-800)',
},
secondary: {
background: { 'color-hover': '#F6F6F6', 'color-active': '#EDEDED' },
border: {
color: 'var(--c--theme--colors--greyscale-300)',
'color-hover': 'var(--c--theme--colors--greyscale-300)',
},
color: 'var(--c--theme--colors--primary-text)',
},
'tertiary-text': {
background: {
'color-hover': 'var(--c--theme--colors--greyscale-100)',
},
'color-hover': 'var(--c--theme--colors--primary-text)',
color: 'var(--c--theme--colors--primary-600)',
},
},
datagrid: {
header: {
color: 'var(--c--theme--colors--primary-text)',
size: 'var(--c--theme--font--sizes--s)',
},
body: {
'background-color': 'transparent',
'background-color-hover': '#F4F4FD',
},
pagination: {
'background-color': 'transparent',
'background-color-active': 'var(--c--theme--colors--primary-300)',
'border-color': 'var(--c--theme--colors--primary-400)',
},
},
'forms-checkbox': {
'border-radius': '0',
color: 'var(--c--theme--colors--primary-text)',
text: {
color: 'var(--c--theme--colors--greyscale-text)',
size: 'var(--c--theme--font--sizes--t)',
},
},
'forms-datepicker': { 'border-radius': '0' },
'forms-fileuploader': { 'border-radius': '0' },
'forms-field': {
color: 'var(--c--theme--colors--primary-text)',
'footer-font-size': 'var(--c--theme--font--sizes--t)',
'footer-color': 'var(--c--theme--colors--greyscale-text)',
},
'forms-input': {
'border-radius': '4px',
'background-color': '#fff',
'border-color': 'var(--c--theme--colors--primary-text)',
'box-shadow-color': 'var(--c--theme--colors--primary-text)',
'value-color': 'var(--c--theme--colors--primary-text)',
'font-size': '14px',
},
'forms-labelledbox': {
'label-color': { big: 'var(--c--theme--colors--primary-text)' },
},
'forms-radio': {
'accent-color': 'var(--c--theme--colors--primary-600)',
},
'forms-select': {
'item-font-size': '14px',
'border-radius': '4px',
'border-radius-hover': '4px',
'background-color': '#fff',
'border-color': 'var(--c--theme--colors--primary-text)',
'border-color-hover': 'var(--c--theme--colors--primary-text)',
'box-shadow-color': 'var(--c--theme--colors--primary-text)',
},
'forms-switch': {
'handle-border-radius': '2px',
'rail-border-radius': '4px',
'accent-color': 'var(--c--theme--colors--primary-text)',
},
'forms-textarea': { 'border-radius': '0' },
'la-gauffre': { activated: true },
'home-proconnect': { activated: true },
},
},
},
};
@@ -3,7 +3,7 @@ import { create } from 'zustand';
import { tokens } from './cunningham-tokens';
type Tokens = typeof tokens.themes.default & Partial<typeof tokens.themes.dsfr>;
type Tokens = typeof tokens.themes.default;
type ColorsTokens = Tokens['theme']['colors'];
type FontSizesTokens = Tokens['theme']['font']['sizes'];
type SpacingsTokens = Tokens['theme']['spacings'];
@@ -28,7 +28,7 @@ export const useCunninghamTheme = create<AuthStore>((set, get) => {
) as Tokens;
return {
theme: 'dsfr',
theme: 'default',
themeTokens: () => currentTheme().theme,
colorsTokens: () => currentTheme().theme.colors,
componentTokens: () => currentTheme().components,
@@ -1,10 +1,7 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
import {
Tooltip,
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
import { Tooltip } from '@openfun/cunningham-react';
import { useQueryClient } from '@tanstack/react-query';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
@@ -15,11 +12,13 @@ import {
Doc,
KEY_DOC,
KEY_LIST_DOC,
KEY_SUB_DOC,
useTrans,
useUpdateDoc,
} from '@/docs/doc-management';
import { useBroadcastStore, useResponsiveStore } from '@/stores';
import { useDocTreeStore } from '../../doc-tree/context/DocTreeContext';
interface DocTitleProps {
doc: Doc;
}
@@ -55,20 +54,27 @@ export const DocTitleText = ({ title }: DocTitleTextProps) => {
const DocTitleInput = ({ doc }: DocTitleProps) => {
const { isDesktop } = useResponsiveStore();
const { t } = useTranslation();
const queryClient = useQueryClient();
const { colorsTokens } = useCunninghamTheme();
const treeStore = useDocTreeStore();
const [titleDisplay, setTitleDisplay] = useState(doc.title);
const { toast } = useToastProvider();
const { untitledDocument } = useTrans();
const { broadcast } = useBroadcastStore();
const { mutate: updateDoc } = useUpdateDoc({
listInvalideQueries: [KEY_DOC, KEY_LIST_DOC],
onSuccess(data) {
toast(t('Document title updated successfully'), VariantType.SUCCESS);
listInvalideQueries: [KEY_LIST_DOC],
onSuccess(updatedDoc) {
// Broadcast to every user connected to the document
broadcast(`${KEY_DOC}-${data.id}`);
broadcast(`${KEY_DOC}-${updatedDoc.id}`);
if (updatedDoc.id === treeStore.root?.id) {
treeStore.setRoot(updatedDoc);
}
queryClient.setQueryData(
[KEY_SUB_DOC, { id: updatedDoc.id }],
updatedDoc,
);
},
});
@@ -6,6 +6,7 @@ import { Doc } from '../types';
export type DocParams = {
id: string;
isTree?: boolean;
};
export const getDoc = async ({ id }: DocParams): Promise<Doc> => {
@@ -19,14 +20,15 @@ export const getDoc = async ({ id }: DocParams): Promise<Doc> => {
};
export const KEY_DOC = 'doc';
export const KEY_SUB_DOC = 'sub-doc';
export const KEY_DOC_VISIBILITY = 'doc-visibility';
export function useDoc(
param: DocParams,
queryConfig?: UseQueryOptions<Doc, APIError, Doc>,
queryConfig?: Omit<UseQueryOptions<Doc, APIError, Doc>, 'queryFn'>,
) {
return useQuery<Doc, APIError, Doc>({
queryKey: [KEY_DOC, param],
queryKey: queryConfig?.queryKey ?? [KEY_DOC, param],
queryFn: () => getDoc(param),
...queryConfig,
});
@@ -8,6 +8,7 @@ import {
useAPIInfiniteQuery,
} from '@/api';
import { DocSearchTarget } from '../../doc-search/components/DocSearchFilters';
import { Doc } from '../types';
export const isDocsOrdering = (data: string): data is DocsOrdering => {
@@ -31,6 +32,8 @@ export type DocsParams = {
is_creator_me?: boolean;
title?: string;
is_favorite?: boolean;
target?: DocSearchTarget;
parent_id?: string;
};
export type DocsResponse = APIList<Doc>;
@@ -53,8 +56,14 @@ export const getDocs = async (params: DocsParams): Promise<DocsResponse> => {
if (params.is_favorite !== undefined) {
searchParams.set('is_favorite', params.is_favorite.toString());
}
const response = await fetchAPI(`documents/?${searchParams.toString()}`);
let response: Response;
if (params.parent_id && params.target === DocSearchTarget.CURRENT) {
response = await fetchAPI(
`documents/${params.parent_id}/descendants/?${searchParams.toString()}`,
);
} else {
response = await fetchAPI(`documents/?${searchParams.toString()}`);
}
if (!response.ok) {
throw new APIError('Failed to get the docs', await errorCauses(response));
@@ -17,16 +17,20 @@ import { Doc } from '../types';
interface ModalRemoveDocProps {
onClose: () => void;
doc: Doc;
afterDelete?: (doc: Doc) => void;
}
export const ModalRemoveDoc = ({ onClose, doc }: ModalRemoveDocProps) => {
export const ModalRemoveDoc = ({
onClose,
doc,
afterDelete,
}: ModalRemoveDocProps) => {
const { toast } = useToastProvider();
const { push } = useRouter();
const pathname = usePathname();
const {
mutate: removeDoc,
isError,
error,
} = useRemoveDoc({
@@ -34,6 +38,11 @@ export const ModalRemoveDoc = ({ onClose, doc }: ModalRemoveDocProps) => {
toast(t('The document has been deleted.'), VariantType.SUCCESS, {
duration: 4000,
});
if (afterDelete) {
afterDelete(doc);
return;
}
if (pathname === '/') {
onClose();
} else {
@@ -87,7 +96,9 @@ export const ModalRemoveDoc = ({ onClose, doc }: ModalRemoveDocProps) => {
<Box aria-label={t('Content modal to delete document')}>
{!isError && (
<Text $size="sm" $variation="600">
{t('Are you sure you want to delete this document ?')}
{t('Are you sure you want to delete the document "{{title}}"?', {
title: doc.title ?? t('Untitled document'),
})}
</Text>
)}
@@ -42,10 +42,14 @@ export interface Doc {
is_favorite: boolean;
link_reach: LinkReach;
link_role: LinkRole;
nb_accesses_ancestors: number;
nb_accesses_direct: number;
user_roles: Role[];
created_at: string;
updated_at: string;
nb_accesses_direct: number;
nb_accesses_ancestors: number;
children?: Doc[];
childrenCount?: number;
numchild: number;
abilities: {
accesses_manage: boolean;
accesses_view: boolean;
@@ -0,0 +1,66 @@
import { Button } from '@openfun/cunningham-react';
import { useTranslation } from 'react-i18next';
import { Box } from '@/components';
import { FilterDropdown } from '@/components/filter/FilterDropdown';
export enum DocSearchTarget {
ALL = 'all',
CURRENT = 'current',
}
export type DocSearchFiltersValues = {
target?: DocSearchTarget;
};
export type DocSearchFiltersProps = {
values?: DocSearchFiltersValues;
onValuesChange?: (values: DocSearchFiltersValues) => void;
onReset?: () => void;
};
export const DocSearchFilters = ({
values,
onValuesChange,
onReset,
}: DocSearchFiltersProps) => {
const { t } = useTranslation();
const hasFilters = Object.keys(values ?? {}).length > 0;
const handleTargetChange = (target: DocSearchTarget) => {
onValuesChange?.({ ...values, target });
};
return (
<Box
$direction="row"
$align="center"
$height="35px"
$justify="space-between"
$gap="10px"
$margin={{ vertical: 'base' }}
>
<Box $direction="row" $align="center" $gap="10px">
<FilterDropdown
selectedValue={values?.target}
options={[
{
label: t('All docs'),
value: DocSearchTarget.ALL,
callback: () => handleTargetChange(DocSearchTarget.ALL),
},
{
label: t('Current doc'),
value: DocSearchTarget.CURRENT,
callback: () => handleTargetChange(DocSearchTarget.CURRENT),
},
]}
/>
</Box>
{hasFilters && (
<Button color="primary-text" size="small" onClick={onReset}>
{t('Reset')}
</Button>
)}
</Box>
);
};
@@ -15,17 +15,37 @@ import {
import { Doc, useInfiniteDocs } from '@/docs/doc-management';
import { useResponsiveStore } from '@/stores';
import { useDocTreeStore } from '../../doc-tree/context/DocTreeContext';
import EmptySearchIcon from '../assets/illustration-docs-empty.png';
import {
DocSearchFilters,
DocSearchFiltersValues,
DocSearchTarget,
} from './DocSearchFilters';
import { DocSearchItem } from './DocSearchItem';
type DocSearchModalProps = ModalProps & {};
type DocSearchModalProps = ModalProps & {
showFilters?: boolean;
defaultFilters?: DocSearchFiltersValues;
};
export const DocSearchModal = ({ ...modalProps }: DocSearchModalProps) => {
export const DocSearchModal = ({
showFilters = false,
defaultFilters,
...modalProps
}: DocSearchModalProps) => {
const { t } = useTranslation();
const router = useRouter();
const treeStore = useDocTreeStore();
const [search, setSearch] = useState('');
const [filters, setFilters] = useState<DocSearchFiltersValues>(
defaultFilters ?? {},
);
const { isDesktop } = useResponsiveStore();
const {
data,
isFetching,
@@ -36,27 +56,41 @@ export const DocSearchModal = ({ ...modalProps }: DocSearchModalProps) => {
} = useInfiniteDocs({
page: 1,
title: search,
...filters,
parent_id: treeStore?.root?.id,
});
const loading = isFetching || isRefetching || isLoading;
const handleInputSearch = useDebouncedCallback(setSearch, 300);
const handleSelect = (doc: Doc) => {
if (treeStore?.initialRootId !== doc.id) {
treeStore.setSelectedNode(doc);
treeStore.setRoot(doc);
treeStore.setInitialTargetId(doc.id);
}
router.push(`/docs/${doc.id}`);
modalProps.onClose?.();
};
const handleResetFilters = () => {
setFilters({});
};
const docsData: QuickSearchData<Doc> = useMemo(() => {
const docs = data?.pages.flatMap((page) => page.results) || [];
const groupName =
filters.target === DocSearchTarget.CURRENT
? t('Select a page')
: t('Select a document');
return {
groupName: docs.length > 0 ? t('Select a document') : '',
groupName: docs.length > 0 ? groupName : '',
elements: search ? docs : [],
emptyString: t('No document found'),
endActions: hasNextPage
? [{ content: <InView onChange={() => void fetchNextPage()} /> }]
: [],
};
}, [data, hasNextPage, fetchNextPage, t, search]);
}, [data, hasNextPage, fetchNextPage, t, search, filters.target]);
return (
<Modal
@@ -75,6 +109,13 @@ export const DocSearchModal = ({ ...modalProps }: DocSearchModalProps) => {
onFilter={handleInputSearch}
>
<Box $height={isDesktop ? '500px' : 'calc(100vh - 68px - 1rem)'}>
{showFilters && (
<DocSearchFilters
values={filters}
onValuesChange={setFilters}
onReset={handleResetFilters}
/>
)}
{search.length === 0 && (
<Box
$direction="column"
@@ -3,6 +3,7 @@ import {
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
import { useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
@@ -11,7 +12,7 @@ import { APIError } from '@/api';
import { Box } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { User } from '@/features/auth';
import { Doc, Role } from '@/features/docs';
import { Doc, KEY_SUB_DOC, Role } from '@/features/docs';
import { useCreateDocAccess, useCreateDocInvitation } from '../api';
import { OptionType } from '../types';
@@ -39,6 +40,7 @@ export const DocShareAddMemberList = ({
}: Props) => {
const { t } = useTranslation();
const { toast } = useToastProvider();
const queryClient = useQueryClient();
const [isLoading, setIsLoading] = useState(false);
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
const [invitationRole, setInvitationRole] = useState<Role>(Role.EDITOR);
@@ -91,14 +93,32 @@ export const DocShareAddMemberList = ({
};
return isInvitationMode
? createInvitation({
...payload,
email: user.email,
})
: createDocAccess({
...payload,
memberId: user.id,
});
? createInvitation(
{
...payload,
email: user.email,
},
{
onSuccess: () => {
void queryClient.invalidateQueries({
queryKey: [KEY_SUB_DOC, { id: doc.id }],
});
},
},
)
: createDocAccess(
{
...payload,
memberId: user.id,
},
{
onSuccess: () => {
void queryClient.invalidateQueries({
queryKey: [KEY_SUB_DOC, { id: doc.id }],
});
},
},
);
});
const settledPromises = await Promise.allSettled(promises);
@@ -1,4 +1,5 @@
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
import { useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import {
@@ -8,7 +9,7 @@ import {
IconOptions,
} from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Doc, Role } from '@/docs/doc-management';
import { Doc, KEY_SUB_DOC, Role } from '@/docs/doc-management';
import { User } from '@/features/auth';
import { useDeleteDocInvitation, useUpdateDocInvitation } from '../api';
@@ -23,6 +24,7 @@ type Props = {
};
export const DocShareInvitationItem = ({ doc, invitation }: Props) => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const { spacingsTokens } = useCunninghamTheme();
const spacing = spacingsTokens();
const fakeUser: User = {
@@ -37,6 +39,11 @@ export const DocShareInvitationItem = ({ doc, invitation }: Props) => {
const canUpdate = doc.abilities.accesses_manage;
const { mutate: updateDocInvitation } = useUpdateDocInvitation({
onSuccess: () => {
void queryClient.invalidateQueries({
queryKey: [KEY_SUB_DOC, { id: doc.id }],
});
},
onError: (error) => {
toast(
error?.data?.role?.[0] ?? t('Error during update invitation'),
@@ -49,6 +56,11 @@ export const DocShareInvitationItem = ({ doc, invitation }: Props) => {
});
const { mutate: removeDocInvitation } = useDeleteDocInvitation({
onSuccess: () => {
void queryClient.invalidateQueries({
queryKey: [KEY_SUB_DOC, { id: doc.id }],
});
},
onError: (error) => {
toast(
error?.data?.role?.[0] ?? t('Error during delete invitation'),
@@ -1,4 +1,5 @@
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
import { useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import {
@@ -8,7 +9,7 @@ import {
IconOptions,
} from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Access, Doc, Role } from '@/docs/doc-management/';
import { Access, Doc, KEY_SUB_DOC, Role } from '@/docs/doc-management/';
import { useResponsiveStore } from '@/stores';
import { useDeleteDocAccess, useUpdateDocAccess } from '../api';
@@ -25,13 +26,20 @@ export const DocShareMemberItem = ({ doc, access }: Props) => {
const { t } = useTranslation();
const { isLastOwner, isOtherOwner } = useWhoAmI(access);
const { toast } = useToastProvider();
const { isDesktop } = useResponsiveStore();
const { spacingsTokens } = useCunninghamTheme();
const queryClient = useQueryClient();
const spacing = spacingsTokens();
const isNotAllowed =
isOtherOwner || !!isLastOwner || !doc.abilities.accesses_manage;
const { mutate: updateDocAccess } = useUpdateDocAccess({
onSuccess: () => {
void queryClient.invalidateQueries({
queryKey: [KEY_SUB_DOC, { id: doc.id }],
});
},
onError: () => {
toast(t('Error during invitation update'), VariantType.ERROR, {
duration: 4000,
@@ -40,6 +48,11 @@ export const DocShareMemberItem = ({ doc, access }: Props) => {
});
const { mutate: removeDocAccess } = useDeleteDocAccess({
onSuccess: () => {
void queryClient.invalidateQueries({
queryKey: [KEY_SUB_DOC, { id: doc.id }],
});
},
onError: () => {
toast(t('Error while deleting invitation'), VariantType.ERROR, {
duration: 4000,
@@ -0,0 +1,44 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
import { Doc, KEY_LIST_DOC } from '../../doc-management';
export type CreateDocParam = Pick<Doc, 'title'> & {
parentId: string;
};
export const createDocChildren = async ({
title,
parentId,
}: CreateDocParam): Promise<Doc> => {
const response = await fetchAPI(`documents/${parentId}/children/`, {
method: 'POST',
body: JSON.stringify({
title,
}),
});
if (!response.ok) {
throw new APIError('Failed to create the doc', await errorCauses(response));
}
return response.json() as Promise<Doc>;
};
interface CreateDocProps {
onSuccess: (data: Doc) => void;
}
export function useCreateChildrenDoc({ onSuccess }: CreateDocProps) {
const queryClient = useQueryClient();
return useMutation<Doc, APIError, CreateDocParam>({
mutationFn: createDocChildren,
onSuccess: (data) => {
void queryClient.resetQueries({
queryKey: [KEY_LIST_DOC],
});
onSuccess(data);
},
});
}
@@ -0,0 +1,58 @@
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI, useAPIInfiniteQuery } from '@/api';
import { DocsResponse } from '../../doc-management';
export type DocsChildrenParams = {
docId: string;
page?: number;
page_size?: number;
};
export const getDocChildren = async (
params: DocsChildrenParams,
): Promise<DocsResponse> => {
const { docId, page, page_size } = params;
const searchParams = new URLSearchParams();
if (page) {
searchParams.set('page', page.toString());
}
if (page_size) {
searchParams.set('page_size', page_size.toString());
}
const response = await fetchAPI(
`documents/${docId}/children/?${searchParams.toString()}`,
);
if (!response.ok) {
throw new APIError(
'Failed to get the doc children',
await errorCauses(response),
);
}
return response.json() as Promise<DocsResponse>;
};
export const KEY_LIST_DOC_CHILDREN = 'doc-children';
export function useDocChildren(
params: DocsChildrenParams,
queryConfig?: Omit<
UseQueryOptions<DocsResponse, APIError, DocsResponse>,
'queryKey' | 'queryFn'
>,
) {
return useQuery<DocsResponse, APIError, DocsResponse>({
queryKey: [KEY_LIST_DOC_CHILDREN, params],
queryFn: () => getDocChildren(params),
...queryConfig,
});
}
export const useInfiniteDocChildren = (params: DocsChildrenParams) => {
return useAPIInfiniteQuery(KEY_LIST_DOC_CHILDREN, getDocChildren, params);
};
@@ -0,0 +1,45 @@
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
import { Doc } from '../../doc-management';
export type DocsTreeParams = {
docId: string;
};
export const getDocTree = async (params: DocsTreeParams): Promise<Doc> => {
const { docId } = params;
const searchParams = new URLSearchParams();
const response = await fetchAPI(
`documents/${docId}/tree/?${searchParams.toString()}`,
);
if (!response.ok) {
throw new APIError(
'Failed to get the doc tree',
await errorCauses(response),
);
}
return response.json() as Promise<Doc>;
};
export const KEY_LIST_DOC_CHILDREN = 'doc-tree';
export function useDocTree(
params: DocsTreeParams,
queryConfig?: Omit<
UseQueryOptions<Doc, APIError, Doc>,
'queryKey' | 'queryFn'
>,
) {
return useQuery<Doc, APIError, Doc>({
queryKey: [KEY_LIST_DOC_CHILDREN, params],
queryFn: () => getDocTree(params),
staleTime: 0,
refetchOnWindowFocus: false,
...queryConfig,
});
}
@@ -0,0 +1,36 @@
import { TreeViewMoveModeEnum } from '@gouvfr-lasuite/ui-kit';
import { useMutation } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
export type MoveDocParam = {
sourceDocumentId: string;
targetDocumentId: string;
position: TreeViewMoveModeEnum;
};
export const moveDoc = async ({
sourceDocumentId,
targetDocumentId,
position,
}: MoveDocParam): Promise<void> => {
const response = await fetchAPI(`documents/${sourceDocumentId}/move/`, {
method: 'POST',
body: JSON.stringify({
target_document_id: targetDocumentId,
position,
}),
});
if (!response.ok) {
throw new APIError('Failed to move the doc', await errorCauses(response));
}
return response.json() as Promise<void>;
};
export function useMoveDoc() {
return useMutation<void, APIError, MoveDocParam>({
mutationFn: moveDoc,
});
}
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.40918 4.69434C5.28613 4.69434 5.18359 4.65332 5.10156 4.57129C5.02409 4.48926 4.98535 4.389 4.98535 4.27051C4.98535 4.15202 5.02409 4.05404 5.10156 3.97656C5.18359 3.89453 5.28613 3.85352 5.40918 3.85352H10.5977C10.7161 3.85352 10.8141 3.89453 10.8916 3.97656C10.9736 4.05404 11.0146 4.15202 11.0146 4.27051C11.0146 4.389 10.9736 4.48926 10.8916 4.57129C10.8141 4.65332 10.7161 4.69434 10.5977 4.69434H5.40918ZM5.40918 7.08008C5.28613 7.08008 5.18359 7.03906 5.10156 6.95703C5.02409 6.875 4.98535 6.77474 4.98535 6.65625C4.98535 6.53776 5.02409 6.43978 5.10156 6.3623C5.18359 6.28027 5.28613 6.23926 5.40918 6.23926H10.5977C10.7161 6.23926 10.8141 6.28027 10.8916 6.3623C10.9736 6.43978 11.0146 6.53776 11.0146 6.65625C11.0146 6.77474 10.9736 6.875 10.8916 6.95703C10.8141 7.03906 10.7161 7.08008 10.5977 7.08008H5.40918ZM5.40918 9.46582C5.28613 9.46582 5.18359 9.42708 5.10156 9.34961C5.02409 9.26758 4.98535 9.1696 4.98535 9.05566C4.98535 8.93262 5.02409 8.83008 5.10156 8.74805C5.18359 8.66602 5.28613 8.625 5.40918 8.625H7.86328C7.98633 8.625 8.08659 8.66602 8.16406 8.74805C8.24609 8.83008 8.28711 8.93262 8.28711 9.05566C8.28711 9.1696 8.24609 9.26758 8.16406 9.34961C8.08659 9.42708 7.98633 9.46582 7.86328 9.46582H5.40918ZM2.25098 13.2529V2.88281C2.25098 2.17188 2.42643 1.63639 2.77734 1.27637C3.13281 0.916341 3.66374 0.736328 4.37012 0.736328H11.6299C12.3363 0.736328 12.8649 0.916341 13.2158 1.27637C13.5713 1.63639 13.749 2.17188 13.749 2.88281V13.2529C13.749 13.9684 13.5713 14.5039 13.2158 14.8594C12.8649 15.2148 12.3363 15.3926 11.6299 15.3926H4.37012C3.66374 15.3926 3.13281 15.2148 2.77734 14.8594C2.42643 14.5039 2.25098 13.9684 2.25098 13.2529ZM3.35156 13.2324C3.35156 13.5742 3.44043 13.8363 3.61816 14.0186C3.80046 14.2008 4.06934 14.292 4.4248 14.292H11.5752C11.9307 14.292 12.1973 14.2008 12.375 14.0186C12.5573 13.8363 12.6484 13.5742 12.6484 13.2324V2.90332C12.6484 2.56152 12.5573 2.29948 12.375 2.11719C12.1973 1.93034 11.9307 1.83691 11.5752 1.83691H4.4248C4.06934 1.83691 3.80046 1.93034 3.61816 2.11719C3.44043 2.29948 3.35156 2.56152 3.35156 2.90332V13.2324Z" fill="#8585F6"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,178 @@
import {
TreeViewItem,
TreeViewNodeProps,
useTree,
} from '@gouvfr-lasuite/ui-kit';
import { useRouter } from 'next/navigation';
import { useEffect, useRef } from 'react';
import { css } from 'styled-components';
import { Box, Icon, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Doc, KEY_SUB_DOC, useDoc } from '@/features/docs/doc-management';
import { useLeftPanelStore } from '@/features/left-panel';
import Logo from './../assets/sub-page-logo.svg';
import { DocTreeItemActions } from './DocTreeItemActions';
const ItemTextCss = css`
overflow: hidden;
text-overflow: ellipsis;
white-space: initial;
display: -webkit-box;
line-clamp: 1;
/* width: 100%; */
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
`;
type Props = TreeViewNodeProps<Doc> & {
treeData: ReturnType<typeof useTree<Doc>>;
doc: Doc;
setSelectedNode: (node: Doc) => void;
};
export const DocSubPageItem = ({
doc,
setSelectedNode,
treeData,
...props
}: Props) => {
const { loadChildren, node } = props;
const isInitialLoad = useRef(false);
const { spacingsTokens } = useCunninghamTheme();
const spacing = spacingsTokens();
const router = useRouter();
const { togglePanel } = useLeftPanelStore();
const { data: docQuery } = useDoc(
{ isTree: true, id: doc.id },
{
initialData: doc,
queryKey: [KEY_SUB_DOC, { id: doc.id }],
refetchOnMount: false,
refetchOnWindowFocus: false,
},
);
useEffect(() => {
if (docQuery && isInitialLoad.current === true) {
console.log('docQuery', docQuery);
treeData?.updateNode(docQuery.id, docQuery);
}
if (docQuery) {
isInitialLoad.current = true;
}
}, [docQuery, treeData]);
const afterCreate = (createdDoc: Doc) => {
const actualChildren = node.data.children ?? [];
if (actualChildren.length === 0 && loadChildren) {
loadChildren(node?.data.value)
.then((allChildren) => {
node.open();
router.push(`/docs/${doc.id}`);
treeData?.setChildren(node.data.value.id, allChildren);
togglePanel();
})
.catch(console.error);
} else {
const newDoc = {
...createdDoc,
children: [],
childrenCount: 0,
parentId: node.id,
};
treeData?.addChild(node.data.value.id, newDoc);
node.open();
router.push(`/docs/${createdDoc.id}`);
togglePanel();
}
};
if (!treeData) {
return null;
}
return (
<TreeViewItem
{...props}
loadChildren={() =>
treeData?.handleLoadChildren(props.node.data.value.id)
}
onClick={() => {
setSelectedNode(props.node.data.value as Doc);
router.push(`/docs/${props.node.data.value.id}`);
}}
>
<Box
data-testid={`doc-sub-page-item-${props.node.data.value.id}`}
$width="100%"
$direction="row"
$gap={spacing['xs']}
role="button"
tabIndex={0}
$align="center"
$css={css`
.light-doc-item-actions {
display: 'flex';
opacity: 0;
&:has(.isOpen) {
opacity: 1;
}
}
&:hover {
.light-doc-item-actions {
opacity: 1;
}
}
`}
>
<Box $width={16} $height={16}>
<Logo />
</Box>
<Box
$direction="row"
$align="center"
$css={css`
display: flex;
flex-direction: row;
width: 100%;
gap: 0.5rem;
align-items: center;
`}
>
<Text $css={ItemTextCss} $size="sm">
{doc.title}
</Text>
{doc.nb_accesses_direct > 1 && (
<Icon
$isMaterialIcon="filled"
iconName="group"
$size="16px"
$variation="400"
/>
)}
</Box>
<Box
$direction="row"
$gap={spacing['xs']}
$align="center"
className="light-doc-item-actions"
>
<DocTreeItemActions
doc={doc}
treeData={treeData}
parentId={node.data.parentKey}
onCreateSuccess={afterCreate}
/>
</Box>
</Box>
</TreeViewItem>
);
};
@@ -0,0 +1,238 @@
import {
OpenMap,
TreeView,
TreeViewMoveResult,
useTree,
} from '@gouvfr-lasuite/ui-kit';
import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { css } from 'styled-components';
import { Box, SeparatedSection, StyledLink } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import {
Doc,
KEY_DOC,
LinkReach,
LinkRole,
getDoc,
} from '../../doc-management';
import { SimpleDocItem } from '../../docs-grid';
import { getDocChildren } from '../api/useDocChildren';
import { useDocTree } from '../api/useDocTree';
import { useMoveDoc } from '../api/useMove';
import { subPageToTree, useDocTreeStore } from '../context/DocTreeContext';
import { DocSubPageItem } from './DocSubPageItem';
import { DocTreeItemActions } from './DocTreeItemActions';
type DocTreeProps = {
initialTargetId: string;
};
export const DocTree = ({ initialTargetId }: DocTreeProps) => {
const { spacingsTokens } = useCunninghamTheme();
const queryClient = useQueryClient();
const store = useDocTreeStore();
const spacing = spacingsTokens();
const treeData = useTree(
[],
async (docId) => {
const doc = await getDoc({ id: docId });
const newDoc = { ...doc, childrenCount: doc.numchild };
void queryClient.setQueryData([KEY_DOC, { id: docId }], newDoc);
return newDoc;
},
async (docId) => {
const doc = await getDocChildren({ docId: docId });
return subPageToTree(doc.results ?? []);
},
);
const router = useRouter();
const [initialOpenState, setInitialOpenState] = useState<OpenMap | undefined>(
undefined,
);
const { mutate: moveDoc } = useMoveDoc();
const { data } = useDocTree({
docId: initialTargetId,
});
const handleMove = (result: TreeViewMoveResult) => {
moveDoc({
sourceDocumentId: result.sourceId,
targetDocumentId: result.targetModeId,
position: result.mode,
});
treeData?.handleMove(result);
};
const buildDocTree = (data?: Doc) => {
if (!data) {
return;
}
const { children: rootChildren, ...root } = data;
const children = rootChildren ?? [];
store.setRoot(root);
const initialOpenState: OpenMap = {};
initialOpenState[root.id] = true;
subPageToTree(children, (child) => {
if (child?.children?.length && child?.children?.length > 0) {
initialOpenState[child.id] = true;
}
});
treeData.resetTree(children);
setInitialOpenState(initialOpenState);
if (initialTargetId === root.id) {
treeData?.setSelectedNode(root);
} else {
treeData?.selectNodeById(initialTargetId);
}
};
useEffect(() => {
if (treeData?.selectedNode?.id !== store.selectedNode?.id) {
store.setSelectedNode(treeData?.selectedNode ?? null);
}
}, [store, treeData?.selectedNode]);
useEffect(() => {
buildDocTree(data);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);
const rootIsSelected = treeData?.selectedNode?.id === store.root?.id;
if (!initialTargetId) {
return null;
}
return (
<Box data-testid="doc-tree" $height="100%">
<SeparatedSection showSeparator={false}>
<Box $padding={{ horizontal: 'sm' }}>
<Box
$css={css`
padding: ${spacing['2xs']};
border-radius: 4px;
width: 100%;
background-color: ${rootIsSelected
? 'var(--c--theme--colors--greyscale-100)'
: 'transparent'};
&:hover {
background-color: var(--c--theme--colors--greyscale-100);
}
.doc-tree-root-item-actions {
display: 'flex';
opacity: 0;
&:has(.isOpen) {
opacity: 1;
}
}
&:hover {
.doc-tree-root-item-actions {
opacity: 1;
}
}
`}
>
{store.root !== null && (
<StyledLink
$css={css`
width: 100%;
`}
href={`/docs/${store.root.id}`}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
treeData?.setSelectedNode(store.root ?? undefined);
router.push(`/docs/${store.root?.id}`);
}}
>
<Box $direction="row" $align="center" $width="100%">
<SimpleDocItem doc={store.root} showAccesses={true} />
<div className="doc-tree-root-item-actions">
<DocTreeItemActions
doc={store.root}
treeData={treeData}
onCreateSuccess={(createdDoc) => {
const newDoc = {
...createdDoc,
children: [],
childrenCount: 0,
parentId: store.root?.id ?? undefined,
};
treeData?.addChild(null, newDoc);
}}
/>
</div>
</Box>
</StyledLink>
)}
</Box>
</Box>
</SeparatedSection>
<button
onClick={() => {
const children = data?.children ?? [];
const newDoc = {
id: '1',
children: [],
childrenCount: 0,
title: 'TITI',
creator: 'test',
is_favorite: false,
};
// Ajout des propriétés manquantes pour correspondre au type Doc
const completeDoc = {
...newDoc,
link_reach: LinkReach.PUBLIC,
link_role: LinkRole.EDITOR,
user_roles: [],
// Ajoutez ici les autres propriétés requises par le type Doc
};
children.push(completeDoc);
buildDocTree(data);
}}
>
Refresh
</button>
{initialOpenState && treeData.nodes.length > 0 && (
<TreeView
handleMove={handleMove}
initialOpenState={initialOpenState}
selectedNodeId={
treeData.selectedNode?.id ?? store.initialTargetId ?? undefined
}
treeData={treeData.nodes ?? []}
rootNodeId={store.root?.id ?? ''}
renderNode={(props) => {
if (treeData === undefined) {
return null;
}
return (
<DocSubPageItem
{...props}
treeData={treeData}
doc={props.node.data.value as Doc}
loadChildren={(node) => treeData.handleLoadChildren(node.id)}
setSelectedNode={(node) => treeData.setSelectedNode(node)}
/>
);
}}
/>
)}
</Box>
);
};
@@ -0,0 +1,129 @@
import {
DropdownMenu,
DropdownMenuOption,
useTree,
} from '@gouvfr-lasuite/ui-kit';
import { useModal } from '@openfun/cunningham-react';
import { useRouter } from 'next/navigation';
import { Fragment, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, BoxButton, Icon } from '@/components';
import { useLeftPanelStore } from '@/features/left-panel';
import { Doc, ModalRemoveDoc } from '../../doc-management';
import { useCreateChildrenDoc } from '../api/useCreateChildren';
import { useDocTreeStore } from '../context/DocTreeContext';
type DocTreeItemActionsProps = {
doc: Doc;
parentId?: string | null;
treeData: ReturnType<typeof useTree<Doc>>;
onCreateSuccess?: (newDoc: Doc) => void;
};
export const DocTreeItemActions = ({
doc,
parentId,
onCreateSuccess,
treeData,
}: DocTreeItemActionsProps) => {
const [isOpen, setIsOpen] = useState(false);
const router = useRouter();
const treeStore = useDocTreeStore();
const { t } = useTranslation();
const deleteModal = useModal();
const { togglePanel } = useLeftPanelStore();
const options: DropdownMenuOption[] = [
{
label: t('Delete'),
icon: <Icon iconName="delete" />,
callback: deleteModal.open,
},
];
const { mutate: createChildrenDoc } = useCreateChildrenDoc({
onSuccess: (doc) => {
onCreateSuccess?.(doc);
togglePanel();
treeData.setSelectedNode(doc);
router.push(`/docs/${doc.id}`);
},
});
const afterDelete = () => {
if (parentId) {
router.push(`/docs/${parentId}`);
treeData?.selectNodeById(parentId);
treeData?.deleteNode(doc.id);
void treeData?.refreshNode(parentId);
} else if (doc.id === treeStore.root?.id && !parentId) {
router.push(`/docs/`);
} else if (treeStore.root) {
router.push(`/docs/${treeStore.root.id}`);
treeData?.deleteNode(doc.id);
treeData?.setSelectedNode(treeStore.root);
}
};
return (
<Fragment>
<Box
$direction="row"
$align="center"
className={` ${isOpen ? 'isOpen' : ''}`}
$css={css`
gap: var(--c--theme----c--theme--spacings--xs);
`}
>
<DropdownMenu
options={options}
isOpen={isOpen}
onOpenChange={setIsOpen}
>
<Icon
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
setIsOpen(!isOpen);
}}
iconName="more_horiz"
$isMaterialIcon="filled"
$theme="primary"
$variation="600"
/>
</DropdownMenu>
<BoxButton
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
createChildrenDoc({
title: t('Untitled page'),
parentId: doc.id,
});
}}
color="primary"
>
<Icon
$variation="800"
$theme="primary"
$isMaterialIcon="filled"
iconName="add_box"
/>
</BoxButton>
</Box>
{deleteModal.isOpen && (
<ModalRemoveDoc
onClose={deleteModal.onClose}
doc={doc}
afterDelete={afterDelete}
/>
)}
</Fragment>
);
};
@@ -0,0 +1,37 @@
import { TreeViewDataType } from '@gouvfr-lasuite/ui-kit';
import { create } from 'zustand';
import { Doc } from '../../doc-management';
export const subPageToTree = (
children: Doc[],
callback?: (doc: Doc) => void,
): TreeViewDataType<Doc>[] => {
children.forEach((child) => {
child.childrenCount = child.numchild ?? 0;
callback?.(child);
subPageToTree(child.children ?? [], callback);
});
return children;
};
interface DocTreeStore {
initialTargetId?: string | null;
initialRootId?: string | null;
setRoot: (doc: Doc | null) => void;
root: Doc | null;
setInitialTargetId: (id: string) => void;
setSelectedNode: (node: Doc | null) => void;
selectedNode: Doc | null;
}
export const useDocTreeStore = create<DocTreeStore>((set) => ({
root: null,
selectedNode: null,
initialTargetId: undefined,
initialRootId: undefined,
setRoot: (doc) => set({ root: doc }),
setInitialTargetId: (id) => set({ initialTargetId: id }),
setSelectedNode: (node) => set({ selectedNode: node }),
}));
@@ -0,0 +1,189 @@
import {
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
KeyboardSensor,
Modifier,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import { getEventCoordinates } from '@dnd-kit/utilities';
import { TreeViewMoveModeEnum } from '@gouvfr-lasuite/ui-kit';
import { useQueryClient } from '@tanstack/react-query';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Text } from '@/components';
import { Doc, KEY_LIST_DOC, Role } from '../../doc-management';
import { useMoveDoc } from '../../doc-tree/api/useMove';
import { DocsGridItem } from './DocsGridItem';
import { Draggable } from './dnd/Draggable';
import { Droppable } from './dnd/Droppable';
const activationConstraint = {
distance: 20,
};
type DocGridContentListProps = {
docs: Doc[];
};
export const DocGridContentList = ({ docs }: DocGridContentListProps) => {
const queryClient = useQueryClient();
const { t } = useTranslation();
const [selectedDoc, setSelectedDoc] = useState<Doc>();
const canDrag = selectedDoc?.user_roles.some((role) => role === Role.OWNER);
const [canDrop, setCanDrop] = useState<boolean>();
const { mutate: handleMove } = useMoveDoc();
const mouseSensor = useSensor(MouseSensor, {
activationConstraint,
});
const touchSensor = useSensor(TouchSensor, {
activationConstraint,
});
const keyboardSensor = useSensor(KeyboardSensor, {});
const sensors = useSensors(mouseSensor, touchSensor, keyboardSensor);
const handleDragStart = (e: DragStartEvent) => {
document.body.style.cursor = 'grabbing';
if (e.active.data.current) {
setSelectedDoc(e.active.data.current as Doc);
}
};
const handleDragEnd = (e: DragEndEvent) => {
setSelectedDoc(undefined);
setCanDrop(undefined);
document.body.style.cursor = 'default';
if (!canDrag || !canDrop) {
return;
}
const { active, over } = e;
if (!over?.id || active.id === over?.id) {
return;
}
handleMove(
{
sourceDocumentId: active.id as string,
targetDocumentId: over.id as string,
position: TreeViewMoveModeEnum.FIRST_CHILD,
},
{
onSuccess: () => {
void queryClient.invalidateQueries({
queryKey: [KEY_LIST_DOC],
});
},
},
);
};
const overlayText = useMemo(() => {
if (!canDrag) {
return t('You must be the owner to move the document');
}
if (!canDrop) {
return t('You must be at least the editor of the target document');
}
return selectedDoc?.title || t('Unnamed document');
}, [canDrag, canDrop, selectedDoc, t]);
const overlayBgColor = useMemo(() => {
if (!canDrag) {
return 'var(--c--theme--colors--danger-600)';
}
if (canDrop !== undefined && !canDrop) {
return 'var(--c--theme--colors--danger-600)';
}
return '#5858D3';
}, [canDrag, canDrop]);
if (docs.length === 0) {
return null;
}
return (
<DndContext
sensors={sensors}
modifiers={[snapToTopLeft]}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
{docs.map((doc) => {
const canDropItem = doc.user_roles.some(
(role) =>
role === Role.ADMIN || role === Role.OWNER || role === Role.EDITOR,
);
return (
<Droppable
enabledDrop={canDrag}
canDrop={canDrag && canDropItem}
onOver={(isOver) => {
if (isOver) {
setCanDrop(canDropItem);
}
}}
key={doc.id}
id={doc.id}
data={doc}
>
<Draggable key={doc.id} id={doc.id} data={doc}>
<DocsGridItem dragMode={!!selectedDoc} doc={doc} key={doc.id} />
</Draggable>
</Droppable>
);
})}
<DragOverlay dropAnimation={null}>
<Box
$width="fit-content"
$padding={{ horizontal: 'xs', vertical: '3xs' }}
$radius="12px"
$background={overlayBgColor}
data-testid="drag-doc-overlay"
$height="auto"
>
<Text $size="xs" $variation="000" $weight="500">
{overlayText}
</Text>
</Box>
</DragOverlay>
</DndContext>
);
};
export const snapToTopLeft: Modifier = ({
activatorEvent,
draggingNodeRect,
transform,
}) => {
if (draggingNodeRect && activatorEvent) {
const activatorCoordinates = getEventCoordinates(activatorEvent);
if (!activatorCoordinates) {
return transform;
}
const offsetX = activatorCoordinates.x - draggingNodeRect.left;
const offsetY = activatorCoordinates.y - draggingNodeRect.top;
return {
...transform,
x: transform.x + offsetX - 3,
y: transform.y + offsetY - 3,
};
}
return transform;
};
@@ -9,7 +9,7 @@ import { useResponsiveStore } from '@/stores';
import { useResponsiveDocGrid } from '../hooks/useResponsiveDocGrid';
import { DocsGridItem } from './DocsGridItem';
import { DocGridContentList } from './DocGridContentList';
import { DocsGridLoader } from './DocsGridLoader';
type DocsGridProps = {
@@ -37,6 +37,9 @@ export const DocsGrid = ({
is_creator_me: target === DocDefaultFilter.MY_DOCS,
}),
});
const docs = data?.pages.flatMap((page) => page.results) ?? [];
const loading = isFetching || isLoading;
const hasDocs = data?.pages.some((page) => page.results.length > 0);
const loadMore = (inView: boolean) => {
@@ -114,11 +117,7 @@ export const DocsGrid = ({
)}
</Box>
{data?.pages.map((currentPage) => {
return currentPage.results.map((doc) => (
<DocsGridItem doc={doc} key={doc.id} />
));
})}
<DocGridContentList docs={docs} />
{hasNextPage && !loading && (
<InView
@@ -16,8 +16,9 @@ import { DocsGridItemSharedButton } from './DocsGridItemSharedButton';
import { SimpleDocItem } from './SimpleDocItem';
type DocsGridItemProps = {
doc: Doc;
dragMode?: boolean;
};
export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
const { t } = useTranslation();
const { isDesktop } = useResponsiveStore();
const { flexLeft, flexRight } = useResponsiveDocGrid();
@@ -46,7 +47,9 @@ export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
cursor: pointer;
border-radius: 4px;
&:hover {
background-color: var(--c--theme--colors--greyscale-100);
background-color: ${dragMode
? 'none'
: 'var(--c--theme--colors--greyscale-100)'};
}
`}
>
@@ -79,25 +82,35 @@ export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
: undefined
}
>
<Tooltip
content={
<Text $textAlign="center" $variation="000">
{isPublic
? t('Accessible to anyone')
: t('Accessible to authenticated users')}
</Text>
}
placement="top"
>
<div>
<Icon
$theme="greyscale"
$variation="600"
$size="14px"
iconName={isPublic ? 'public' : 'vpn_lock'}
/>
</div>
</Tooltip>
{dragMode && (
<Icon
$theme="greyscale"
$variation="600"
$size="14px"
iconName={isPublic ? 'public' : 'vpn_lock'}
/>
)}
{!dragMode && (
<Tooltip
content={
<Text $textAlign="center" $variation="000">
{isPublic
? t('Accessible to anyone')
: t('Accessible to authenticated users')}
</Text>
}
placement="top"
>
<div>
<Icon
$theme="greyscale"
$variation="600"
$size="14px"
iconName={isPublic ? 'public' : 'vpn_lock'}
/>
</div>
</Tooltip>
)}
</Box>
)}
</Box>
@@ -38,7 +38,7 @@ export const SimpleDocItem = ({
const { untitledDocument } = useTrans();
return (
<Box $direction="row" $gap={spacings.sm} $overflow="auto">
<Box $direction="row" $gap={spacings.sm} $overflow="auto" $width="100%">
<Box
$direction="row"
$align="center"
@@ -0,0 +1,25 @@
import { Data, useDraggable } from '@dnd-kit/core';
type DraggableProps<T> = {
id: string;
data?: Data<T>;
children: React.ReactNode;
};
export const Draggable = <T,>(props: DraggableProps<T>) => {
const { attributes, listeners, setNodeRef } = useDraggable({
id: props.id,
data: props.data,
});
return (
<div
ref={setNodeRef}
{...listeners}
{...attributes}
data-testid={`draggable-doc-${props.id}`}
>
{props.children}
</div>
);
};
@@ -0,0 +1,48 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { Data, useDroppable } from '@dnd-kit/core';
import { useEffect } from 'react';
import { css } from 'styled-components';
import { Box } from '@/components';
import { Doc } from '@/features/docs/doc-management';
type DroppableProps = {
id: string;
onOver?: (isOver: boolean, data?: Data<Doc>) => void;
data?: Data<Doc>;
children: React.ReactNode;
enabledDrop?: boolean;
canDrop?: boolean;
};
export const Droppable = (props: DroppableProps) => {
const { isOver, setNodeRef } = useDroppable({
id: props.id,
data: props.data,
});
const enableHover = props.canDrop && isOver;
useEffect(() => {
props.onOver?.(isOver, props.data);
}, [isOver, props.data, props.onOver]);
return (
<Box
ref={setNodeRef}
data-testid={`droppable-doc-${props.id}`}
$css={css`
border-radius: 4px;
background-color: ${enableHover
? 'var(--c--theme--colors--primary-100)'
: 'transparent'};
border: 1.5px solid
${enableHover
? 'var(--c--theme--colors--primary-500)'
: 'transparent'};
`}
>
{props.children}
</Box>
);
};
@@ -1,15 +1,14 @@
import { css } from 'styled-components';
import { Box, SeparatedSection } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Box } from '@/components';
import { useDocStore } from '@/docs/doc-management';
import { SimpleDocItem } from '@/docs/docs-grid';
import { DocTree } from '@/features/docs/doc-tree/components/DocTree';
import { useDocTreeStore } from '@/features/docs/doc-tree/context/DocTreeContext';
export const LeftPanelDocContent = () => {
const { currentDoc } = useDocStore();
const { spacingsTokens } = useCunninghamTheme();
const spacing = spacingsTokens();
if (!currentDoc) {
const treeStore = useDocTreeStore();
if (!currentDoc || !treeStore.initialTargetId) {
return null;
}
@@ -19,19 +18,9 @@ export const LeftPanelDocContent = () => {
$width="100%"
$css="width: 100%; overflow-y: auto; overflow-x: hidden;"
>
<SeparatedSection showSeparator={false}>
<Box $padding={{ horizontal: 'sm' }}>
<Box
$css={css`
padding: ${spacing['2xs']};
border-radius: 4px;
background-color: var(--c--theme--colors--greyscale-100);
`}
>
<SimpleDocItem doc={currentDoc} showAccesses={true} />
</Box>
</Box>
</SeparatedSection>
{treeStore.initialTargetId && (
<DocTree initialTargetId={treeStore.initialTargetId} />
)}
</Box>
);
};
@@ -1,12 +1,15 @@
import { Button, ModalSize, useModal } from '@openfun/cunningham-react';
import { t } from 'i18next';
import { useRouter } from 'next/navigation';
import { useRouter } from 'next/router';
import { PropsWithChildren } from 'react';
import { Box, Icon, SeparatedSection } from '@/components';
import { useCreateDoc } from '@/docs/doc-management';
import { DocSearchModal } from '@/docs/doc-search';
import { useAuth } from '@/features/auth';
import { useCreateDoc, useDocStore } from '@/features/docs/doc-management';
import { DocSearchTarget } from '@/features/docs/doc-search/components/DocSearchFilters';
import { useCreateChildrenDoc } from '@/features/docs/doc-tree/api/useCreateChildren';
import { useDocTreeStore } from '@/features/docs/doc-tree/context/DocTreeContext';
import { useCmdK } from '@/hook/useCmdK';
import { useLeftPanelStore } from '../stores';
@@ -15,23 +18,52 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
const router = useRouter();
const searchModal = useModal();
const { authenticated } = useAuth();
useCmdK(searchModal.open);
const treeStore = useDocTreeStore();
const { currentDoc } = useDocStore();
const isDoc = router.pathname === '/docs/[id]';
useCmdK(() => {
const isEditorToolbarOpen =
document.getElementsByClassName('bn-formatting-toolbar').length > 0;
if (isEditorToolbarOpen) {
return;
}
searchModal.open();
});
const { togglePanel } = useLeftPanelStore();
const { mutate: createDoc } = useCreateDoc({
onSuccess: (doc) => {
router.push(`/docs/${doc.id}`);
void router.push(`/docs/${doc.id}`);
togglePanel();
},
});
const { mutate: createChildrenDoc } = useCreateChildrenDoc({
onSuccess: (doc) => {
treeStore.treeData?.addRootNode(doc);
treeStore.treeData?.selectNodeById(doc.id);
void router.push(`/docs/${doc.id}`);
togglePanel();
},
});
const goToHome = () => {
router.push('/');
void router.push('/');
togglePanel();
};
const createNewDoc = () => {
createDoc();
if (treeStore.root && isDoc) {
createChildrenDoc({
title: t('Untitled page'),
parentId: treeStore.root.id,
});
} else {
createDoc();
}
};
return (
@@ -65,15 +97,29 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
/>
)}
</Box>
{authenticated && (
<Button onClick={createNewDoc}>{t('New doc')}</Button>
<Button
color={!isDoc ? 'primary' : 'tertiary'}
onClick={createNewDoc}
disabled={currentDoc && !currentDoc.abilities.update}
>
{t(isDoc ? 'New page' : 'New doc')}
</Button>
)}
</Box>
</SeparatedSection>
{children}
</Box>
{searchModal.isOpen && (
<DocSearchModal {...searchModal} size={ModalSize.LARGE} />
<DocSearchModal
{...searchModal}
size={ModalSize.LARGE}
showFilters={isDoc}
defaultFilters={{
target: isDoc ? DocSearchTarget.CURRENT : undefined,
}}
/>
)}
</>
);
@@ -175,6 +175,7 @@ export class ApiPlugin implements WorkboxPlugin {
is_favorite: false,
nb_accesses_direct: 1,
nb_accesses_ancestors: 1,
numchild: 0,
updated_at: new Date().toISOString(),
abilities: {
accesses_manage: true,
@@ -201,6 +202,7 @@ export class ApiPlugin implements WorkboxPlugin {
},
link_reach: LinkReach.RESTRICTED,
link_role: LinkRole.READER,
user_roles: [],
};
await DocsDB.cacheResponse(
@@ -6,12 +6,6 @@ export const useCmdK = (callback: () => void) => {
if ((e.key === 'k' || e.key === 'K') && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
const isProseMirrorFocused =
document.activeElement?.classList.contains('ProseMirror');
if (isProseMirrorFocused) {
return;
}
callback();
}
};
@@ -1,6 +1,5 @@
import type { AppProps } from 'next/app';
import Head from 'next/head';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { AppProvider } from '@/core/';
@@ -19,14 +18,6 @@ export default function App({ Component, pageProps }: AppPropsWithLayout) {
const getLayout = Component.getLayout ?? ((page) => page);
const { t } = useTranslation();
useEffect(() => {
console.log(
`%c
\r\n \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \r\n \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \r\n \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \r\n \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \r\n \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \r\n \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \r\n \r`,
'font-size: 11px;line-height:15px;background-image: linear-gradient(#000091, #005f91);color: transparent;background-clip: text;',
);
}, []);
return (
<>
<Head>
@@ -7,14 +7,15 @@ import { useTranslation } from 'react-i18next';
import { Box, Text, TextErrors } from '@/components';
import { DocEditor } from '@/docs/doc-editor';
import { KEY_AUTH, setAuthUrl } from '@/features/auth';
import {
Doc,
KEY_DOC,
useCollaboration,
useDoc,
useDocStore,
} from '@/docs/doc-management/';
import { KEY_AUTH, setAuthUrl } from '@/features/auth';
} from '@/features/docs/doc-management/';
import { useDocTreeStore } from '@/features/docs/doc-tree/context/DocTreeContext';
import { MainLayout } from '@/layouts';
import { useBroadcastStore } from '@/stores';
import { NextPageWithLayout } from '@/types/next';
@@ -24,6 +25,14 @@ export function DocLayout() {
query: { id },
} = useRouter();
const treeStore = useDocTreeStore();
useEffect(() => {
if (typeof id === 'string' && !treeStore.initialTargetId) {
treeStore.setInitialTargetId(id);
}
}, [id, treeStore]);
if (typeof id !== 'string') {
return null;
}
@@ -35,7 +44,7 @@ export function DocLayout() {
</Head>
<MainLayout>
<DocPage id={id} />
{treeStore.initialTargetId && <DocPage id={id} />}
</MainLayout>
</>
);
@@ -56,6 +65,8 @@ const DocPage = ({ id }: DocProps) => {
{
staleTime: 0,
queryKey: [KEY_DOC, { id }],
refetchOnMount: false,
refetchOnWindowFocus: false,
},
);
@@ -84,6 +95,13 @@ const DocPage = ({ id }: DocProps) => {
setCurrentDoc(docQuery);
}, [docQuery, setCurrentDoc, isFetching]);
useEffect(() => {
return () => {
setCurrentDoc(undefined);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
/**
* We add a broadcast task to reset the query cache
* when the document visibility changes.
@@ -1,4 +1,7 @@
@import url('../cunningham/cunningham-style.css');
@import url('@gouvfr-lasuite/ui-kit/style');
@import url('../cunningham/cunningham-tokens.css');
@import url('../cunningham/cunningham-custom-tokens.css');
@import url('@fontsource/material-icons');
body {
margin: 0;
@@ -41,3 +44,29 @@ main ::-webkit-scrollbar-thumb:hover,
cursor: pointer;
outline: inherit;
}
.material-icons-filled {
font-family: 'Material Icons', sans-serif;
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizelegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}
@@ -15,7 +15,7 @@ export const AppWrapper = ({ children }: PropsWithChildren) => {
return (
<QueryClientProvider client={queryClient}>
<CunninghamProvider theme="dsfr">{children}</CunninghamProvider>
<CunninghamProvider theme="default">{children}</CunninghamProvider>
</QueryClientProvider>
);
};
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "impress",
"version": "2.5.0",
"version": "2.6.0",
"private": true,
"workspaces": {
"packages": [
@@ -1,6 +1,6 @@
{
"name": "eslint-config-impress",
"version": "2.5.0",
"version": "2.6.0",
"license": "MIT",
"scripts": {
"lint": "eslint --ext .js ."
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "packages-i18n",
"version": "2.5.0",
"version": "2.6.0",
"private": true,
"scripts": {
"extract-translation": "yarn extract-translation:impress",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "server-y-provider",
"version": "2.5.0",
"version": "2.6.0",
"description": "Y.js provider for docs",
"repository": "https://github.com/numerique-gouv/impress",
"license": "MIT",
+221 -62
View File
@@ -225,12 +225,12 @@
"@babel/types" "^7.25.9"
"@babel/helpers@^7.26.7":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.10.tgz#6baea3cd62ec2d0c1068778d63cb1314f6637384"
integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==
version "7.26.7"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.7.tgz#fd1d2a7c431b6e39290277aacfd8367857c576a4"
integrity sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==
dependencies:
"@babel/template" "^7.26.9"
"@babel/types" "^7.26.10"
"@babel/template" "^7.25.9"
"@babel/types" "^7.26.7"
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.9", "@babel/parser@^7.26.5", "@babel/parser@^7.26.7":
version "7.26.7"
@@ -239,13 +239,6 @@
dependencies:
"@babel/types" "^7.26.7"
"@babel/parser@^7.26.9":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749"
integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==
dependencies:
"@babel/types" "^7.26.10"
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe"
@@ -970,6 +963,13 @@
"@babel/plugin-transform-typescript" "^7.25.9"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.23.2", "@babel/runtime@^7.24.5", "@babel/runtime@^7.25.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
version "7.26.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.7.tgz#f4e7fe527cd710f8dc0618610b61b4b060c3c341"
integrity sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/runtime@^7.9.2":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2"
integrity sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==
@@ -985,15 +985,6 @@
"@babel/parser" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/template@^7.26.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2"
integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==
dependencies:
"@babel/code-frame" "^7.26.2"
"@babel/parser" "^7.26.9"
"@babel/types" "^7.26.9"
"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.7":
version "7.26.7"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.7.tgz#99a0a136f6a75e7fb8b0a1ace421e0b25994b8bb"
@@ -1015,14 +1006,6 @@
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/types@^7.26.10", "@babel/types@^7.26.9":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259"
integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==
dependencies:
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -1242,6 +1225,45 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz#037817b574262134cabd68fc4ec1a454f168407b"
integrity sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==
"@dnd-kit/accessibility@^3.1.1":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz#3b4202bd6bb370a0730f6734867785919beac6af"
integrity sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==
dependencies:
tslib "^2.0.0"
"@dnd-kit/core@6.3.1":
version "6.3.1"
resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-6.3.1.tgz#4c36406a62c7baac499726f899935f93f0e6d003"
integrity sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==
dependencies:
"@dnd-kit/accessibility" "^3.1.1"
"@dnd-kit/utilities" "^3.2.2"
tslib "^2.0.0"
"@dnd-kit/modifiers@9.0.0":
version "9.0.0"
resolved "https://registry.yarnpkg.com/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz#96a0280c77b10c716ef79d9792ce7ad04370771d"
integrity sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==
dependencies:
"@dnd-kit/utilities" "^3.2.2"
tslib "^2.0.0"
"@dnd-kit/sortable@10.0.0":
version "10.0.0"
resolved "https://registry.yarnpkg.com/@dnd-kit/sortable/-/sortable-10.0.0.tgz#1f9382b90d835cd5c65d92824fa9dafb78c4c3e8"
integrity sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==
dependencies:
"@dnd-kit/utilities" "^3.2.2"
tslib "^2.0.0"
"@dnd-kit/utilities@^3.2.2":
version "3.2.2"
resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.2.tgz#5a32b6af356dc5f74d61b37d6f7129a4040ced7b"
integrity sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==
dependencies:
tslib "^2.0.0"
"@dual-bundle/import-meta-resolve@^4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#519c1549b0e147759e7825701ecffd25e5819f7b"
@@ -1567,6 +1589,11 @@
resolved "https://registry.yarnpkg.com/@fontsource/material-icons-outlined/-/material-icons-outlined-5.1.1.tgz#3a659277f5029fb1ea5f64739d7ef48fd2112a8e"
integrity sha512-HjWe3anHu9RptoAvm2UF6MPXkwsbFbH+vkg48GRIhXPsXyfmZteGHVFZpPxaZnakrmnLXDxUnnKSj7zzqk+Rhg==
"@fontsource/material-icons@5.2.5":
version "5.2.5"
resolved "https://registry.yarnpkg.com/@fontsource/material-icons/-/material-icons-5.2.5.tgz#cdb9dd23c0da4b021c866dc529ed4c203414ec3c"
integrity sha512-9k0LBRVgResIeD+vC/epYmm/awN2k2L8twwEtUWQ3FHluMi+7PbISOpXqksvfqPn9FJy4/KEeWOhFTR/SrbhNw==
"@formatjs/ecma402-abstract@2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.2.tgz#0ee291effe7ee2c340742a6c95d92eacb5e6c00a"
@@ -1613,6 +1640,23 @@
resolved "https://registry.yarnpkg.com/@gouvfr-lasuite/integration/-/integration-1.0.2.tgz#ed0000f4b738c5a19bb60f5b80a9a2f5d9414234"
integrity sha512-npOotZQSyu6SffHiPP+jQVOkJ3qW2KE2cANhEK92sNLX9uZqQaCqljO5GhzsBmh0lB76fiXnrr9i8SIpnDUSZg==
"@gouvfr-lasuite/ui-kit@file:../../../design-system":
version "0.1.3"
dependencies:
"@dnd-kit/core" "6.3.1"
"@dnd-kit/modifiers" "9.0.0"
"@dnd-kit/sortable" "10.0.0"
"@openfun/cunningham-react" "3.0.0"
"@types/node" "22.10.7"
clsx "2.1.1"
cmdk "1.0.4"
react "19.0.0"
react-arborist "3.4.3"
react-aria-components "1.6.0"
react-dom "19.0.0"
react-resizable-panels "2.1.7"
react-stately "3.35.0"
"@gulpjs/to-absolute-glob@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz#1fc2460d3953e1d9b9f2dfdb4bcc99da4710c021"
@@ -4049,6 +4093,21 @@
"@react-types/shared" "^3.27.0"
"@swc/helpers" "^0.5.0"
"@react-dnd/asap@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-4.0.1.tgz#5291850a6b58ce6f2da25352a64f1b0674871aab"
integrity sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg==
"@react-dnd/invariant@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@react-dnd/invariant/-/invariant-2.0.0.tgz#09d2e81cd39e0e767d7da62df9325860f24e517e"
integrity sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==
"@react-dnd/shallowequal@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz#a3031eb54129f2c66b2753f8404266ec7bf67f0a"
integrity sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==
"@react-pdf/fns@3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@react-pdf/fns/-/fns-3.0.0.tgz#2e0137d48b14c531b2f6a9214cb36ea2a7aea3ba"
@@ -6273,7 +6332,7 @@
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@22.13.9", "@types/node@^22.7.5":
"@types/node@*", "@types/node@22.10.7", "@types/node@22.13.9", "@types/node@^22.7.5":
version "22.13.9"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.9.tgz#5d9a8f7a975a5bd3ef267352deb96fb13ec02eca"
integrity sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==
@@ -7547,7 +7606,7 @@ clone@^2.1.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
clsx@^2.0.0, clsx@^2.1.1:
clsx@2.1.1, clsx@^2.0.0, clsx@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
@@ -8124,6 +8183,15 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
dnd-core@14.0.1:
version "14.0.1"
resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-14.0.1.tgz#76d000e41c494983210fb20a48b835f81a203c2e"
integrity sha512-+PVS2VPTgKFPYWo3vAFEA8WPbTf7/xo43TifH9G8S1KqnrQu0o77A3unrF5yOugy4mIz7K5wAVFHUcha7wsz6A==
dependencies:
"@react-dnd/asap" "^4.0.0"
"@react-dnd/invariant" "^2.0.0"
redux "^4.1.1"
doctrine@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
@@ -11484,6 +11552,11 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
"memoize-one@>=3.1.1 <6":
version "5.2.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
memoize-one@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
@@ -12930,6 +13003,17 @@ raw-body@2.5.2:
iconv-lite "0.4.24"
unpipe "1.0.0"
react-arborist@3.4.3:
version "3.4.3"
resolved "https://registry.yarnpkg.com/react-arborist/-/react-arborist-3.4.3.tgz#982791a07d1e279f279be88162c920112f3cee90"
integrity sha512-yFnq1nIQhT2uJY4TZVz2tgAiBb9lxSyvF4vC3S8POCK8xLzjGIxVv3/4dmYquQJ7AHxaZZArRGHiHKsEewKdTQ==
dependencies:
react-dnd "^14.0.3"
react-dnd-html5-backend "^14.0.3"
react-window "^1.8.11"
redux "^5.0.0"
use-sync-external-store "^1.2.0"
react-aria-components@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/react-aria-components/-/react-aria-components-1.5.0.tgz#9c51ba8427e827d1192f965249e16ff7d34cbb55"
@@ -13146,6 +13230,24 @@ react-aria@^3.37.0:
"@react-aria/visually-hidden" "^3.8.19"
"@react-types/shared" "^3.27.0"
react-dnd-html5-backend@^14.0.3:
version "14.1.0"
resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-14.1.0.tgz#b35a3a0c16dd3a2bfb5eb7ec62cf0c2cace8b62f"
integrity sha512-6ONeqEC3XKVf4eVmMTe0oPds+c5B9Foyj8p/ZKLb7kL2qh9COYxiBHv3szd6gztqi/efkmriywLUVlPotqoJyw==
dependencies:
dnd-core "14.0.1"
react-dnd@^14.0.3:
version "14.0.5"
resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-14.0.5.tgz#ecf264e220ae62e35634d9b941502f3fca0185ed"
integrity sha512-9i1jSgbyVw0ELlEVt/NkCUkxy1hmhJOkePoCH713u75vzHGyXhPDm28oLfc2NMSBjZRM1Y+wRjHXJT3sPrTy+A==
dependencies:
"@react-dnd/invariant" "^2.0.0"
"@react-dnd/shallowequal" "^2.0.0"
dnd-core "14.0.1"
fast-deep-equal "^3.1.3"
hoist-non-react-statics "^3.3.2"
react-dom@*, react-dom@19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57"
@@ -13230,6 +13332,11 @@ react-remove-scroll@^2.6.2:
use-callback-ref "^1.3.3"
use-sidecar "^1.1.3"
react-resizable-panels@2.1.7:
version "2.1.7"
resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz#afd29d8a3d708786a9f95183a38803c89f13c2e7"
integrity sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==
react-select@5.10.1:
version "5.10.1"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.10.1.tgz#e858dd98358ccd864b65d53ab0fb682cd5e96b89"
@@ -13276,6 +13383,69 @@ react-stately@3.34.0:
"@react-stately/tree" "^3.8.6"
"@react-types/shared" "^3.26.0"
react-stately@3.35.0, react-stately@^3.35.0:
version "3.35.0"
resolved "https://registry.yarnpkg.com/react-stately/-/react-stately-3.35.0.tgz#92bfc83bb4f7626a57c6aeabe4d08aeaab1fa2f7"
integrity sha512-1BH21J/TOHpyZe7c+f1BU2bnRWaBDTjLH0WdBuzNfPOXu7RBG3ebPIRvqd7UkPaVfIcol2QJnxe8S0a314JWKA==
dependencies:
"@react-stately/calendar" "^3.7.0"
"@react-stately/checkbox" "^3.6.11"
"@react-stately/collections" "^3.12.1"
"@react-stately/color" "^3.8.2"
"@react-stately/combobox" "^3.10.2"
"@react-stately/data" "^3.12.1"
"@react-stately/datepicker" "^3.12.0"
"@react-stately/disclosure" "^3.0.1"
"@react-stately/dnd" "^3.5.1"
"@react-stately/form" "^3.1.1"
"@react-stately/list" "^3.11.2"
"@react-stately/menu" "^3.9.1"
"@react-stately/numberfield" "^3.9.9"
"@react-stately/overlays" "^3.6.13"
"@react-stately/radio" "^3.10.10"
"@react-stately/searchfield" "^3.5.9"
"@react-stately/select" "^3.6.10"
"@react-stately/selection" "^3.19.0"
"@react-stately/slider" "^3.6.1"
"@react-stately/table" "^3.13.1"
"@react-stately/tabs" "^3.7.1"
"@react-stately/toggle" "^3.8.1"
"@react-stately/tooltip" "^3.5.1"
"@react-stately/tree" "^3.8.7"
"@react-types/shared" "^3.27.0"
react-stately@3.36.1:
version "3.36.1"
resolved "https://registry.yarnpkg.com/react-stately/-/react-stately-3.36.1.tgz#605c18e6aa7a900f19b066699b5b35b7800cb759"
integrity sha512-H9kiGAylNec/iE5qk7qQLV1cvtSAIVq3mgt87zx2EA+f+/sYy2oBtchFPaDiBf/m7xMEKf0Fr9zSLU6G99xQ8g==
dependencies:
"@react-stately/calendar" "^3.7.1"
"@react-stately/checkbox" "^3.6.12"
"@react-stately/collections" "^3.12.2"
"@react-stately/color" "^3.8.3"
"@react-stately/combobox" "^3.10.3"
"@react-stately/data" "^3.12.2"
"@react-stately/datepicker" "^3.13.0"
"@react-stately/disclosure" "^3.0.2"
"@react-stately/dnd" "^3.5.2"
"@react-stately/form" "^3.1.2"
"@react-stately/list" "^3.12.0"
"@react-stately/menu" "^3.9.2"
"@react-stately/numberfield" "^3.9.10"
"@react-stately/overlays" "^3.6.14"
"@react-stately/radio" "^3.10.11"
"@react-stately/searchfield" "^3.5.10"
"@react-stately/select" "^3.6.11"
"@react-stately/selection" "^3.20.0"
"@react-stately/slider" "^3.6.2"
"@react-stately/table" "^3.14.0"
"@react-stately/tabs" "^3.8.0"
"@react-stately/toast" "^3.0.0"
"@react-stately/toggle" "^3.8.2"
"@react-stately/tooltip" "^3.5.2"
"@react-stately/tree" "^3.8.8"
"@react-types/shared" "^3.28.0"
react-stately@^3.34.0:
version "3.36.0"
resolved "https://registry.yarnpkg.com/react-stately/-/react-stately-3.36.0.tgz#1544f0a742145d9bc2d67a8c76af3648a9982fd6"
@@ -13308,37 +13478,6 @@ react-stately@^3.34.0:
"@react-stately/tree" "^3.8.8"
"@react-types/shared" "^3.28.0"
react-stately@^3.35.0:
version "3.35.0"
resolved "https://registry.yarnpkg.com/react-stately/-/react-stately-3.35.0.tgz#92bfc83bb4f7626a57c6aeabe4d08aeaab1fa2f7"
integrity sha512-1BH21J/TOHpyZe7c+f1BU2bnRWaBDTjLH0WdBuzNfPOXu7RBG3ebPIRvqd7UkPaVfIcol2QJnxe8S0a314JWKA==
dependencies:
"@react-stately/calendar" "^3.7.0"
"@react-stately/checkbox" "^3.6.11"
"@react-stately/collections" "^3.12.1"
"@react-stately/color" "^3.8.2"
"@react-stately/combobox" "^3.10.2"
"@react-stately/data" "^3.12.1"
"@react-stately/datepicker" "^3.12.0"
"@react-stately/disclosure" "^3.0.1"
"@react-stately/dnd" "^3.5.1"
"@react-stately/form" "^3.1.1"
"@react-stately/list" "^3.11.2"
"@react-stately/menu" "^3.9.1"
"@react-stately/numberfield" "^3.9.9"
"@react-stately/overlays" "^3.6.13"
"@react-stately/radio" "^3.10.10"
"@react-stately/searchfield" "^3.5.9"
"@react-stately/select" "^3.6.10"
"@react-stately/selection" "^3.19.0"
"@react-stately/slider" "^3.6.1"
"@react-stately/table" "^3.13.1"
"@react-stately/tabs" "^3.7.1"
"@react-stately/toggle" "^3.8.1"
"@react-stately/tooltip" "^3.5.1"
"@react-stately/tree" "^3.8.7"
"@react-types/shared" "^3.27.0"
react-style-singleton@^2.2.2, react-style-singleton@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz#4265608be69a4d70cfe3047f2c6c88b2c3ace388"
@@ -13366,6 +13505,14 @@ react-transition-group@^4.3.0:
loose-envify "^1.4.0"
prop-types "^15.6.2"
react-window@^1.8.11:
version "1.8.11"
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.11.tgz#a857b48fa85bd77042d59cc460964ff2e0648525"
integrity sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==
dependencies:
"@babel/runtime" "^7.0.0"
memoize-one ">=3.1.1 <6"
react@*, react@19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/react/-/react-19.0.0.tgz#6e1969251b9f108870aa4bff37a0ce9ddfaaabdd"
@@ -13408,6 +13555,18 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
redux@^4.1.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
dependencies:
"@babel/runtime" "^7.9.2"
redux@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b"
integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==
reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
version "1.0.10"
resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9"
+1 -1
View File
@@ -1,7 +1,7 @@
environments:
dev:
values:
- version: 2.5.0
- version: 2.6.0
---
repositories:
- name: bitnami
+1 -1
View File
@@ -1,5 +1,5 @@
apiVersion: v2
type: application
name: docs
version: 2.5.0
version: 2.6.0
appVersion: latest
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "2.5.0",
"version": "2.6.0",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {