diff --git a/web/src/assets/styles/app.scss b/web/src/assets/styles/app.scss index 3ade7e3ad8..dab621c696 100644 --- a/web/src/assets/styles/app.scss +++ b/web/src/assets/styles/app.scss @@ -1,45 +1,11 @@ -// Make proposal actions compact -.proposal-actions li + li { - margin-block-start: 0; -} - -.proposal-action--delete { - font-weight: bold; -} - -// Align the expandable-actions with the actions list -// See https://www.patternfly.org/components/list#css-variables -.expandable-actions { - // --pf-v5-c-list--PaddingLeft - --pf-v5-c-list--nested--MarginLeft - --pf-v5-c-list--m-icon-lg__item-icon-MinWidth - margin-inline-start: calc(var(--pf-v5-global--spacer--lg) - var(--pf-v5-global--spacer--sm) - var(--pf-v5-global--icon--FontSize--sm)); -} - -.expandable-actions > div { - margin-block-start: 0; -} - -// Using a "selected-product" CSS class because sadly we cannot use -// ".pf-v5-c-card:has(> input[type="radio"]:checked)" yet -// -// See: -// - https://drafts.csswg.org/selectors/#relational -// - https://caniuse.com/css-has -.pf-v5-c-card.selected-product { - --pf-v5-c-card--BoxShadow: var(--pf-v5-global--BoxShadow--md); - - .pf-v5-c-radio { - // https://drafts.csswg.org/css-ui/#widget-accent - // https://caniuse.com/mdn-css_properties_accent-color - accent-color: var(--color-primary-darkest); - } - - .pf-v5-c-radio__label { - color: var(--color-primary); - font-weight: bold; +// Better alignment for expandable section with a sibling list +ul.pf-v5-c-list + div.pf-v5-c-expandable-section { + > button { + margin-inline-start: calc(var(--pf-v5-global--spacer--lg) - var(--pf-v5-global--spacer--sm) - var(--pf-v5-global--icon--FontSize--sm)); } - .pf-v5-c-radio__description { - color: var(--color-primary-darkest); + > div { + margin-block-start: 0; } } @@ -56,97 +22,6 @@ button.remove-link:hover { color: var(--pf-v5-c-button--m-danger--BackgroundColor); } - -button.hidden-popover-button { - visibility: hidden; - display: inline; -} - -.wifi-network-menu button.pf-v5-c-dropdown__toggle { - padding-right: 0; -} - -.keep-words { - word-break: keep-all; -} - -button.kebab-toggler { - padding-right: 0; - - svg { - vertical-align: middle; - } -} - -.volumes-list { - .pf-v5-c-label { - margin-inline-end: 5px; - } -} - -.pattern-container { - display: grid; - grid-template-columns: 16px auto; - grid-template-rows: auto auto; - gap: 0.2em 1em; - grid-auto-flow: row; - grid-template-areas: - "checkbox label" - "empty summary"; - margin-bottom: 1em; - padding: 0.5em; - border-radius: 5px; -} - -.pattern-container:hover { - background-color: #eee; -} - -.pattern-label { - display: grid; - grid-template-columns: 32px auto; - grid-template-rows: auto; - gap: 0 1em; - grid-auto-flow: row; - grid-template-areas: "label-icon label-text"; - grid-area: label; -} - -.pattern-label-icon { - grid-area: label-icon; - align-self: center; -} - -.pattern-label-text { - grid-area: label-text; - font-size: 110%; - font-weight: bold; - justify-self: start; - align-self: center; -} - -.pattern-summary { - grid-area: summary; - color: #666; -} - -.pattern-checkbox { - grid-area: checkbox; - justify-self: center; - align-self: center; -} - -.pattern-group-name { - font-size: 120%; -} - -.locale-container { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 0 1em; - width: 100%; -} - .first-username-dropdown { position: absolute; width: 100%; diff --git a/web/src/assets/styles/blocks.scss b/web/src/assets/styles/blocks.scss index e0ec9c93b6..f580648b72 100644 --- a/web/src/assets/styles/blocks.scss +++ b/web/src/assets/styles/blocks.scss @@ -2,142 +2,6 @@ // In the future we might add different section layouts by using data-variant attribute // or similar strategy -// Custom selection list -.selection-list > * { - border: 1px solid #eee; - transition: - font-size 0.15s ease-in-out, - font-weight 0.25s ease-in-out, - margin-block 0.15s ease-in-out, - box-shadow 0.35s ease-in-out; - - margin-block-start: -2px; -} - -.selection-list .header { - border-block-end: 1px solid #eee; - padding: var(--spacer-normal); -} - -.selection-list .content { - padding: var(--spacer-normal); -} - -.selection-list [data-state="focused"] { - margin-block: 20px; - box-shadow: 0 0 6px rgb(0 0 0 / 16%), 0 6px 12px rgb(0 0 0 / 32%); -} - -.selection-list [data-state="unstyled"] { - border: 0; -} - -[data-type="agama/sidebar"] { - /** Override the header background, see styles/layout.scss */ - --agama-header-bg: var(--color-primary-lighter); - - position: absolute; - padding: 0; - right: 0; - z-index: 1000; - inline-size: 70%; - min-inline-size: min-content; - box-shadow: -10px 10px 20px 0 var(--color-primary); - - header { - --focus-color: var(--color-primary-darkest); - } - - footer { - border-top: 1px solid var(--color-gray); - } - - a, button { - font-size: 16px; - font-weight: var(--fw-bold); - text-decoration: underline; - text-underline-offset: 2px; - padding-block: 0; - - &:hover { - color: var(--color-link-hover); - text-decoration: underline; - - svg { - color: var(--color-link); - } - } - - svg { - color: var(--color-link); - vertical-align: text-bottom; - margin-block-end: -2px; - } - } - - a { - margin-inline-start: var(--pf-v5-global--spacer--md); - - // Keep links and buttons labels aligned by adding the same margin than - // .pf-v5-c-button__icon.pf-m-start - svg { - margin-inline-end: var(--pf-v5-global--spacer--xs); - } - } - - // Remove not wanted PatternFly padding left on a loading link - button.pf-m-progress { - --pf-v5-c-button--m-progress--PaddingLeft: var(--pf-v5-global--spacer--md); - } - - button.pf-m-progress + div { - padding-inline-start: calc(var(--pf-v5-global--spacer--md)); - } - - &[data-state="hidden"] { - transition: all 0.04s ease-in-out; - inline-size: 0; - min-inline-size: 0; - box-shadow: none; - } - - &[data-state="visible"] { - transition: all 0.2s ease-in-out; - } -} - - -.disclosure > button { - margin-inline-start: var(--pf-v5-global--spacer--md); - display: inline-flex; - align-items: center; - // Keep links and buttons labels aligned by adding the same margin than - // .pf-v5-c-button__icon.pf-m-start - svg { - margin-inline-end: var(--pf-v5-global--spacer--xs); - transition: transform 0.2s ease-in-out; - } - - &[aria-expanded="true"] { - svg { - transform: rotate(90deg); - } - } - - &[aria-expanded="false"] + div { - display: none; - visibility: hidden; - } -} - -.disclosure > div { - margin-inline-start: calc( - var(--pf-v5-global--spacer--md) + 12px // half of the icon size; - ); - border-inline-start: 1px solid var(--color-primary-lighter); - padding-block: var(--spacer-small); -} - // raw file content with formatting similar to
.filecontent {
font-family: var(--ff-code);
@@ -359,72 +223,6 @@ ul[data-items-type="agama/patterns"] {
}
}
-[data-type="agama/tag"] {
- font-size: var(--fs-small);
-
- &[data-variant="teal"] {
- color: var(--color-teal);
- }
-
- &[data-variant="orange"] {
- color: var(--color-orange);
- }
-
- &[data-variant="gray-highlight"] {
- padding: var(--spacer-smaller);
- color: var(--color-gray-darkest);
- background: var(--color-gray);
- border: 1px solid var(--color-gray-dark);
- border-radius: 5px;
- margin-inline-start: var(--spacer-smaller);
- }
-}
-
-[data-type="agama/controlled-panels"] {
- [data-type="agama/option"] {
- label, input {
- cursor: pointer;
- }
-
- label {
- display: flex;
- gap: var(--spacer-smaller);
- }
- }
-
- [data-variant="buttons"] {
- input { position: absolute; opacity: 0 }
-
- label {
- border: 1px solid var(--color-primary);
- padding: var(--spacer-small);
- gap: var(--spacer-small);
- border-radius: var(--spacer-smaller);
- position: relative;
-
- &:has(input:checked) {
- background: var(--color-primary);
- color: white;
- }
-
- &:has(input:focus-visible) {
- // outline: 1px dotted;
- // outline-offset: 0.25rem;
- box-shadow: 0 0 0 3px var(--focus-color);
- }
- }
-
- [data-type="agama/option"]:not(:last-child) {
- border-inline-end: 2px solid var(--color-gray-darker);
- padding-inline-end: var(--spacer-small);
- }
- }
-
- > div[aria-expanded="false"] {
- display: none;
- }
-}
-
table[data-type="agama/tree-table"] {
th:first-child {
padding-inline-end: var(--spacer-normal);
@@ -556,70 +354,6 @@ table.proposal-result {
}
}
-[data-type="agama/options-picker"] {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: var(--spacer-smaller);
-
- [role="option"] {
- cursor: pointer;
- border: 1px solid var(--color-gray);
- padding: var(--spacer-small);
- border-block-end-width: 4px;
-
- &[aria-selected="true"] {
- background: var(--color-gray-light);
- border-color: var(--color-primary);
- }
-
- >:first-child {
- margin-block-end: var(--spacer-small);
- }
-
- >:last-child {
- font-size: var(--fs-small);
- }
- }
-}
-
-[data-type="agama/reminder"] {
- --accent-color: var(--color-primary-lighter);
- --inline-margin: calc(var(--header-icon-size) + var(--spacer-small));
-
- display: flex;
- gap: var(--spacer-small);
- margin-inline: var(--inline-margin);
- margin-block-end: var(--spacer-normal);
- padding: var(--spacer-smaller) var(--spacer-small);
- border-inline-start: 3px solid var(--accent-color);
-
- svg {
- fill: var(--accent-color);
- }
-
- h4 {
- color: var(--accent-color);
- }
-
- h4 ~ * {
- margin-block-start: var(--spacer-small);
- }
-}
-
-section [data-type="agama/reminder"] {
- margin-inline: 0;
-}
-
-[data-type="agama/reminder"][data-variant="subtle"] {
- --accent-color: var(--color-primary);
- padding-block: 0;
- border-inline-start-width: 1px;
-
- h4 {
- font-size: var(--fs-normal);
- }
-}
-
[role="dialog"] {
section:not([class^="pf-c"]) {
> svg:first-child {
@@ -633,20 +367,6 @@ section [data-type="agama/reminder"] {
}
}
-.tpm-hint {
- container-type: inline-size;
- container-name: tpm-info;
- text-align: start;
-
- .pf-v5-c-alert__title {
- margin-block-end: var(--spacer-small);
- }
-
- .pf-v5-c-alert__description {
- max-inline-size: 100%;
- }
-}
-
[data-type="agama/expandable-selector"] {
// The expandable selector is built on top of PF/Table#expandable
// Let's tweak some styles
@@ -669,87 +389,3 @@ section [data-type="agama/reminder"] {
}
}
}
-
-[data-type="agama/field"] {
- > div:first-child {
- font-size: var(--fs-large);
-
- button {
- padding-inline: 0;
- }
-
- button:hover {
- color: var(--color-link-hover);
- fill: var(--color-link-hover);
- }
-
- button b, button:hover b {
- text-decoration: underline;
- text-underline-offset: var(--spacer-smaller);
- }
-
- div.pf-v5-c-skeleton {
- display: inline-block;
- vertical-align: middle;
- height: 1.5ex;
- }
- }
-
- > div:nth-child(n+2) {
- margin-inline-start: calc(var(--icon-size-s) + 1ch);
- }
-
- > div:nth-child(2) {
- color: gray;
- font-size: var(--fs-medium);
- }
-
- > div:nth-child(n+3) {
- margin-block-start: var(--spacer-small);
- }
-
- &.highlighted > div:last-child {
- --spacing: calc(var(--icon-size-s) / 2);
- margin-inline: var(--spacing);
- padding-inline: var(--spacing);
- border-inline-start: 2px solid;
- }
-
- &.highlighted.on > div:last-child {
- border-color: var(--color-link-hover);
- }
-
- &.highlighted.off > div:last-child {
- border-color: var(--color-gray-darker);
- }
-
- &.on {
- button:not(.password-toggler) {
- fill: var(--color-link-hover);
- }
- }
-
- hr {
- margin-block: var(--spacer-normal);
- border: 0;
- border-bottom: thin dashed var(--color-gray);
- }
-}
-
-[data-type="agama/field"] button.pf-v5-c-menu-toggle.pf-m-plain {
- padding: 0;
-}
-
-[data-type="agama/field"] .pf-v5-c-menu__list {
- padding: calc(var(--spacer-smaller) / 2) 0;
- margin: 0;
-}
-
-#boot-form {
- legend {
- label {
- font-size: var(--fs-large);
- font-weight: bold;
- }
- }
-}
diff --git a/web/src/assets/styles/composition.scss b/web/src/assets/styles/composition.scss
index 09be3715ef..eebf2a6c1d 100644
--- a/web/src/assets/styles/composition.scss
+++ b/web/src/assets/styles/composition.scss
@@ -32,13 +32,7 @@
}
}
-[data-state="reversed"] {
- flex-direction: row-reverse;
-}
-
body > div[inert],
-body > div[aria-hidden="true"],
-div[data-type="agama/page"] > [inert],
-div[data-type="agama/page"] > [aria-hidden="true"] {
+body > div[aria-hidden="true"] {
filter: grayscale(1) blur(2px);
}
diff --git a/web/src/assets/styles/index.scss b/web/src/assets/styles/index.scss
index 9c4e142efa..e97db6b414 100644
--- a/web/src/assets/styles/index.scss
+++ b/web/src/assets/styles/index.scss
@@ -6,7 +6,6 @@
// TODO: merge app and global
@use "~/assets/styles/global.scss";
@use "~/assets/styles/app.scss";
-@use "~/assets/styles/layout.scss";
@use "~/assets/styles/utilities.scss";
@use "~/assets/styles/composition.scss";
@use "~/assets/styles/blocks.scss";
diff --git a/web/src/assets/styles/layout.scss b/web/src/assets/styles/layout.scss
deleted file mode 100644
index d8a7034ad5..0000000000
--- a/web/src/assets/styles/layout.scss
+++ /dev/null
@@ -1,84 +0,0 @@
-@use "~/assets/styles/utilities.scss";
-
-[data-layout="agama/base"] {
- --agama-header-bg: var(--color-primary);
-
- @extend .shadow;
- display: grid;
- block-size: 100dvh;
- background: white;
- overflow: hidden;
- grid-template-columns: 1fr;
- grid-template-rows: 60px 1fr 70px;
- grid-template-areas:
- "header"
- "body"
- "footer"
- ;
-
- > * {
- padding: var(--spacer-small);
- }
-
- > header {
- @extend .bottom-shadow;
- grid-area: header;
- display: flex;
- align-items: center;
- justify-content: space-between;
- background: var(--agama-header-bg);
- color: white;
- fill: white;
-
- h1 {
- display: grid;
- align-items: center;
- gap: var(--spacer-small);
- grid-template-columns: var(--header-icon-size) 1fr;
- grid-template-areas:
- "icon text"
- ;
-
- svg {
- grid-area: icon;
- block-size: var(--header-icon-size);
- inline-size: var(--header-icon-size);
- }
-
- span {
- grid-area: text;
- }
- }
- }
-
- main {
- grid-area: body;
- overflow: auto;
- padding-block: var(--spacer-normal);
- container-type: inline-size;
- container-name: agama-page-content;
- }
-
- footer {
- grid-area: footer;
- @extend .top-shadow;
- display: flex;
- flex-direction: row-reverse;
- justify-content: space-between;
- align-items: center;
-
- img {
- inline-size: 30%;
- max-inline-size: 150px;
- }
- }
-}
-
-[data-type="agama/header-actions"] {
- display: flex;
- gap: var(--spacer-small);
-}
-
-[data-variant="flip-X"] {
- transform: scaleX(-1);
-}
diff --git a/web/src/assets/styles/patternfly-overrides.scss b/web/src/assets/styles/patternfly-overrides.scss
index da930f63e1..4cf2b70cd6 100644
--- a/web/src/assets/styles/patternfly-overrides.scss
+++ b/web/src/assets/styles/patternfly-overrides.scss
@@ -67,25 +67,6 @@
--pf-v5-c-button--m-secondary--Color: var(--color-link-hover);
}
-// Force a separation between PF/EmptyStateBody paragraph without needing
-// either: add the .pf-v5-c-content class nor wrapping PF/Text into
-// PF/TextContent
-.pf-v5-c-empty-state__body p:not(:last-child) {
- margin-block-end: var(--pf-v5-global--spacer--md);
-}
-
-// Do not add block padding to empty state inside a table/column
-table td > .pf-v5-c-empty-state {
- --pf-v5-c-empty-state--PaddingTop: 0;
- --pf-v5-c-empty-state--PaddingBottom: 0;
-}
-
-// Fix single-line sub-progress miss-alignment
-.pf-v5-c-progress.pf-m-singleline .pf-v5-c-progress__bar {
- grid-row: 1/3;
- grid-column: 1/3;
-}
-
.pf-v5-c-modal-box__body {
padding-block: var(--pf-v5-c-modal-box__body--PaddingTop);
}
diff --git a/web/src/assets/styles/utilities.scss b/web/src/assets/styles/utilities.scss
index 9e8030b5e9..980c327205 100644
--- a/web/src/assets/styles/utilities.scss
+++ b/web/src/assets/styles/utilities.scss
@@ -1,7 +1,3 @@
-.justify-between {
- justify-content: space-between;
-}
-
// Sadly, Firefox does not support :has pseudo-selector yet.
// See @components/layout/Center documentation.
//
@@ -19,25 +15,6 @@
inline-size: 100%;
}
-.horizontally-centered {
- inline-size: 100%;
- margin-inline: 0 auto;
- text-align: center;
-}
-
-.title {
- font-size: var(--fs-large);
- font-weight: var(--fw-bold);
-}
-
-.bold {
- font-weight: bold;
-}
-
-.fs-small {
- font-size: var(--fs-small);
-}
-
// Utility classes for sizing icons
.icon-xxxs {
block-size: var(--icon-size-xxxs);
@@ -74,10 +51,6 @@
inline-size: var(--icon-size-xxxl);
}
-.color-warn {
- color: var(--color-warn);
-}
-
.color-success {
color: var(--color-success);
fill: var(--color-success);
@@ -87,85 +60,10 @@
inline-size: 100%;
}
-.full-size {
- width: 100%;
- height: 100%;
-}
-
-.transform-on-hover {
- transition: all 0.2s ease-in-out;
-
- &:hover {
- transform: scale(1.4);
- color: var(--color-primary-darkest);
- }
-}
-
-.gradient-border-bottom {
- border-bottom: 1px solid #efefef;
- border-image: linear-gradient(
- var(--gradient-border-angle),
- var(--gradient-border-start-color),
- var(--gradient-border-end-color)
- ) 1;
-}
-
-.visually-hidden {
- border: 0;
- clip: rect(0 0 0 0);
- height: auto;
- margin: 0;
- overflow: hidden;
- padding: 0;
- position: absolute;
- width: 1px;
- white-space: nowrap;
-}
-
-.shadow {
- box-shadow: 0 0 10px 0 var(--color-gray-darker);
-}
-
-.top-shadow {
- box-shadow: 0 5px 10px 0 var(--color-gray-darker);
-}
-
-.bottom-shadow {
- box-shadow: 0 -3px 10px 0 var(--color-gray-darker);
-}
-
-.plain-control {
- position: relative;
- background: none;
- border: none;
-}
-
-.plain-button {
- border: none;
- background: none;
- color: inherit;
- font: inherit;
- padding: 0;
- text-align: start;
-}
-
-.inline-flex-button{
- @extend .plain-button;
- display: inline-flex;
- align-items: center;
- gap: 0.7ch;
- text-decoration: underline;
-}
-
-.p-0 {
- padding: 0;
-}
-
.block-size-auto {
block-size: auto;
}
-
.inline-size-auto {
inline-size: auto;
}
@@ -218,17 +116,6 @@
}
}
-.large {
- /** block-size fallbacks **/
- height: 95dvh;
- width: 95dvw;
- max-width: calc(var(--ui-max-inline-size) + var(--spacer-large));
- /** END block-size fallbacks **/
- block-size: 95dvh;
- inline-size: 95dvw;
- max-inline-size: calc(var(--ui-max-inline-size) + var(--spacer-large))
-}
-
.scrollbox {
background:
linear-gradient(#fff 33%, rgb(255 255 255 / 0%)),
@@ -241,10 +128,6 @@
background-size: 100% 48px, 100% 48px, 100% 16px, 100% 16px;
}
-.height-75 {
- height: 75dvh;
-}
-
// FIXME: drop as soon as Tip component gets rethought / refactored
.label-tip .pf-v5-c-label__text {
display: flex;
diff --git a/web/src/assets/styles/variables.scss b/web/src/assets/styles/variables.scss
index 7c79ff96c2..7e35e4e433 100644
--- a/web/src/assets/styles/variables.scss
+++ b/web/src/assets/styles/variables.scss
@@ -40,49 +40,19 @@
--icon-size-xxl: 5rem;
--icon-size-xxxl: 10rem;
- --wrapper-padding: var(--spacer-small);
- --wrapper-background: white;
-
--color-primary: #0c322c;
- --color-primary-lighter: #30ba78;
--color-gray-light: #fcfcfc;
--color-gray: #f2f2f2;
--color-gray-dark: #efefef; // Fog
- --color-gray-darker: #999;
- --color-gray-darkest: #333;
--color-gray-dimmed: #888;
- --color-gray-dimmest: #666;
- --color-teal: #279c9c;
- --color-blue: #0d4ea6;
- --color-orange: #e86427;
+ --color-success: #30ba78;
--color-link: #0c322c;
--color-link-hover: #30ba78;
-
--color-button-primary: var(--color-link);
--color-button-primary-hover: var(--color-link-hover);
-
--color-button-plain-link: var(--color-link);
--color-button-plain-link-hover: var(--color-link-hover);
- --color-background-primary: var(--color-primary);
- --color-background-secondary: var(--color-gray-dark);
-
- --color-text-primary: var(--color-primary);
- --color-text-secondary: var(--color-gray-dark);
-
- --color-success: #30ba78;
- --color-warn: #d4351c; // #FE7C3F; // Persimmon
-
--focus-color: #00b2e2; //cerulean 500
-
- --gradient-border-angle: 45deg;
- --gradient-border-start-color: var(--color-gray);
- --gradient-border-end-color: transparent;
-
- --header-icon-size: var(--icon-size-m);
- --section-icon-size: var(--icon-size);
-
- --header-block-size: auto;
- --footer-block-size: auto;
}
diff --git a/web/src/components/core/EmptyState.jsx b/web/src/components/core/EmptyState.jsx
index 46f7cd9770..32c256aa46 100644
--- a/web/src/components/core/EmptyState.jsx
+++ b/web/src/components/core/EmptyState.jsx
@@ -22,7 +22,7 @@
// @ts-check
import React from "react";
-import { EmptyState, EmptyStateHeader, EmptyStateBody } from "@patternfly/react-core";
+import { EmptyState, EmptyStateHeader, EmptyStateBody, Flex } from "@patternfly/react-core";
import { Icon } from "~/components/layout";
/**
@@ -42,10 +42,10 @@ import { Icon } from "~/components/layout";
* @param {string} props.title
* @param {IconName} props.icon
* @param {string} props.color
- * @param {Pick} [props.headingLevel="h4"]
+ * @param {EmptyStateHeaderProps["headingLevel"]} [props.headingLevel="h4"]
* @param {boolean} [props.noPadding=false]
* @param {React.ReactNode} props.children
- * @param {EmptyStateProps} [props.props]
+ * @param {EmptyStateProps} [props.rest]
* @todo write documentation
*/
export default function EmptyStateWrapper({
@@ -55,12 +55,12 @@ export default function EmptyStateWrapper({
headingLevel = "h4",
noPadding = false,
children,
- ...props
+ ...rest
}) {
- if (noPadding) props.className = [props.className, 'no-padding'].join(" ").trim();
+ if (noPadding) rest.className = [rest.className, 'no-padding'].join(" ").trim();
return (
-
+
}
/>
- {children}
+
+ {children}
+
);
diff --git a/web/src/components/core/FileViewer.jsx b/web/src/components/core/FileViewer.jsx
deleted file mode 100644
index 8fa58990aa..0000000000
--- a/web/src/components/core/FileViewer.jsx
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (c) [2023] SUSE LLC
- *
- * All Rights Reserved.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of version 2 of the GNU General Public License as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, contact SUSE LLC.
- *
- * To contact SUSE LLC about this file by physical or electronic mail, you may
- * find current contact information at www.suse.com.
- */
-
-import React, { useState, useEffect } from "react";
-import { Popup } from "~/components/core";
-import { Alert } from "@patternfly/react-core";
-import { Loading } from "~/components/layout";
-import { _ } from "~/i18n";
-
-import cockpit from "../../lib/cockpit";
-
-export default function FileViewer({ file, title, onCloseCallback }) {
- // the popup is visible
- const [isOpen, setIsOpen] = useState(true);
- // error message for failed load
- const [error, setError] = useState(null);
- // the file content
- const [content, setContent] = useState("");
- // current state
- const [state, setState] = useState("loading");
-
- useEffect(() => {
- // NOTE: reading non-existing files in cockpit does not fail, the result is null
- // see https://cockpit-project.org/guide/latest/cockpit-file
- cockpit.file(file).read()
- .then((data) => {
- setState("ready");
- setContent(data);
- })
- .catch((data) => {
- setState("ready");
- setError(data.message);
- });
- }, [file]);
-
- const close = () => {
- setIsOpen(false);
- if (onCloseCallback) onCloseCallback();
- };
-
- return (
-
- {state === "loading" && }
- {(content === null || error) &&
-
- {error}
- }
-
- {content}
-
-
-
- {_("Close")}
-
-
- );
-}
diff --git a/web/src/components/core/FileViewer.test.jsx b/web/src/components/core/FileViewer.test.jsx
deleted file mode 100644
index 6c9b3972d2..0000000000
--- a/web/src/components/core/FileViewer.test.jsx
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (c) [2023] SUSE LLC
- *
- * All Rights Reserved.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of version 2 of the GNU General Public License as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, contact SUSE LLC.
- *
- * To contact SUSE LLC about this file by physical or electronic mail, you may
- * find current contact information at www.suse.com.
- */
-
-import React from "react";
-
-import { screen, waitFor, within } from "@testing-library/react";
-import { mockGettext, plainRender } from "~/test-utils";
-import { FileViewer } from "~/components/core";
-import cockpit from "../../lib/cockpit";
-
-jest.mock("../../lib/cockpit");
-
-const readFn = jest.fn(() => new Promise(jest.fn()));
-
-const fileFn = jest.fn();
-fileFn.mockImplementation(() => {
- return {
- read: readFn
- };
-});
-
-cockpit.file.mockImplementation(fileFn);
-
-// testing data
-const file_name = "/testfile";
-const content = "Read file content";
-const title = "YaST Logs";
-
-mockGettext();
-
-describe("FileViewer", () => {
- beforeEach(() => {
- readFn.mockResolvedValue(content);
- });
-
- it("displays the specified file and the title", async () => {
- plainRender( );
- const dialog = await screen.findByRole("dialog");
-
- // the file was read from cockpit
- expect(fileFn).toHaveBeenCalledWith(file_name);
- expect(readFn).toHaveBeenCalled();
-
- within(dialog).getByText(title);
- within(dialog).getByText(content);
- });
-
- it("displays the file name when the title is missing", async () => {
- plainRender( );
- const dialog = await screen.findByRole("dialog");
-
- within(dialog).getByText(file_name);
- });
-
- it("closes the popup after clicking the close button", async () => {
- const { user } = plainRender( );
- const dialog = await screen.findByRole("dialog");
- const closeButton = within(dialog).getByRole("button", { name: /Close/i });
-
- await user.click(closeButton);
- await waitFor(() => {
- expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
- });
- });
-
- it("triggers the onCloseCallback after clicking the close button", async () => {
- const callback = jest.fn();
- const { user } = plainRender( );
- const dialog = await screen.findByRole("dialog");
- const closeButton = within(dialog).getByRole("button", { name: /Close/i });
-
- await user.click(closeButton);
-
- expect(callback).toHaveBeenCalled();
- });
-
- describe("when the file does not exist", () => {
- beforeEach(() => {
- readFn.mockResolvedValue(null);
- });
-
- it("displays an error", async () => {
- plainRender( );
- const dialog = await screen.findByRole("dialog");
-
- await within(dialog).findByText(/cannot read the file/i);
- });
- });
-
- describe("when the file cannot be read", () => {
- beforeEach(() => {
- readFn.mockRejectedValue(new Error("read error"));
- });
-
- it("displays the error message", async () => {
- plainRender( );
- const dialog = await screen.findByRole("dialog");
-
- await within(dialog).findByText(/read error/i);
- });
- });
-});
diff --git a/web/src/components/core/InstallationFinished.jsx b/web/src/components/core/InstallationFinished.jsx
index c60b2943b3..a56ece90d5 100644
--- a/web/src/components/core/InstallationFinished.jsx
+++ b/web/src/components/core/InstallationFinished.jsx
@@ -99,13 +99,15 @@ function InstallationFinished() {
icon={ }
/>
- {_("The installation on your machine is complete.")}
-
- {usingIguana
- ? _("At this point you can power off the machine.")
- : _("At this point you can reboot the machine to log in to the new system.")}
-
- {usingTpm && }
+
+ {_("The installation on your machine is complete.")}
+
+ {usingIguana
+ ? _("At this point you can power off the machine.")
+ : _("At this point you can reboot the machine to log in to the new system.")}
+
+ {usingTpm && }
+
diff --git a/web/src/components/core/Page.jsx b/web/src/components/core/Page.jsx
index 6e2ed90862..a6c70af5a0 100644
--- a/web/src/components/core/Page.jsx
+++ b/web/src/components/core/Page.jsx
@@ -30,7 +30,6 @@ import {
PageGroup, PageSection,
Stack
} from "@patternfly/react-core";
-import { PageMenu } from "~/components/core";
import { _ } from "~/i18n";
import tabsStyles from '@patternfly/react-styles/css/components/Tabs/tabs';
import flexStyles from '@patternfly/react-styles/css/utilities/Flex/flex';
@@ -52,16 +51,6 @@ import flexStyles from '@patternfly/react-styles/css/utilities/Flex/flex';
*/
const Actions = ({ children }) => <>{children}>;
-/**
- * Component for rendering options related to the page. I.e., a menu.
- *
- * @note it is defined in its own file and then included here under Page.Menu
- * "alias".
- *
- * @see core/PageMenu to know more.
- */
-const Menu = PageMenu;
-
/**
* A convenient component representing a Page action
*
@@ -180,47 +169,6 @@ const CardSection = ({ title, children, ...props }) => {
*
*
*
- * @example Using custom actions
- *
- *
- *
- *
- * alert("Are you sure?")}>
- * Reset to defaults
- *
- * Accept
- *
- *
- *
- * @example Using custom actions and a page menu
- *
- *
- *
- *
- *
- * Expert mode
- * Help
- *
- *
- *
- *
- * alert("Are you sure?")}>
- * Reset to defaults
- *
- * Accept
- *
- *
- *
- * @example Using a page menu from external component
- * ...
- * import { UserPageMenu } from "somewhere";
- * ...
- *
- *
- *
- *
- *
- *
* @param {object} props
* @param {string} [props.icon] - The icon for the page.
* @param {string} [props.title="Agama"] - The title for the page. By default it
@@ -245,7 +193,6 @@ Page.CardSection = CardSection;
Page.Actions = Actions;
Page.NextActions = NextActions;
Page.Action = Action;
-Page.Menu = Menu;
Page.MainContent = MainContent;
Page.CancelAction = CancelAction;
Page.Header = Header;
diff --git a/web/src/components/core/PageMenu.jsx b/web/src/components/core/PageMenu.jsx
deleted file mode 100644
index 608c9a0384..0000000000
--- a/web/src/components/core/PageMenu.jsx
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (c) [2023-2024] SUSE LLC
- *
- * All Rights Reserved.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of version 2 of the GNU General Public License as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, contact SUSE LLC.
- *
- * To contact SUSE LLC about this file by physical or electronic mail, you may
- * find current contact information at www.suse.com.
- */
-
-// @ts-check
-
-import React, { useState } from 'react';
-import {
- Dropdown, DropdownGroup, DropdownItem, DropdownList,
- MenuToggle
-} from '@patternfly/react-core';
-import { _ } from "~/i18n";
-import { Icon } from "~/components/layout";
-
-/**
- * Internal component to build the {PageMenu} toggler
- * @component
- *
- * @typedef {object} TogglerBaseProps
- * @property {React.Ref} toggleRef
- * @property {string} label
- *
- * @typedef {TogglerBaseProps & import('@patternfly/react-core').MenuToggleProps} TogglerProps
- *
- * @param {TogglerProps} props
- */
-const Toggler = ({ toggleRef, label, onClick, "aria-label": ariaLabel = _(("Show page menu")) }) => {
- return (
-
- {label}
-
-
- );
-};
-
-/**
- * A group of actions belonging to a {PageMenu} component
- * @component
- *
- * Built on top of {@link https://www.patternfly.org/components/menus/dropdown#dropdowngroup PF/DropdownGroup}
- *
- * @see {PageMenu} examples.
- *
- * @param {import('@patternfly/react-core').DropdownGroupProps} props
- */
-const Group = ({ children, ...props }) => {
- return (
-
- {children}
-
- );
-};
-
-/**
- * An option belonging to a {PageMenu} component
- * @component
- *
- * Built on top of {@link https://www.patternfly.org/components/menus/dropdown#dropdownitem PF/DropdownItem}
- *
- * @see {PageMenu} examples.
- *
- * @param {import('@patternfly/react-core').DropdownItemProps} props
- */
-const Option = ({ children, ...props }) => {
- return (
-
- {children}
-
- );
-};
-
-/**
- * A collection of {Option}s belonging to a {PageMenu} component
- * @component
- *
- * Built on top of {@link https://www.patternfly.org/components/menus/dropdown#dropdownlist PF/DropdownList}
- *
- * @see {PageMenu} examples.
- *
- * @param {import('@patternfly/react-core').DropdownListProps} props
- */
-const Options = ({ children, ...props }) => {
- return (
-
- {children}
-
- );
-};
-
-/**
- * Component for rendering actions related to a page.
- * @component
- *
- * It consist in a {@link https://www.patternfly.org/components/menus/dropdown PF/Dropdown}
- * rendered in the header close to the action for opening the Sidebar.
- *
- * @note when wrapping it in another component intended to hold all the needed
- * logic for building the page menu, it's name must includes the "PageMenu" suffix.
- * This is needed to allow core/Page properly work with it. See core/Page component
- * for a better understanding.
- *
- * @see core/Page component.
- *
- * @example Usage example
- *
- *
- *
- *
- * Reprobe
- *
- *
- *
- *
- *
- * DASD
- *
- *
- * iSCSI
- *
- *
- *
- *
- *
- * @typedef {object} PageMenuProps
- * @property {string} [togglerAriaLabel]
- * @property {string} label
- * @property {React.ReactNode} children
- *
- * @param {PageMenuProps} props
- */
-const PageMenu = ({ togglerAriaLabel, label, children }) => {
- const [isOpen, setIsOpen] = useState(false);
-
- const toggle = () => setIsOpen(!isOpen);
- const close = () => setIsOpen(false);
-
- return (
- }
- onSelect={close}
- onOpenChange={close}
- popperProps={{ minWidth: "150px", position: "right" }}
- data-type="agama/page-menu"
- >
-
- {children}
-
-
- );
-};
-
-PageMenu.Group = Group;
-PageMenu.Options = Options;
-PageMenu.Option = Option;
-
-export default PageMenu;
diff --git a/web/src/components/core/PageMenu.test.jsx b/web/src/components/core/PageMenu.test.jsx
deleted file mode 100644
index a20bbf27e5..0000000000
--- a/web/src/components/core/PageMenu.test.jsx
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (c) [2022-2023] SUSE LLC
- *
- * All Rights Reserved.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of version 2 of the GNU General Public License as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, contact SUSE LLC.
- *
- * To contact SUSE LLC about this file by physical or electronic mail, you may
- * find current contact information at www.suse.com.
- */
-
-import React from "react";
-import { screen, waitForElementToBeRemoved } from "@testing-library/react";
-import { plainRender } from "~/test-utils";
-import { PageMenu } from "~/components/core";
-
-it("renders the component initially closed", async () => {
- plainRender(
-
- A dummy action
-
- );
-
- screen.getByRole("button", { name: "Testing menu" });
- expect(screen.queryByRole("menuitem", { name: "A dummy action" })).toBeNull();
-});
-
-it("shows and hides the component content on user request", async () => {
- const { user } = plainRender(
-
- <>A dummy action>
-
- );
-
- const toggler = screen.getByRole("button", { name: "Menu toggler" });
-
- expect(screen.queryByRole("menuitem", { name: "A dummy action" })).toBeNull();
-
- await user.click(toggler);
-
- screen.getByRole("menuitem", { name: "A dummy action" });
-
- await user.click(toggler);
- await waitForElementToBeRemoved(screen.queryByRole("menuitem", { name: "A dummy action" }));
- // NOTE: the above is the same than:
- // await waitFor(() => {
- // expect(screen.queryByRole("menuitem", { name: "A dummy action" })).toBeNull();
- // });
-});
-
-it("hides the component content when user clicks on one of its actions", async () => {
- const { user } = plainRender(
-
-
- <>Section>
- <>Page>
-
- <>Exit>
-
- );
-
- const toggler = screen.getByRole("button");
- await user.click(toggler);
- const action = screen.getByRole("menuitem", { name: "Section" });
- await user.click(action);
-
- expect(screen.queryByRole("menuitem", { name: "A dummy action" })).toBeNull();
-});
-
-it('closes the component when user clicks outside', async () => {
- const { user } = plainRender(
- <>
- Sibling
-
- <>Option 1>
- <>Option 2>
-
- >
- );
-
- const toggler = screen.getByRole("button");
- const sibling = screen.getByText("Sibling");
-
- // Open the dropdown
- await user.click(toggler);
-
- // Ensure the dropdown is open
- screen.getByRole("menuitem", { name: "Option 2" });
-
- // Click outside the dropdown
- await user.click(sibling);
-
- // Ensure the dropdown is closed
- await waitForElementToBeRemoved(() => screen.queryByRole("menuitem", { name: "Option 2" }));
-});
diff --git a/web/src/components/core/Popup.jsx b/web/src/components/core/Popup.jsx
index e16fc38413..5779d2fb80 100644
--- a/web/src/components/core/Popup.jsx
+++ b/web/src/components/core/Popup.jsx
@@ -23,15 +23,9 @@
import React from "react";
import { Button, Modal } from "@patternfly/react-core";
+import { Loading } from "~/components/layout";
import { _ } from "~/i18n";
import { partition } from "~/utils";
-import { Loading } from "~/components/layout";
-
-/**
- * @typedef {import("@patternfly/react-core").ModalProps} ModalProps
- * @typedef {import("@patternfly/react-core").ButtonProps} ButtonProps
- * @typedef {Omit} ButtonWithoutVariantProps
- */
/**
* @typedef {import("@patternfly/react-core").ModalProps} ModalProps
diff --git a/web/src/components/core/Selector.jsx b/web/src/components/core/Selector.jsx
deleted file mode 100644
index 5e5763e6e1..0000000000
--- a/web/src/components/core/Selector.jsx
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (c) [2024] SUSE LLC
- *
- * All Rights Reserved.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of version 2 of the GNU General Public License as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, contact SUSE LLC.
- *
- * To contact SUSE LLC about this file by physical or electronic mail, you may
- * find current contact information at www.suse.com.
- */
-
-// @ts-check
-import React from "react";
-import { _ } from "~/i18n";
-import { noop } from "~/utils";
-
-/**
- * @callback onSelectionChangeCallback
- * @param {Array} selection - ids of selected options
- */
-
-/**
- * Agama component for building a selector
- *
- * @example Usage example
- * const options = [
- * { id: "es_ES", country: "Spain", label: "Spanish" },
- * { id: "cs_CZ", country: "Czechia", label: "Czech" },
- * { id: "de_DE", country: "Germany", label: "German" },
- * { id: "en_GB", country: "United Kingdom", label: "English" }
- * ];
- *
- * const selectedIds = ["es_ES", "en_GB"];
- *
- * const renderFn = ({ label, country }) => {label} - {country};
- *
- * return (
- * changePreferredLocales(selection)}
- * />
- * );
- *
- * @param {object} props - component props
- * @param {string} [props.id] - Id attribute for selector.
- * @param {boolean} [props.isMultiple=false] - Whether the selector should allow multiple selection.
- * @param {Array