diff --git a/.cursor/rules/patternfly-ux-capitalization.mdc b/.cursor/rules/patternfly-ux-capitalization.mdc new file mode 100644 index 0000000000..74e11167f9 --- /dev/null +++ b/.cursor/rules/patternfly-ux-capitalization.mdc @@ -0,0 +1,39 @@ +--- +description: + PatternFly UX writing — sentence case and capitalization for user-visible UI copy (aligned with PatternFly guidelines). +globs: + - "**/*.tsx" + - "**/*.jsx" +alwaysApply: false +--- + +# PatternFly capitalization (UI copy) + +Follow [PatternFly capitalization guidelines](https://www.patternfly.org/ux-writing/capitalization/) for **user-visible strings** (labels, headings, buttons, table text, empty states, errors, etc.). + +## Defaults + +- Use **sentence case**: capitalize only the **first word** of the phrase/sentence, plus **proper nouns**, **product names**, **acronyms**, and **initialisms** (e.g. OpenShift, OVN, API, DNS). +- **Buttons, navigation, page titles, headings**: sentence case unless an existing product term requires otherwise. +- Prefer **consistency** within the same surface (wizard step, table, modal). + +## Feature vs generic wording + +- Capitalize a name when it refers to a **specific product feature or UI location**; use **lowercase** when it is a **generic concept** (PatternFly’s *Compliance* / *Sources* examples apply the same idea). + +## Breadcrumbs and user-defined names + +- **Breadcrumbs**: match the capitalization of the **source page title** (even if mixed styles). +- **User- or system-defined names** (e.g. cluster names, resource names): **preserve the exact casing** the user or API used. + +## Third-party products + +- Spell and capitalize **external product names** as in **that vendor’s** documentation. + +## Docs / component names in prose + +- In **documentation-style** text about components: component names **lowercase** unless they start a sentence. **Code identifiers** follow normal language conventions. + +## Scope note + +- **`libs/ui-lib/lib/ocm/**`** does not use `t('ai:…')`; still apply these rules to **plain English** UI strings there. **`t('ai:…')`** strings under `libs/ui-lib` (outside `ocm`) follow **`translations.mdc`** for workflow; English source strings should still respect sentence case where it fits the string catalog. diff --git a/libs/locales/lib/en/translation.json b/libs/locales/lib/en/translation.json index 512804d14c..e377804be8 100644 --- a/libs/locales/lib/en/translation.json +++ b/libs/locales/lib/en/translation.json @@ -184,7 +184,6 @@ "ai:Cluster CIDR": "Cluster CIDR", "ai:Cluster configured and ready for installation.": "Cluster configured and ready for installation.", "ai:Cluster details": "Cluster details", - "ai:Cluster Details": "Cluster Details", "ai:cluster doesn't contain the feature_usage field": "cluster does not contain the feature_usage field", "ai:cluster doesn't contain the openshift_version field": "cluster does not contain the openshift_version field", "ai:Cluster Events": "Cluster Events", @@ -981,7 +980,7 @@ "ai:useAlerts must be used within AlertsContextProvider": "useAlerts must be used within AlertsContextProvider", "ai:Used to describe hosts' physical location. Helps for quicker host selection during cluster creation.": "Used to describe hosts' physical location. Helps for quicker host selection during cluster creation.", "ai:useFeatureSupportLevel must be used within FeatureSupportLevelContextProvider.": "useFeatureSupportLevel must be used within FeatureSupportLevelContextProvider.", - "ai:User-Managed Networking": "User-Managed Networking", + "ai:User-Managed networking": "User-Managed networking", "ai:Username": "Username", "ai:UUID": "UUID", "ai:Valid hostname": "Valid hostname", @@ -1014,7 +1013,7 @@ "ai:With BMC form": "With BMC form", "ai:With Discovery ISO": "With Discovery ISO", "ai:With iPXE": "With iPXE", - "ai:With User-Managed Networking, you'll need to provide and configure a load balancer for the API and ingress endpoints.": "With User-Managed Networking, you'll need to provide and configure a load balancer for the API and ingress endpoints.", + "ai:With User-Managed networking, you'll need to provide and configure a load balancer for the API and ingress endpoints.": "With User-Managed networking, you'll need to provide and configure a load balancer for the API and ingress endpoints.", "ai:Worker_one": "Worker", "ai:Worker_other": "Workers", "ai:Worker Hosts Labels": "Worker Hosts Labels", diff --git a/libs/ui-lib-tests/cypress/support/variables/networking.ts b/libs/ui-lib-tests/cypress/support/variables/networking.ts index c75f301fff..74d5ad2ed0 100644 --- a/libs/ui-lib-tests/cypress/support/variables/networking.ts +++ b/libs/ui-lib-tests/cypress/support/variables/networking.ts @@ -17,7 +17,7 @@ Cypress.env( '#form-input-clusterNetworks-0-hostPrefix-field-helper', ); Cypress.env('serviceNetworkCidrFieldHelperId', '#form-input-serviceNetworks-0-cidr-field-helper'); -Cypress.env('userManagedNetworkingRadioText', 'User-Managed Networking'); +Cypress.env('userManagedNetworkingRadioText', 'User-Managed networking'); Cypress.env('openVirtualNetworkingRadioText', 'Open Virtual Networking'); Cypress.env('networkTypeToggleId', '#form-input-networkType-field'); Cypress.env('hostSubnetFieldId', '#form-input-hostSubnet-field'); diff --git a/libs/ui-lib/lib/common/components/clusterWizard/networkingSteps/ManagedNetworkingControlGroup.tsx b/libs/ui-lib/lib/common/components/clusterWizard/networkingSteps/ManagedNetworkingControlGroup.tsx index 49aa7bd30a..3cb8c47487 100644 --- a/libs/ui-lib/lib/common/components/clusterWizard/networkingSteps/ManagedNetworkingControlGroup.tsx +++ b/libs/ui-lib/lib/common/components/clusterWizard/networkingSteps/ManagedNetworkingControlGroup.tsx @@ -55,10 +55,10 @@ export const ManagedNetworkingControlGroup = ({ value={'userManaged'} label={ <> - {t('ai:User-Managed Networking')}{' '} + {t('ai:User-Managed networking')}{' '} diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/ReviewClusterDetailTable.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/ReviewClusterDetailTable.tsx index 3f936b23ab..50d3a15c96 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/ReviewClusterDetailTable.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/ReviewClusterDetailTable.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Table, TableVariant, Tbody, Td, Tr } from '@patternfly/react-table'; import { genericTableRowKey, getDefaultCpuArchitecture } from '../../../../common'; -import { getDiskEncryptionEnabledOnStatus } from '../../clusterDetail/ClusterProperties'; +import { getDiskEncryptionEnabledOnStatus } from './utils'; import OpenShiftVersionDetail from '../../clusterDetail/OpenShiftVersionDetail'; import { useNewFeatureSupportLevel } from '../../../../common/components/newFeatureSupportLevels'; import { Cluster } from '@openshift-assisted/types/assisted-installer-service'; diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/ReviewNetworkingTable.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/ReviewNetworkingTable.tsx index bfc0a56d44..8dfc804443 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/ReviewNetworkingTable.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/ReviewNetworkingTable.tsx @@ -2,7 +2,7 @@ import { Title } from '@patternfly/react-core'; import { Table, TableVariant, Tbody, Td, Tr } from '@patternfly/react-table'; import React from 'react'; import { genericTableRowKey, isDualStack, NETWORK_TYPE_LABELS } from '../../../../common'; -import { getManagementType, getStackTypeLabel } from '../../clusterDetail/ClusterProperties'; +import { getManagementType, getStackTypeLabel } from './utils'; import { Cluster } from '@openshift-assisted/types/assisted-installer-service'; type ReviewTableRowsType = { diff --git a/libs/ui-lib/lib/ocm/components/clusterDetail/ClusterProperties.test.ts b/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/utils.test.ts similarity index 99% rename from libs/ui-lib/lib/ocm/components/clusterDetail/ClusterProperties.test.ts rename to libs/ui-lib/lib/ocm/components/clusterConfiguration/review/utils.test.ts index dbba0e32ea..3cdd278ab6 100644 --- a/libs/ui-lib/lib/ocm/components/clusterDetail/ClusterProperties.test.ts +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/utils.test.ts @@ -1,5 +1,5 @@ import { test, describe, expect } from 'vitest'; -import { getStackTypeLabel } from './ClusterProperties'; +import { getStackTypeLabel } from './utils'; import { Cluster } from '@openshift-assisted/types/assisted-installer-service'; const createCluster = (overrides: Partial = {}): Cluster => diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/utils.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/utils.tsx new file mode 100644 index 0000000000..1cc62d0236 --- /dev/null +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/review/utils.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { isDualStack } from '../../../../common'; +import { Cluster, DiskEncryption } from '@openshift-assisted/types/assisted-installer-service'; + +export const getManagementType = ({ userManagedNetworking }: Cluster): string => + userManagedNetworking ? 'User-Managed networking' : 'Cluster-managed networking'; + +export const getStackTypeLabel = (cluster: Cluster): string => + isDualStack(cluster) ? 'Dual-stack' : 'IPv4'; + +export const getDiskEncryptionEnabledOnStatus = (diskEncryption: DiskEncryption['enableOn']) => { + let diskEncryptionType = null; + switch (diskEncryption) { + case undefined: + case 'none': + break; + case 'all': + diskEncryptionType = ( + <> + Enabled on control plane nodes +
+ Enabled on workers + + ); + break; + case 'masters': + diskEncryptionType = <>Enabled on control plane nodes; + break; + case 'workers': + diskEncryptionType = <>Enabled on workers; + break; + case 'arbiters': + diskEncryptionType = <>Enabled on arbiters; + break; + case 'masters,arbiters': + diskEncryptionType = <>Enabled on control plane nodes and arbiters; + break; + case 'masters,workers': + diskEncryptionType = <>Enabled on control plane nodes and workers; + break; + case 'arbiters,workers': + diskEncryptionType = <>Enabled on arbiters and workers; + break; + case 'masters,arbiters,workers': + diskEncryptionType = ( + <> + Enabled on control plane nodes +
+ Enabled on arbiters +
+ Enabled on workers + + ); + break; + default: { + const _exhaustive: never = diskEncryption; + return _exhaustive; + } + } + return diskEncryptionType; +}; diff --git a/libs/ui-lib/lib/ocm/components/clusterDetail/AssistedInstallerExtraDetailCard.tsx b/libs/ui-lib/lib/ocm/components/clusterDetail/AssistedInstallerExtraDetailCard.tsx deleted file mode 100644 index 981901570c..0000000000 --- a/libs/ui-lib/lib/ocm/components/clusterDetail/AssistedInstallerExtraDetailCard.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import { Provider } from 'react-redux'; -import { storeDay1 } from '../../store'; -import { useSelector } from 'react-redux'; - -import { AlertsContextProvider, CpuArchitecture, FeatureListType } from '../../../common'; -import ClusterProperties from './ClusterProperties'; -import { Grid } from '@patternfly/react-core'; -import { NewFeatureSupportLevelProvider } from '../featureSupportLevels'; -import useInfraEnv from '../../hooks/useInfraEnv'; -import { usePullSecret } from '../../hooks'; -import { selectCurrentClusterState } from '../../store/slices/current-cluster/selectors'; -import { useFeatureDetection } from '../../hooks/use-feature-detection'; -import { OpenShiftVersionsContextProvider } from '../clusterWizard/OpenShiftVersionsContext'; - -type AssistedInstallerExtraDetailCardProps = { - allEnabledFeatures: FeatureListType; -}; - -const AssistedInstallerExtraDetailCard: React.FC = ({ - allEnabledFeatures, -}) => { - useFeatureDetection(allEnabledFeatures); - const { data: cluster } = useSelector(selectCurrentClusterState); - const pullSecret = usePullSecret(); - const { infraEnv } = useInfraEnv( - cluster?.id ? cluster?.id : '', - cluster?.cpuArchitecture - ? (cluster.cpuArchitecture as CpuArchitecture) - : CpuArchitecture.USE_DAY1_ARCHITECTURE, - cluster?.name, - pullSecret, - cluster?.openshiftVersion, - ); - - if (!cluster || cluster.status === 'adding-hosts') { - return null; - } - - return ( - - } - cluster={cluster} - cpuArchitecture={infraEnv?.cpuArchitecture as CpuArchitecture} - openshiftVersion={cluster.openshiftVersion} - platformType={cluster.platform?.type} - > - - - - - - ); -}; - -const Wrapper: React.FC = (props) => { - return ( - - - - - - - - ); -}; - -export default Wrapper; diff --git a/libs/ui-lib/lib/ocm/components/clusterDetail/ClusterProperties.tsx b/libs/ui-lib/lib/ocm/components/clusterDetail/ClusterProperties.tsx deleted file mode 100644 index 78449bce7f..0000000000 --- a/libs/ui-lib/lib/ocm/components/clusterDetail/ClusterProperties.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import React from 'react'; -import { GridItem, Content } from '@patternfly/react-core'; -import { - DetailItem, - DetailList, - getDefaultCpuArchitecture, - isDualStack, - NETWORK_TYPE_LABELS, - selectApiVip, - selectIngressVip, - selectIpv4Cidr, - selectIpv4HostPrefix, - selectIpv6Cidr, - selectIpv6HostPrefix, -} from '../../../common'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; -import { ClusterFeatureSupportLevelsDetailItem } from '../featureSupportLevels'; -import OpenShiftVersionDetail from './OpenShiftVersionDetail'; -import { useNewFeatureSupportLevel } from '../../../common/components/newFeatureSupportLevels'; -import { Cluster, DiskEncryption } from '@openshift-assisted/types/assisted-installer-service'; - -type ClusterPropertiesProps = { - cluster: Cluster; - externalMode?: boolean; -}; - -export const getNetworkType = (clusterNetworkType: Cluster['networkType']): string => { - return NETWORK_TYPE_LABELS[clusterNetworkType || '']; -}; - -export const getManagementType = ({ userManagedNetworking }: Cluster): string => { - let managementType: string; - userManagedNetworking - ? (managementType = 'User-Managed Networking') - : (managementType = 'Cluster-managed networking'); - return managementType; -}; - -export const getStackTypeLabel = (cluster: Cluster): string => - isDualStack(cluster) ? 'Dual-stack' : 'IPv4'; - -export const getDiskEncryptionEnabledOnStatus = (diskEncryption: DiskEncryption['enableOn']) => { - let diskEncryptionType = null; - switch (diskEncryption) { - case 'all': - diskEncryptionType = ( - <> - Enabled on control plane nodes -
- Enabled on workers - - ); - break; - case 'masters': - diskEncryptionType = <>Enabled on control plane nodes; - break; - case 'workers': - diskEncryptionType = <>Enabled on workers; - break; - } - return diskEncryptionType; -}; - -const ClusterProperties = ({ cluster, externalMode = false }: ClusterPropertiesProps) => { - const { t } = useTranslation(); - const isDualStackType = isDualStack(cluster); - const featureSupportLevelContext = useNewFeatureSupportLevel(); - - const activeFeatureConfiguration = featureSupportLevelContext.activeFeatureConfiguration; - const underlyingCpuArchitecture = - activeFeatureConfiguration?.underlyingCpuArchitecture || getDefaultCpuArchitecture(); - - return ( - <> - {!externalMode && ( - - {t('ai:Cluster Details')} - - )} - - - {externalMode ? undefined : ( - } - testId="openshift-version" - /> - )} - - - - - - - - - - - - - {isDualStackType ? ( - <> - - - - ) : undefined} - - {isDualStackType ? ( - - ) : undefined} - - - - - - ); -}; -export default ClusterProperties; diff --git a/libs/ui-lib/lib/ocm/components/clusterDetail/index.ts b/libs/ui-lib/lib/ocm/components/clusterDetail/index.ts index f4d8de0216..0286b7b570 100644 --- a/libs/ui-lib/lib/ocm/components/clusterDetail/index.ts +++ b/libs/ui-lib/lib/ocm/components/clusterDetail/index.ts @@ -2,4 +2,3 @@ export * from './utils'; export { default as ClusterDetail } from './ClusterDetail'; export { default as ClusterInstallationProgressCard } from './ClusterInstallationProgressCard'; export { default as AssistedInstallerDetailCard } from './AssistedInstallerDetailCard'; -export { default as AssistedInstallerExtraDetailCard } from './AssistedInstallerExtraDetailCard'; diff --git a/libs/ui-lib/lib/ocm/components/featureSupportLevels/featureStateUtils.ts b/libs/ui-lib/lib/ocm/components/featureSupportLevels/featureStateUtils.ts index 7bfd2398dc..29a38497a1 100644 --- a/libs/ui-lib/lib/ocm/components/featureSupportLevels/featureStateUtils.ts +++ b/libs/ui-lib/lib/ocm/components/featureSupportLevels/featureStateUtils.ts @@ -229,7 +229,7 @@ export const getNewFeatureDisabledReason = ( } case 'USER_MANAGED_NETWORKING': { if (!isSupported) { - return `User-Managed Networking is not supported when using ${ + return `User-Managed networking is not supported when using ${ platformType ? ExternalPlatformLabels[platformType] : '' }`; }