From da2021493887618cdc5816870733fcd88d069403 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Mon, 13 Nov 2023 23:30:22 +0000 Subject: [PATCH] Create new ListItem and FileUploader component and use on controls page (#15103) This adds a new ListItem component and FileUploader component and updates the Custom settings and scripts page to use this new ListItem component. This List component centralises where the markup and styles live. We still need to update the bootstrap list item and eula upload list item but will do that in the future. - [x] Manual QA for all new/changed functionality --- .../FileUploader/FileUploader.stories.tsx | 24 ++++ .../components/FileUploader/FileUploader.tsx | 22 +++- .../components/FileUploader/_styles.scss | 0 .../components/FileUploader/index.ts | 0 frontend/components/Graphic/_styles.scss | 6 + .../components/ListItem/ListItem.stories.tsx | 34 ++++++ frontend/components/ListItem/ListItem.tsx | 60 ++++++++++ frontend/components/ListItem/_styles.scss | 31 +++++ frontend/components/ListItem/index.ts | 1 + frontend/interfaces/mdm.ts | 2 + .../cards/CustomSettings/CustomSettings.tsx | 112 ++++++++---------- .../ProfileListItem/ProfileListItem.tsx | 68 ++++++----- .../components/ProfileListItem/_styles.scss | 18 ++- .../ProfileUploader/ProfileUploader.tsx | 73 ++++++++++++ .../ProfileUploader}/helpers.ts | 0 .../components/ProfileUploader/index.ts | 1 + .../ScriptListHeading/ScriptListHeading.tsx | 36 ------ .../components/ScriptListHeading/_styles.scss | 1 + .../ScriptListItem/ScriptListItem.tsx | 57 ++++----- .../components/ScriptListItem/_styles.scss | 64 +--------- .../ScriptUploader/ScriptUploader.tsx | 3 +- .../BootstrapPackageUploader.tsx | 3 +- .../components/EulaUploader/EulaUploader.tsx | 2 +- frontend/styles/var/mixins.scss | 1 + 24 files changed, 393 insertions(+), 226 deletions(-) create mode 100644 frontend/components/FileUploader/FileUploader.stories.tsx rename frontend/{pages/ManageControlsPage => }/components/FileUploader/FileUploader.tsx (75%) rename frontend/{pages/ManageControlsPage => }/components/FileUploader/_styles.scss (100%) rename frontend/{pages/ManageControlsPage => }/components/FileUploader/index.ts (100%) create mode 100644 frontend/components/Graphic/_styles.scss create mode 100644 frontend/components/ListItem/ListItem.stories.tsx create mode 100644 frontend/components/ListItem/ListItem.tsx create mode 100644 frontend/components/ListItem/_styles.scss create mode 100644 frontend/components/ListItem/index.ts create mode 100644 frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/ProfileUploader.tsx rename frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/{ => components/ProfileUploader}/helpers.ts (100%) create mode 100644 frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/index.ts diff --git a/frontend/components/FileUploader/FileUploader.stories.tsx b/frontend/components/FileUploader/FileUploader.stories.tsx new file mode 100644 index 0000000000..68de42c15d --- /dev/null +++ b/frontend/components/FileUploader/FileUploader.stories.tsx @@ -0,0 +1,24 @@ +import { Meta, StoryObj } from "@storybook/react"; + +import FileUploader from "./FileUploader"; + +const meta: Meta = { + title: "Components/FileUploader", + component: FileUploader, + args: { + graphicName: "file-configuration-profile", + message: "The main message", + additionalInfo: "The additional message", + accept: ".pdf", + isLoading: false, + onFileUpload: () => { + alert("File uploaded!"); + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Basic: Story = {}; diff --git a/frontend/pages/ManageControlsPage/components/FileUploader/FileUploader.tsx b/frontend/components/FileUploader/FileUploader.tsx similarity index 75% rename from frontend/pages/ManageControlsPage/components/FileUploader/FileUploader.tsx rename to frontend/components/FileUploader/FileUploader.tsx index 31c32ecf2b..556439e13c 100644 --- a/frontend/pages/ManageControlsPage/components/FileUploader/FileUploader.tsx +++ b/frontend/components/FileUploader/FileUploader.tsx @@ -8,10 +8,23 @@ import Graphic from "components/Graphic"; const baseClass = "file-uploader"; +type ISupportedGraphicNames = Extract< + GraphicNames, + | "file-configuration-profile" + | "file-sh" + | "file-py" + | "file-script" + | "file-pdf" + | "file-pkg" + | "file-p7m" + | "file-pem" +>; + interface IFileUploaderProps { - graphicName: GraphicNames; + graphicName: ISupportedGraphicNames; message: string; additionalInfo?: string; + /** Controls the loading spinner on the upload button */ isLoading?: boolean; /** A comma seperated string of one or more file types accepted to upload. * This is the same as the html accept attribute. @@ -22,6 +35,9 @@ interface IFileUploaderProps { onFileUpload: (files: FileList | null) => void; } +/** + * A component that encapsulates the UI for uploading a file. + */ const FileUploader = ({ graphicName, message, @@ -37,7 +53,9 @@ const FileUploader = ({

{message}

-

{additionalInfo}

+ {additionalInfo && ( +

{additionalInfo}

+ )} + + + ), + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Basic: Story = {}; diff --git a/frontend/components/ListItem/ListItem.tsx b/frontend/components/ListItem/ListItem.tsx new file mode 100644 index 0000000000..6c6323bd4d --- /dev/null +++ b/frontend/components/ListItem/ListItem.tsx @@ -0,0 +1,60 @@ +import React from "react"; +import classnames from "classnames"; + +import Graphic from "components/Graphic"; +import { GraphicNames } from "components/graphics"; + +const baseClass = "list-item"; + +type ISupportedGraphicNames = Extract< + GraphicNames, + | "file-configuration-profile" + | "file-sh" + | "file-py" + | "file-script" + | "file-pdf" + | "file-pkg" + | "file-p7m" + | "file-pem" +>; + +/** + * A generic ListItem component that can be used to display a list of items. It + * encapsulates the UI logic and styling for displaying a graphic, title, + * details, and actions. + */ +interface IListItemProps { + /** The grahpic you want to display for this list item. */ + graphic: ISupportedGraphicNames; + title: string; + details: React.ReactNode; + /** A collection of React Nodes that will render as list item actions. Can be + * used to render buttons, links, etc. + */ + actions: React.ReactNode; + className?: string; +} + +const ListItem = ({ + graphic, + title, + details, + actions, + className, +}: IListItemProps) => { + const classNames = classnames(baseClass, className); + return ( +
+
+ +
+ {title} +
{details}
+
+
+
{actions}
+
+ ); +}; + +export default ListItem; diff --git a/frontend/components/ListItem/_styles.scss b/frontend/components/ListItem/_styles.scss new file mode 100644 index 0000000000..d184de2728 --- /dev/null +++ b/frontend/components/ListItem/_styles.scss @@ -0,0 +1,31 @@ +.list-item { + display: flex; + justify-content: space-between; + align-items: center; + + &__main-content { + display: flex; + align-items: center; + gap: $pad-medium; + } + + &__info { + display: flex; + flex-direction: column; + } + + &__title { + font-size: $x-small; + font-weight: $bold; + } + + &__details { + font-size: $xx-small; + } + + &__actions { + display: flex; + justify-content: flex-end; + gap: $pad-medium; + } +} diff --git a/frontend/components/ListItem/index.ts b/frontend/components/ListItem/index.ts new file mode 100644 index 0000000000..3fb6c1bab3 --- /dev/null +++ b/frontend/components/ListItem/index.ts @@ -0,0 +1 @@ +export { default } from "./ListItem"; diff --git a/frontend/interfaces/mdm.ts b/frontend/interfaces/mdm.ts index 9cb068b0df..cab185ece7 100644 --- a/frontend/interfaces/mdm.ts +++ b/frontend/interfaces/mdm.ts @@ -55,6 +55,8 @@ export interface IMdmSummaryResponse { mobile_device_management_solution: IMdmSolution[] | null; } +type SupportedMdmPlatform = "darwin" | "windows"; + export interface IMdmProfile { profile_id: number; team_id: number; diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx index db92bd845e..f1c45adf9d 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx @@ -1,27 +1,27 @@ import React, { useContext, useRef, useState } from "react"; import { useQuery } from "react-query"; -import { AxiosResponse } from "axios"; -import { IApiError } from "interfaces/errors"; import { IMdmProfile, IMdmProfilesResponse } from "interfaces/mdm"; import mdmAPI from "services/entities/mdm"; -import { AppContext } from "context/app"; import { NotificationContext } from "context/notification"; import CustomLink from "components/CustomLink"; +import Spinner from "components/Spinner"; +import DataError from "components/DataError"; -import FileUploader from "../../../components/FileUploader"; import UploadList from "../../../components/UploadList"; -import { UPLOAD_ERROR_MESSAGES, getErrorMessage } from "./helpers"; import DeleteProfileModal from "./components/DeleteProfileModal/DeleteProfileModal"; import ProfileListItem from "./components/ProfileListItem"; import ProfileListHeading from "./components/ProfileListHeading"; +import ProfileUploader from "./components/ProfileUploader"; const baseClass = "custom-settings"; interface ICustomSettingsProps { currentTeamId: number; + /** handler that fires when a change occures on the section (e.g. disk encryption + * enabled, profile uploaded) */ onMutation: () => void; } @@ -32,7 +32,6 @@ const CustomSettings = ({ const { renderFlash } = useContext(NotificationContext); const [showDeleteProfileModal, setShowDeleteProfileModal] = useState(false); - const [showLoading, setShowLoading] = useState(false); const selectedProfile = useRef(null); @@ -41,48 +40,23 @@ const CustomSettings = ({ setShowDeleteProfileModal(true); }; - const { data: profiles, refetch: refetchProfiles } = useQuery< - IMdmProfilesResponse, - unknown, - IMdmProfile[] | null - >(["profiles", currentTeamId], () => mdmAPI.getProfiles(currentTeamId), { - select: (data) => data.profiles, - refetchOnWindowFocus: false, - }); - - const onFileUpload = async (files: FileList | null) => { - setShowLoading(true); - - if (!files || files.length === 0) { - setShowLoading(false); - return; + const { + data: profiles, + isLoading: isLoadingProfiles, + isError: isErrorProfiles, + refetch: refetchProfiles, + } = useQuery( + ["profiles", currentTeamId], + () => mdmAPI.getProfiles(currentTeamId), + { + select: (data) => data.profiles, + refetchOnWindowFocus: false, } + ); - const file = files[0]; - - if ( - // file.type might be empty on some systems as uncommon file extensions - // would return an empty string. - (file.type !== "" && file.type !== "application/x-apple-aspen-config") || - !file.name.includes(".mobileconfig") - ) { - renderFlash("error", UPLOAD_ERROR_MESSAGES.wrongType.message); - setShowLoading(false); - return; - } - - try { - await mdmAPI.uploadProfile(file, currentTeamId); - refetchProfiles(); - onMutation(); - renderFlash("success", "Successfully uploaded!"); - } catch (e) { - const error = e as AxiosResponse; - const errMessage = getErrorMessage(error); - renderFlash("error", errMessage); - } finally { - setShowLoading(false); - } + const onUploadProfile = () => { + refetchProfiles(); + onMutation(); }; const onCancelDelete = () => { @@ -104,6 +78,30 @@ const CustomSettings = ({ } }; + const renderProfileList = () => { + if (isLoadingProfiles) { + return ; + } + + if (isErrorProfiles) { + return ; + } + + if (!profiles || profiles.length === 0) { + return null; + } + + return ( + ( + + )} + /> + ); + }; + return (

Custom settings

@@ -115,24 +113,10 @@ const CustomSettings = ({ url="https://fleetdm.com/docs/using-fleet/mdm-custom-macos-settings" />

- - {profiles && ( - ( - - )} - /> - )} - - {showDeleteProfileModal && selectedProfile.current && ( { + return ( +
+ + {`Uploaded ${formatDistanceToNow(new Date(createdAt))} ago`} + +
+ ); +}; + interface IProfileListItemProps { profile: IMdmProfile; onDelete: (profile: IMdmProfile) => void; @@ -25,35 +40,30 @@ const ProfileListItem = ({ profile, onDelete }: IProfileListItemProps) => { }; return ( -
-
- -
- {profile.name} - - {`Uploaded ${formatDistanceToNow( - new Date(profile.created_at) - )} ago`} - -
-
-
- - -
-
+ } + actions={ + <> + + + + } + /> ); }; diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/_styles.scss b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/_styles.scss index d22eea6a6e..a9911c3428 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/_styles.scss +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/_styles.scss @@ -1,3 +1,19 @@ .profile-list-item { - @include list-item; + &__profile-details { + display: flex; + flex-direction: row; + gap: $pad-xsmall; + } + + &__list-item-details { + display: flex; + flex-direction: row; + gap: $pad-xsmall; + font-size: $xx-small; + } + + &__action-button { + width: 40px; + height: 40px; + } } diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/ProfileUploader.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/ProfileUploader.tsx new file mode 100644 index 0000000000..dd1168f0ab --- /dev/null +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/ProfileUploader.tsx @@ -0,0 +1,73 @@ +import React, { useContext, useState } from "react"; +import { AxiosResponse } from "axios"; + +import { IApiError } from "interfaces/errors"; +import { NotificationContext } from "context/notification"; +import mdmAPI from "services/entities/mdm"; + +import FileUploader from "components/FileUploader"; + +import { UPLOAD_ERROR_MESSAGES, getErrorMessage } from "./helpers"; + +const baseClass = "profile-uploader"; + +interface IProfileUploaderProps { + currentTeamId: number; + onUpload: () => void; +} + +const ProfileUploader = ({ + currentTeamId, + onUpload, +}: IProfileUploaderProps) => { + const [showLoading, setShowLoading] = useState(false); + + const { renderFlash } = useContext(NotificationContext); + + const onFileUpload = async (files: FileList | null) => { + setShowLoading(true); + + if (!files || files.length === 0) { + setShowLoading(false); + return; + } + + const file = files[0]; + + if ( + // file.type might be empty on some systems as uncommon file extensions + // would return an empty string. + (file.type !== "" && file.type !== "application/x-apple-aspen-config") || + !file.name.includes(".mobileconfig") + ) { + renderFlash("error", UPLOAD_ERROR_MESSAGES.wrongType.message); + setShowLoading(false); + return; + } + + try { + await mdmAPI.uploadProfile(file, currentTeamId); + renderFlash("success", "Successfully uploaded!"); + onUpload(); + } catch (e) { + const error = e as AxiosResponse; + const errMessage = getErrorMessage(error); + renderFlash("error", errMessage); + } finally { + setShowLoading(false); + } + }; + + return ( + + ); +}; + +export default ProfileUploader; diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/helpers.ts b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/helpers.ts similarity index 100% rename from frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/helpers.ts rename to frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/helpers.ts diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/index.ts b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/index.ts new file mode 100644 index 0000000000..9cc11b7ea3 --- /dev/null +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/index.ts @@ -0,0 +1 @@ +export { default } from "./ProfileUploader"; diff --git a/frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/ScriptListHeading.tsx b/frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/ScriptListHeading.tsx index 4b1503521e..e4e08c12b8 100644 --- a/frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/ScriptListHeading.tsx +++ b/frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/ScriptListHeading.tsx @@ -1,8 +1,4 @@ import React from "react"; -import ReactTooltip from "react-tooltip"; - -import Icon from "components/Icon"; -import { COLORS } from "styles/var/colors"; const baseClass = "script-list-heading"; @@ -17,38 +13,6 @@ const ScriptListHeading = () => { > Actions
- - - - Script ran and exited with status code 0. - - - - - Script will run when the host comes online. - - - - - Script ran and exited with a non-zero status code. Click on a host to - view error(s). - - ); }; diff --git a/frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/_styles.scss b/frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/_styles.scss index 5670731f47..3094cf5e33 100644 --- a/frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/_styles.scss +++ b/frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/_styles.scss @@ -1,4 +1,5 @@ .script-list-heading { + font-weight: $bold; display: flex; justify-content: space-between; diff --git a/frontend/pages/ManageControlsPage/Scripts/components/ScriptListItem/ScriptListItem.tsx b/frontend/pages/ManageControlsPage/Scripts/components/ScriptListItem/ScriptListItem.tsx index b8ecead6a4..d0b3f848eb 100644 --- a/frontend/pages/ManageControlsPage/Scripts/components/ScriptListItem/ScriptListItem.tsx +++ b/frontend/pages/ManageControlsPage/Scripts/components/ScriptListItem/ScriptListItem.tsx @@ -7,7 +7,7 @@ import scriptAPI, { IScript } from "services/entities/scripts"; import Icon from "components/Icon"; import Button from "components/buttons/Button"; -import Graphic from "components/Graphic"; +import ListItem from "components/ListItem"; const baseClass = "script-list-item"; @@ -45,33 +45,34 @@ const ScriptListItem = ({ script, onDelete }: IScriptListItemProps) => { }; return ( -
-
- -
- {script.name} - - {`Uploaded ${formatDistanceToNow(new Date(script.created_at))} ago`} - -
-
-
- - -
-
+ {`Uploaded ${formatDistanceToNow( + new Date(script.created_at) + )} ago`} + } + actions={ + <> + + + + } + /> ); }; diff --git a/frontend/pages/ManageControlsPage/Scripts/components/ScriptListItem/_styles.scss b/frontend/pages/ManageControlsPage/Scripts/components/ScriptListItem/_styles.scss index 59402e64bc..38727f6444 100644 --- a/frontend/pages/ManageControlsPage/Scripts/components/ScriptListItem/_styles.scss +++ b/frontend/pages/ManageControlsPage/Scripts/components/ScriptListItem/_styles.scss @@ -1,68 +1,6 @@ .script-list-item { - display: flex; - align-items: center; - - &__value-group { - flex: 1; - } - - &__script-data { - display: flex; - align-items: center; - } - - &__script-info { - margin-left: $pad-medium; - display: flex; - flex-direction: column; - } - - &__script-name { - font-size: $x-small; - } - - &__script-uploaded { - font-size: $xx-small; - } - - &__script-statuses { - display: flex; - justify-content: center; - - span { - width: 100px; - text-align: center; - } - } - - &__has-value { - color: $core-vibrant-blue; - } - - &__script-actions { - display: flex; - justify-content: flex-end; - } - - &__refresh-button, - &__download-button, - &__delete-button { + &__action-button { width: 40px; height: 40px; } - - &__refresh-button, - &__download-button { - margin-right: $pad-medium; - } - - @media (max-width: $break-md) { - &__script-statuses { - justify-content: flex-end; - } - - &__script-actions { - display: none; - } - } } diff --git a/frontend/pages/ManageControlsPage/Scripts/components/ScriptUploader/ScriptUploader.tsx b/frontend/pages/ManageControlsPage/Scripts/components/ScriptUploader/ScriptUploader.tsx index a53d4135a5..bee6f85f0a 100644 --- a/frontend/pages/ManageControlsPage/Scripts/components/ScriptUploader/ScriptUploader.tsx +++ b/frontend/pages/ManageControlsPage/Scripts/components/ScriptUploader/ScriptUploader.tsx @@ -5,7 +5,8 @@ import { IApiError } from "interfaces/errors"; import { NotificationContext } from "context/notification"; import scriptAPI from "services/entities/scripts"; -import FileUploader from "pages/ManageControlsPage/components/FileUploader"; +import FileUploader from "components/FileUploader"; + import { getErrorMessage } from "./helpers"; const baseClass = "script-uploader"; diff --git a/frontend/pages/ManageControlsPage/SetupExperience/cards/BootstrapPackage/components/BootstrapPackageUploader/BootstrapPackageUploader.tsx b/frontend/pages/ManageControlsPage/SetupExperience/cards/BootstrapPackage/components/BootstrapPackageUploader/BootstrapPackageUploader.tsx index c332d0b12f..71de0ef3ec 100644 --- a/frontend/pages/ManageControlsPage/SetupExperience/cards/BootstrapPackage/components/BootstrapPackageUploader/BootstrapPackageUploader.tsx +++ b/frontend/pages/ManageControlsPage/SetupExperience/cards/BootstrapPackage/components/BootstrapPackageUploader/BootstrapPackageUploader.tsx @@ -6,7 +6,8 @@ import { NotificationContext } from "context/notification"; import mdmAPI from "services/entities/mdm"; import CustomLink from "components/CustomLink"; -import FileUploader from "pages/ManageControlsPage/components/FileUploader"; +import FileUploader from "components/FileUploader"; + import { UPLOAD_ERROR_MESSAGES, getErrorMessage } from "./helpers"; const baseClass = "bootstrap-package-uploader"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/AutomaticEnrollment/components/EulaSection/components/EulaUploader/EulaUploader.tsx b/frontend/pages/admin/IntegrationsPage/cards/AutomaticEnrollment/components/EulaSection/components/EulaUploader/EulaUploader.tsx index 28b22e9af7..89933f4f11 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/AutomaticEnrollment/components/EulaSection/components/EulaUploader/EulaUploader.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/AutomaticEnrollment/components/EulaSection/components/EulaUploader/EulaUploader.tsx @@ -5,7 +5,7 @@ import { IApiError } from "interfaces/errors"; import mdmAPI from "services/entities/mdm"; import { NotificationContext } from "context/notification"; -import FileUploader from "pages/ManageControlsPage/components/FileUploader/FileUploader"; +import FileUploader from "components/FileUploader/FileUploader"; import CustomLink from "components/CustomLink"; import { UPLOAD_ERROR_MESSAGES, getErrorMessage } from "./helpers"; diff --git a/frontend/styles/var/mixins.scss b/frontend/styles/var/mixins.scss index ab3e740616..7ae94ddcd2 100644 --- a/frontend/styles/var/mixins.scss +++ b/frontend/styles/var/mixins.scss @@ -68,6 +68,7 @@ $max-width: 2560px; &__list-item-name { font-size: $x-small; + font-weight: $bold; } &__list-item-uploaded {