diff --git a/.changeset/tall-pianos-lick.md b/.changeset/tall-pianos-lick.md new file mode 100644 index 00000000..40268fb8 --- /dev/null +++ b/.changeset/tall-pianos-lick.md @@ -0,0 +1,9 @@ +--- +'@axis-backstage/plugin-jira-dashboard': major +--- + +Migrate Jira Dashboard to Backstage UI. Cards, tabs, links, now use Backstage UI components. + +The migration removes the `@mui/material` and `@mui/styles` direct dependencies, but keeping using MUI tables. + +**Breaking:** Consumers must install and configure `@backstage/ui`. diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index af62933f..2fa3b95f 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -1,4 +1,5 @@ import '@backstage/cli/asset-types'; +import '@backstage/ui/css/styles.css'; import ReactDOM from 'react-dom/client'; import App from './App'; diff --git a/plugins/jira-dashboard/dev/index.tsx b/plugins/jira-dashboard/dev/index.tsx index 3939e331..b0af60ec 100644 --- a/plugins/jira-dashboard/dev/index.tsx +++ b/plugins/jira-dashboard/dev/index.tsx @@ -1,37 +1,211 @@ +import { ReactNode } from 'react'; import { createDevApp } from '@backstage/dev-utils'; import { EntityJiraDashboardContent, jiraDashboardPlugin } from '../src'; -import { jiraDashboardApiRef } from '../src/api'; -import { Page, Content } from '@backstage/core-components'; +import { JiraDashboardApi, jiraDashboardApiRef } from '../src/api'; +import { JiraDashboardContent } from '../src/components/JiraDashboardContent'; +import { JiraProjectCard } from '../src/components/JiraProjectCard'; +import { JiraTable } from '../src/components/JiraTable'; +import { JiraUserIssuesCard } from '../src/components/JiraUserIssuesCard'; +import { JiraUserIssuesTable } from '../src/components/JiraUserIssuesTable'; +import { Content, Page } from '@backstage/core-components'; import { EntityProvider } from '@backstage/plugin-catalog-react'; -import mockEntity from './__fixtures__/entity.json'; -import mockJiraResponse from './__fixtures__/jiraResponse.json'; -/* Use to test with Jira Cloud +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { Flex, Text } from '@backstage/ui'; +import { + Issue, + JiraDataResponse, + JiraResponse, + Project, +} from '@axis-backstage/plugin-jira-dashboard-common'; +import multiProjectResponse from './__fixtures__/jiraResponse.json'; +import singleProjectResponse from './__fixtures__/singleProjectResponse.json'; +import cloudResponse from './__fixtures__/jiraCloudResponse.json'; +import multiProjectEntity from './__fixtures__/entity.json'; -import mockJiraCloudResponse from './__fixtures__/jiraCloudResponse.json'; -*/ +// One entity per dashboard variant. The mock API returns a different fixture +// per entity ref, so all three dashboards can coexist in the same dev app. +const singleProjectEntity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'single-project-service', + description: 'Entity backed by a single Jira Server project', + annotations: { 'jira.com/project-key': 'BS' }, + }, + spec: { lifecycle: 'production', type: 'service', owner: 'user:guest' }, +}; + +const cloudProjectEntity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'jenkins-meeting-actions', + description: 'Entity backed by a Jira Cloud project', + annotations: { 'jira.com/project-key': 'MEETING' }, + }, + spec: { lifecycle: 'production', type: 'service', owner: 'user:guest' }, +}; + +// Fixtures are intentionally partial vs the full Jira schema, hence the casts. +const responsesByEntityRef: Record = { + [stringifyEntityRef(multiProjectEntity as Entity)]: + multiProjectResponse as unknown as JiraResponse, + [stringifyEntityRef(singleProjectEntity)]: + singleProjectResponse as unknown as JiraResponse, + [stringifyEntityRef(cloudProjectEntity)]: + cloudResponse as unknown as JiraResponse, +}; + +const userIssues = (singleProjectResponse.data[0].issues ?? + []) as unknown as Issue[]; + +const mockJiraDashboardApi: JiraDashboardApi = { + getJiraResponseByEntity: async entityRef => + responsesByEntityRef[entityRef] ?? + (multiProjectResponse as unknown as JiraResponse), + getLoggedInUserIssues: async () => userIssues, + getProjectAvatar: async () => undefined, +}; + +// Standalone fixtures for the component-in-isolation pages. +const sampleProject = singleProjectResponse.project as Project; +const sampleDataGroup = singleProjectResponse + .data[0] as unknown as JiraDataResponse; +const emptyDataGroup: JiraDataResponse = { + name: 'No open issues', + type: 'filter', + query: 'resolution = Unresolved ORDER BY updated DESC', + issues: [], +}; +const minimalProject: Project = { + name: 'Minimal Project', + key: 'MIN', + description: '', + avatarUrls: { + '48x48': 'https://api.dicebear.com/6.x/open-peeps/svg?seed=Min', + }, + projectTypeKey: '', + lead: { key: '', displayName: '' }, + self: 'https://jira.com/project/999', +}; + +function DevPage(props: { children: ReactNode }) { + return ( + + {props.children} + + ); +} + +function Section(props: { title: string; children: ReactNode }) { + return ( + + {props.title} + {props.children} + + ); +} createDevApp() .registerPlugin(jiraDashboardPlugin) .registerApi({ api: jiraDashboardApiRef, deps: {}, - factory: () => - ({ - getJiraResponseByEntity: async () => mockJiraResponse, - getProjectAvatar: async () => mockJiraResponse, - } as any), + factory: () => mockJiraDashboardApi, }) .addPage({ + title: 'Dashboard: Multi-project', + path: '/jira-dashboard', element: ( - - - - - - - + + + + + + ), + }) + .addPage({ + title: 'Dashboard: Single project', + path: '/jira-dashboard-single', + element: ( + + + + + + ), + }) + .addPage({ + title: 'Dashboard: Jira Cloud', + path: '/jira-dashboard-cloud', + element: ( + + + + + + ), + }) + .addPage({ + title: 'JiraTable states', + path: '/jira-table', + element: ( + +
+ +
+
+ +
+
+ +
+
+ ), + }) + .addPage({ + title: 'JiraProjectCard', + path: '/jira-project-card', + element: ( + +
+ +
+
+ +
+
+ ), + }) + .addPage({ + title: 'JiraUserIssuesCard', + path: '/jira-user-issues', + element: ( + +
+ +
+
+ +
+
+ +
+
), - title: 'Root Page', - path: '/jira-dashboard', }) .render(); diff --git a/plugins/jira-dashboard/package.json b/plugins/jira-dashboard/package.json index 8a7e79e4..5144fa87 100644 --- a/plugins/jira-dashboard/package.json +++ b/plugins/jira-dashboard/package.json @@ -52,8 +52,7 @@ "@backstage/plugin-catalog-react": "^3.2.0", "@backstage/plugin-home-react": "^0.1.40", "@backstage/theme": "^0.7.3", - "@mui/material": "^5.15.7", - "@mui/styles": "^5.15.7", + "@backstage/ui": "^0.17.0", "lodash": "^4.18.1", "luxon": "^3.5.0", "react-use": "^17.2.4", diff --git a/plugins/jira-dashboard/src/components/JiraDashboardContent/JiraDashboardContent.tsx b/plugins/jira-dashboard/src/components/JiraDashboardContent/JiraDashboardContent.tsx index 49610372..80852a5d 100644 --- a/plugins/jira-dashboard/src/components/JiraDashboardContent/JiraDashboardContent.tsx +++ b/plugins/jira-dashboard/src/components/JiraDashboardContent/JiraDashboardContent.tsx @@ -3,11 +3,10 @@ import { ResponseErrorPanel, TableFilter, TableOptions, - TabbedCard, - CardTab, } from '@backstage/core-components'; +import { Tab, TabList, TabPanel, Tabs } from '@backstage/ui'; import { useApi } from '@backstage/core-plugin-api'; -import { useEntity } from '@backstage/plugin-catalog-react'; +import { EntityInfoCard, useEntity } from '@backstage/plugin-catalog-react'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { jiraDashboardApiRef } from '../../api'; import { useJira } from '../../hooks/useJira'; @@ -44,18 +43,27 @@ export const JiraDashboardContent = (props?: { : []; return projects.length > 1 ? ( - - {projects.map(project => ( - - - - ))} - + + + + {projects.map(project => ( + + {project.name} + + ))} + + {projects.map(project => ( + + + + ))} + + ) : ( { return ( - - + - + {tableData.map((value: JiraDataResponse) => ( - - + ))} - + ); }; diff --git a/plugins/jira-dashboard/src/components/JiraProjectCard/JiraProjectCard.tsx b/plugins/jira-dashboard/src/components/JiraProjectCard/JiraProjectCard.tsx index 69a42e1b..cdc071ab 100644 --- a/plugins/jira-dashboard/src/components/JiraProjectCard/JiraProjectCard.tsx +++ b/plugins/jira-dashboard/src/components/JiraProjectCard/JiraProjectCard.tsx @@ -1,8 +1,5 @@ -import Divider from '@mui/material/Divider'; -import Stack from '@mui/material/Stack'; -import Typography from '@mui/material/Typography'; -import { Avatar, InfoCard } from '@backstage/core-components'; -import { LinkButton } from '@backstage/core-components'; +import { Avatar, ButtonLink, Flex } from '@backstage/ui'; +import { EntityInfoCard } from '@backstage/plugin-catalog-react'; import { Project } from '@axis-backstage/plugin-jira-dashboard-common'; import { ProjectInfoLabel } from './ProjectInfoLabel'; import { getProjectUrl } from '../../lib'; @@ -11,24 +8,30 @@ type JiraProjectCardProps = { project: Project; }; +const CardTitle = (props: { title: string; pictureSrc?: string }) => ( + + + {props.title} + +); + export const JiraProjectCard = ({ project }: JiraProjectCardProps) => { - return ( - - - + const title = project.projectTypeKey + ? `${project.name} | ${project.projectTypeKey}` + : project.name; - - {project.name} | {project.projectTypeKey ?? ''} - - - - + return ( + + } + > + {project.projectCategory?.name && ( { value={project?.lead?.displayName || project?.lead?.key} /> )} - - + Go to project - - + + ); }; diff --git a/plugins/jira-dashboard/src/components/JiraProjectCard/ProjectInfoLabel.tsx b/plugins/jira-dashboard/src/components/JiraProjectCard/ProjectInfoLabel.tsx index 58f52c5d..c76a39e0 100644 --- a/plugins/jira-dashboard/src/components/JiraProjectCard/ProjectInfoLabel.tsx +++ b/plugins/jira-dashboard/src/components/JiraProjectCard/ProjectInfoLabel.tsx @@ -1,5 +1,4 @@ -import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; +import { Box, Text } from '@backstage/ui'; type ProjectInfoLabelProps = { label: string; @@ -9,15 +8,12 @@ type ProjectInfoLabelProps = { export const ProjectInfoLabel = ({ label, value }: ProjectInfoLabelProps) => { return ( - theme.palette.text.disabled }} - > + {label} - - + + {value} - + ); }; diff --git a/plugins/jira-dashboard/src/components/JiraTable/JiraTable.tsx b/plugins/jira-dashboard/src/components/JiraTable/JiraTable.tsx index fd1e59b2..f4c371c7 100644 --- a/plugins/jira-dashboard/src/components/JiraTable/JiraTable.tsx +++ b/plugins/jira-dashboard/src/components/JiraTable/JiraTable.tsx @@ -1,5 +1,5 @@ import { CSSProperties } from 'react'; -import Typography from '@mui/material/Typography'; +import { Flex, Link, Text } from '@backstage/ui'; import { Issue, JiraDataResponse, @@ -7,13 +7,12 @@ import { } from '@axis-backstage/plugin-jira-dashboard-common'; import { ErrorPanel, - InfoCard, - Link, Table, TableColumn, TableFilter, TableOptions, } from '@backstage/core-components'; +import { EntityInfoCard } from '@backstage/plugin-catalog-react'; import { capitalize } from 'lodash'; import { columns } from './columns'; import { getJiraBaseUrl, transformAssignees } from '../../lib'; @@ -26,10 +25,9 @@ type Props = { tableColumns?: TableColumn[]; tableStyle?: TableComponentProps['style']; /** - * CSS styles to apply to the top-most element, being the table component or - * the wrapping InfoCard component if filters are shown. - * If no filters are shown, the style prop is merged with the tableStyle prop - * and used in the table component. + * CSS styles merged into the table component's style (after tableStyle). + * Only applied when filters are not shown; with filters the table is wrapped + * in an EntityInfoCard and this is ignored. */ style?: CSSProperties; showFilters?: TableFilter[] | boolean; @@ -74,17 +72,23 @@ export const JiraTable = ({ } } let title = ( - + {`${capitalize(tableContent.name)} (${nbrOfIssues})`} - + ); if (project && tableContent.query) { title = ( {`${capitalize(tableContent.name)} (${nbrOfIssues})`} @@ -104,7 +108,7 @@ export const JiraTable = ({ if (showFilters) { return ( - + options={{ paging: false, @@ -114,15 +118,15 @@ export const JiraTable = ({ }} filters={filters} emptyContent={ - - No issues found  - + + No issues found  + } data={tableContent.issues || []} columns={tableColumns} style={baseTableStyle} /> - + ); } @@ -137,9 +141,9 @@ export const JiraTable = ({ }} filters={filters} emptyContent={ - - No issues found  - + + No issues found  + } data={tableContent.issues || []} columns={tableColumns} diff --git a/plugins/jira-dashboard/src/components/JiraTable/cells/AssigneeCell.test.tsx b/plugins/jira-dashboard/src/components/JiraTable/cells/AssigneeCell.test.tsx index c3280bf4..39361990 100644 --- a/plugins/jira-dashboard/src/components/JiraTable/cells/AssigneeCell.test.tsx +++ b/plugins/jira-dashboard/src/components/JiraTable/cells/AssigneeCell.test.tsx @@ -1,6 +1,5 @@ import { render, screen } from '@testing-library/react'; import { AssigneeCell } from './AssigneeCell'; -import { ThemeProvider, createTheme } from '@mui/material/styles'; import { MemoryRouter } from 'react-router-dom'; import { Issue } from '@axis-backstage/plugin-jira-dashboard-common'; @@ -11,11 +10,7 @@ jest.mock('@backstage/plugin-catalog-react', () => ({ })); const renderWithProviders = (ui: React.ReactElement) => - render( - - {ui} - , - ); + render({ui}); describe('AssigneeCell', () => { const baseAssignee: Issue['fields']['assignee'] = { diff --git a/plugins/jira-dashboard/src/components/JiraTable/cells/AssigneeCell.tsx b/plugins/jira-dashboard/src/components/JiraTable/cells/AssigneeCell.tsx index 64acd9c3..34125ffb 100644 --- a/plugins/jira-dashboard/src/components/JiraTable/cells/AssigneeCell.tsx +++ b/plugins/jira-dashboard/src/components/JiraTable/cells/AssigneeCell.tsx @@ -1,6 +1,4 @@ -import { Avatar, Link } from '@backstage/core-components'; -import Typography from '@mui/material/Typography'; -import Stack from '@mui/material/Stack'; +import { Avatar, Flex, Link, Text } from '@backstage/ui'; import { Issue } from '@axis-backstage/plugin-jira-dashboard-common'; import { EntityPeekAheadPopover } from '@backstage/plugin-catalog-react'; import { stringifyEntityRef } from '@backstage/catalog-model'; @@ -24,33 +22,31 @@ export const AssigneeCell = ({ assignee }: Props) => { const name = assignee.name || assignee.displayName; if (!name || name.toLowerCase() === 'unassigned') { return ( - - - Unassigned - + + Unassigned + ); } + const displayName = assignee.displayName || assignee.name || ''; const avatar = ( - + - - {assignee.displayName || assignee.name} - - + + {displayName} + + ); if (assignee.name) { @@ -63,7 +59,8 @@ export const AssigneeCell = ({ assignee }: Props) => { return ( {avatar} diff --git a/plugins/jira-dashboard/src/components/JiraTable/columns.tsx b/plugins/jira-dashboard/src/components/JiraTable/columns.tsx index 4d32028e..ed6683ca 100644 --- a/plugins/jira-dashboard/src/components/JiraTable/columns.tsx +++ b/plugins/jira-dashboard/src/components/JiraTable/columns.tsx @@ -1,9 +1,7 @@ -import { Link, TableColumn } from '@backstage/core-components'; +import { TableColumn } from '@backstage/core-components'; import { Issue } from '@axis-backstage/plugin-jira-dashboard-common'; -import Typography from '@mui/material/Typography'; +import { Badge, Flex, Link, Text } from '@backstage/ui'; import { getIssueUrl } from '../../lib'; -import Chip from '@mui/material/Chip'; -import Box from '@mui/material/Box'; import { DateTime } from 'luxon'; import { AssigneeCell } from './cells/AssigneeCell'; @@ -21,20 +19,21 @@ export const columnKey: TableColumn = { return null; } return ( - - + + {issue.fields?.issuetype.name} {issue.key} - + ); }, @@ -59,8 +58,11 @@ export const columnSummary: TableColumn = { return ( {issue.fields?.summary} @@ -80,7 +82,13 @@ export const columnPriority: TableColumn = { return null; } return ( - + {issue.fields?.priority?.name} = { return null; } return ( - - + + {issue.fields?.status.name} ); }, @@ -147,13 +153,9 @@ export const columnUpdated: TableColumn = { render: (issue: Partial) => { if (issue.fields?.updated) { return ( - theme.palette.text.secondary }} - color="divider" - variant="body2" - > + {DateTime.fromISO(issue.fields.updated).toFormat('dd/MMM/yy')} - + ); } return null; diff --git a/plugins/jira-dashboard/src/components/JiraUserIssuesCard/JiraUserIssuesCard.tsx b/plugins/jira-dashboard/src/components/JiraUserIssuesCard/JiraUserIssuesCard.tsx index 9ff93094..846b96b4 100644 --- a/plugins/jira-dashboard/src/components/JiraUserIssuesCard/JiraUserIssuesCard.tsx +++ b/plugins/jira-dashboard/src/components/JiraUserIssuesCard/JiraUserIssuesCard.tsx @@ -1,8 +1,6 @@ -import { - BottomLinkProps, - InfoCard, - TableOptions, -} from '@backstage/core-components'; +import { BottomLinkProps, TableOptions } from '@backstage/core-components'; +import { ButtonLink } from '@backstage/ui'; +import { EntityInfoCard } from '@backstage/plugin-catalog-react'; import { JiraUserIssuesTable, @@ -44,13 +42,27 @@ export const JiraUserIssuesCard = ({ filterName, }: JiraUserIssuesCardProps) => { return ( - + + {bottomLinkProps.title} + + ) + } + > - + ); }; diff --git a/yarn.lock b/yarn.lock index 933ab9a9..ba62dea9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1949,8 +1949,7 @@ __metadata: "@backstage/plugin-home-react": "npm:^0.1.40" "@backstage/test-utils": "npm:^1.7.20" "@backstage/theme": "npm:^0.7.3" - "@mui/material": "npm:^5.15.7" - "@mui/styles": "npm:^5.15.7" + "@backstage/ui": "npm:^0.17.0" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" @@ -7970,7 +7969,7 @@ __metadata: languageName: node linkType: hard -"@mui/private-theming@npm:^5.15.20, @mui/private-theming@npm:^5.15.7": +"@mui/private-theming@npm:^5.15.20": version: 5.15.20 resolution: "@mui/private-theming@npm:5.15.20" dependencies: @@ -8008,37 +8007,6 @@ __metadata: languageName: node linkType: hard -"@mui/styles@npm:^5.15.7": - version: 5.15.7 - resolution: "@mui/styles@npm:5.15.7" - dependencies: - "@babel/runtime": "npm:^7.23.9" - "@emotion/hash": "npm:^0.9.1" - "@mui/private-theming": "npm:^5.15.7" - "@mui/types": "npm:^7.2.13" - "@mui/utils": "npm:^5.15.7" - clsx: "npm:^2.1.0" - csstype: "npm:^3.1.2" - hoist-non-react-statics: "npm:^3.3.2" - jss: "npm:^10.10.0" - jss-plugin-camel-case: "npm:^10.10.0" - jss-plugin-default-unit: "npm:^10.10.0" - jss-plugin-global: "npm:^10.10.0" - jss-plugin-nested: "npm:^10.10.0" - jss-plugin-props-sort: "npm:^10.10.0" - jss-plugin-rule-value-function: "npm:^10.10.0" - jss-plugin-vendor-prefixer: "npm:^10.10.0" - prop-types: "npm:^15.8.1" - peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/6da21d2ca3ecb0496ca6bb3fcf0e58d23612b8bf71d919e18cbd6cb1a9fd6e2b19fcf183d40d2398f0014705b223520df13e8287ccc001b69b6c62a6bcc256ea - languageName: node - linkType: hard - "@mui/system@npm:^5.15.20": version: 5.15.20 resolution: "@mui/system@npm:5.15.20" @@ -8067,7 +8035,7 @@ __metadata: languageName: node linkType: hard -"@mui/types@npm:^7.2.13, @mui/types@npm:^7.2.14": +"@mui/types@npm:^7.2.14": version: 7.2.14 resolution: "@mui/types@npm:7.2.14" peerDependencies: @@ -8079,7 +8047,7 @@ __metadata: languageName: node linkType: hard -"@mui/utils@npm:^5.14.15, @mui/utils@npm:^5.15.14, @mui/utils@npm:^5.15.20, @mui/utils@npm:^5.15.7": +"@mui/utils@npm:^5.14.15, @mui/utils@npm:^5.15.14, @mui/utils@npm:^5.15.20": version: 5.15.20 resolution: "@mui/utils@npm:5.15.20" dependencies: @@ -23282,7 +23250,7 @@ __metadata: languageName: node linkType: hard -"jss-plugin-camel-case@npm:^10.10.0, jss-plugin-camel-case@npm:^10.5.1": +"jss-plugin-camel-case@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-camel-case@npm:10.10.0" dependencies: @@ -23293,7 +23261,7 @@ __metadata: languageName: node linkType: hard -"jss-plugin-default-unit@npm:^10.10.0, jss-plugin-default-unit@npm:^10.5.1": +"jss-plugin-default-unit@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-default-unit@npm:10.10.0" dependencies: @@ -23303,7 +23271,7 @@ __metadata: languageName: node linkType: hard -"jss-plugin-global@npm:^10.10.0, jss-plugin-global@npm:^10.5.1": +"jss-plugin-global@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-global@npm:10.10.0" dependencies: @@ -23313,7 +23281,7 @@ __metadata: languageName: node linkType: hard -"jss-plugin-nested@npm:^10.10.0, jss-plugin-nested@npm:^10.5.1": +"jss-plugin-nested@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-nested@npm:10.10.0" dependencies: @@ -23324,7 +23292,7 @@ __metadata: languageName: node linkType: hard -"jss-plugin-props-sort@npm:^10.10.0, jss-plugin-props-sort@npm:^10.5.1": +"jss-plugin-props-sort@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-props-sort@npm:10.10.0" dependencies: @@ -23334,7 +23302,7 @@ __metadata: languageName: node linkType: hard -"jss-plugin-rule-value-function@npm:^10.10.0, jss-plugin-rule-value-function@npm:^10.5.1": +"jss-plugin-rule-value-function@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-rule-value-function@npm:10.10.0" dependencies: @@ -23345,7 +23313,7 @@ __metadata: languageName: node linkType: hard -"jss-plugin-vendor-prefixer@npm:^10.10.0, jss-plugin-vendor-prefixer@npm:^10.5.1": +"jss-plugin-vendor-prefixer@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-vendor-prefixer@npm:10.10.0" dependencies: @@ -23356,7 +23324,7 @@ __metadata: languageName: node linkType: hard -"jss@npm:10.10.0, jss@npm:^10.10.0, jss@npm:^10.5.1, jss@npm:~10.10.0": +"jss@npm:10.10.0, jss@npm:^10.5.1, jss@npm:~10.10.0": version: 10.10.0 resolution: "jss@npm:10.10.0" dependencies: