diff --git a/admin-ui/app/components/GluuDropdown/GluuDropdown.style.ts b/admin-ui/app/components/GluuDropdown/GluuDropdown.style.ts index 039d47fc01..29ca43d3db 100644 --- a/admin-ui/app/components/GluuDropdown/GluuDropdown.style.ts +++ b/admin-ui/app/components/GluuDropdown/GluuDropdown.style.ts @@ -35,6 +35,12 @@ const getPositionStyles = (position: DropdownPosition) => { marginLeft: SHARED_DROPDOWN_STYLES.margin, ...baseTransform, } + case 'bottom-end': + return { + top: '100%', + marginTop: SHARED_DROPDOWN_STYLES.margin, + right: 0, + } default: return {} } @@ -54,6 +60,13 @@ const getArrowStyles = (position: DropdownPosition) => { left: '50%', transform: 'translateX(-50%)', } + case 'bottom-end': + return { + top: 'calc(100% - 2px)', + left: '50%', + transform: 'translateX(-50%)', + zIndex: SHARED_DROPDOWN_STYLES.menuZIndex + 1, + } case 'left': return { right: '-15px', @@ -76,7 +89,8 @@ export const useStyles = makeStyles<{ position: DropdownPosition dropdownBg: string centerText?: boolean -}>()((_theme, { isDark, position, dropdownBg, centerText }) => ({ + optionPadding?: string +}>()((_theme, { isDark, position, dropdownBg, centerText, optionPadding }) => ({ dropdownWrapper: { position: 'relative', display: 'inline-block', @@ -93,7 +107,12 @@ export const useStyles = makeStyles<{ maxHeight: SHARED_DROPDOWN_STYLES.maxHeight, overflow: 'visible', ...getPositionStyles(position), - marginTop: position === 'bottom' ? '13px' : position === 'top' ? undefined : '4px', + marginTop: + position === 'bottom' || position === 'bottom-end' + ? '13px' + : position === 'top' + ? undefined + : '4px', }, dropdownMenuContent: { padding: SHARED_DROPDOWN_STYLES.padding, @@ -140,6 +159,7 @@ export const useStyles = makeStyles<{ ...createBaseOptionStyles({ isDark, ...(centerText && { optionPadding: '12px 12px' }), + ...(optionPadding && { optionPadding }), }), '&.single-option': { justifyContent: 'center', diff --git a/admin-ui/app/components/GluuDropdown/GluuDropdown.tsx b/admin-ui/app/components/GluuDropdown/GluuDropdown.tsx index 71bae11080..0a68cf7eaf 100644 --- a/admin-ui/app/components/GluuDropdown/GluuDropdown.tsx +++ b/admin-ui/app/components/GluuDropdown/GluuDropdown.tsx @@ -49,6 +49,7 @@ export const GluuDropdown = ({ renderOption, renderTrigger, centerText = false, + optionPadding, }: GluuDropdownProps): React.ReactElement => { const [internalState, setInternalState] = useState({ isOpen: false, @@ -66,9 +67,10 @@ export const GluuDropdown = ({ const dropdownBg = useMemo(() => { return isDark ? customColors.darkDropdownBg : customColors.white }, [isDark]) - const { classes } = useStyles({ isDark, position, dropdownBg, centerText }) + const { classes } = useStyles({ isDark, position, dropdownBg, centerText, optionPadding }) const isOpen = controlled ? (controlledIsOpen ?? false) : internalState.isOpen + const arrowAnchoredToTrigger = position === 'bottom-end' const searchQuery = internalState.searchQuery const setIsOpen = useCallback( @@ -152,7 +154,7 @@ export const GluuDropdown = ({ option.onClick?.(option.value, option) onSelect?.(option.value, option) - if (closeOnSelect) { + if (closeOnSelect && !option.keepOpen) { setIsOpen(false) setInternalState((prev) => ({ ...prev, searchQuery: '' })) } @@ -269,7 +271,7 @@ export const GluuDropdown = ({ role="listbox" id={listboxId} > - {showArrow && ( + {showArrow && !arrowAnchoredToTrigger && (
@@ -293,6 +295,11 @@ export const GluuDropdown = ({ )} + {isOpen && showArrow && arrowAnchoredToTrigger && ( +
+ +
+ )} ) } diff --git a/admin-ui/app/components/GluuDropdown/types/GluuDropdownTypes.ts b/admin-ui/app/components/GluuDropdown/types/GluuDropdownTypes.ts index 173266000f..3b85482859 100644 --- a/admin-ui/app/components/GluuDropdown/types/GluuDropdownTypes.ts +++ b/admin-ui/app/components/GluuDropdown/types/GluuDropdownTypes.ts @@ -1,6 +1,6 @@ import type React from 'react' -export type DropdownPosition = 'top' | 'bottom' | 'left' | 'right' +export type DropdownPosition = 'top' | 'bottom' | 'bottom-end' | 'left' | 'right' export type DropdownValue = string | number | boolean @@ -13,6 +13,7 @@ export type GluuDropdownOption = { icon?: React.ReactNode metadata?: Record searchValue?: string + keepOpen?: boolean } export type GluuDropdownProps = { @@ -43,6 +44,7 @@ export type GluuDropdownProps = { selectedOption?: GluuDropdownOption | GluuDropdownOption[], ) => React.ReactNode centerText?: boolean + optionPadding?: string } export type DropdownState = { diff --git a/admin-ui/app/locales/en/translation.json b/admin-ui/app/locales/en/translation.json index c33ede182b..9ae232e17d 100644 --- a/admin-ui/app/locales/en/translation.json +++ b/admin-ui/app/locales/en/translation.json @@ -759,6 +759,7 @@ "platform": "Platform", "authType": "Auth Type", "showCedarLogs?": "Cedarling Log enabled?", + "cedarlingLogs?": "Cedarling logs?", "reloginToViewCedarlingChanges": "Please Re-login to view the cedarling changes.", "allAvailableHintsSelected": "All available hint options are selected", "noMatchingOptions": "No matching options", diff --git a/admin-ui/app/locales/es/translation.json b/admin-ui/app/locales/es/translation.json index 6294fa8f95..1efd331d32 100644 --- a/admin-ui/app/locales/es/translation.json +++ b/admin-ui/app/locales/es/translation.json @@ -721,6 +721,7 @@ "platform": "Plataforma", "authType": "Tipo de Autenticación", "showCedarLogs?": "¿Registro de Cedarling habilitado?", + "cedarlingLogs?": "¿Registros de Cedarling?", "reloginToViewCedarlingChanges": "Por favor, vuelve a iniciar sesión para ver los cambios de Cedarling.", "allAvailableHintsSelected": "Todas las opciones de sugerencia disponibles están seleccionadas", "noMatchingOptions": "No hay opciones coincidentes", diff --git a/admin-ui/app/locales/fr/translation.json b/admin-ui/app/locales/fr/translation.json index 39afb9b62c..53e7b56809 100644 --- a/admin-ui/app/locales/fr/translation.json +++ b/admin-ui/app/locales/fr/translation.json @@ -842,6 +842,7 @@ "platform": "Plateforme", "authType": "Type d'authentification", "showCedarLogs?": "Cedarling Log est-il activé?", + "cedarlingLogs?": "Journaux Cedarling ?", "reloginToViewCedarlingChanges": "Veuillez vous reconnecter pour voir les modifications de cedarling.", "allAvailableHintsSelected": "Toutes les options d'indice disponibles sont sélectionnées.", "noMatchingOptions": "Aucune option correspondante", diff --git a/admin-ui/app/locales/pt/translation.json b/admin-ui/app/locales/pt/translation.json index 99661ec6ef..a53d9592bd 100644 --- a/admin-ui/app/locales/pt/translation.json +++ b/admin-ui/app/locales/pt/translation.json @@ -798,6 +798,7 @@ "platform": "Plataforma", "authType": "Tipo de autenticação", "showCedarLogs?": "O Cedarling Log está habilitado?", + "cedarlingLogs?": "Logs do Cedarling?", "reloginToViewCedarlingChanges": "Por favor, faça login novamente para ver as alterações do cedarling.", "allAvailableHintsSelected": "Todas as opções de dica disponíveis estão selecionadas", "noMatchingOptions": "Nenhuma opção correspondente", diff --git a/admin-ui/app/routes/Apps/Gluu/GluuNavBar.tsx b/admin-ui/app/routes/Apps/Gluu/GluuNavBar.tsx index d116c36b06..12a1762af7 100755 --- a/admin-ui/app/routes/Apps/Gluu/GluuNavBar.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuNavBar.tsx @@ -151,7 +151,7 @@ const GluuNavBar = () => { )} - position="bottom" + position="bottom-end" /> )} diff --git a/admin-ui/app/routes/Apps/Gluu/GluuToggle.tsx b/admin-ui/app/routes/Apps/Gluu/GluuToggle.tsx index 2c7544af0a..b42fcf847b 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuToggle.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuToggle.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import type { ChangeEvent } from 'react' import clsx from 'clsx' +import customColors from '@/customColors' import type { JsonValue } from './types/common' import type { GluuToggleProps } from './types/GluuToggle.types' import { useStyles } from './styles/GluuToggle.style' @@ -39,7 +40,7 @@ const GluuToggle = ,>({ @@ -48,7 +49,7 @@ const GluuToggle = ,>({ diff --git a/admin-ui/app/routes/Apps/Gluu/__tests__/GluuNavBar.test.tsx b/admin-ui/app/routes/Apps/Gluu/__tests__/GluuNavBar.test.tsx index 4f9c5d6e80..561311d513 100644 --- a/admin-ui/app/routes/Apps/Gluu/__tests__/GluuNavBar.test.tsx +++ b/admin-ui/app/routes/Apps/Gluu/__tests__/GluuNavBar.test.tsx @@ -15,6 +15,10 @@ const createTestStore = (userinfo: UserInfo | null): Store => }), }) +jest.mock('@/utils/hooks/useCedarlingLogToggle', () => ({ + useCedarlingLogToggle: () => ({ enabled: false, toggle: jest.fn(), isSaving: false }), +})) + const renderNavBar = (userinfo: UserInfo | null) => { const store = createTestStore(userinfo) const Wrapper = ({ children }: { children: ReactNode }) => ( diff --git a/admin-ui/app/routes/components/Dropdowns/DropdownProfile.tsx b/admin-ui/app/routes/components/Dropdowns/DropdownProfile.tsx index a1b600d0d5..9bc9550e00 100755 --- a/admin-ui/app/routes/components/Dropdowns/DropdownProfile.tsx +++ b/admin-ui/app/routes/components/Dropdowns/DropdownProfile.tsx @@ -5,12 +5,16 @@ import { useAppNavigation, ROUTES } from '@/helpers/navigation' import { auditLogoutLogs } from 'Redux/features/sessionSlice' import { MANUAL_LOGOUT } from '@/audit/messages' import { GluuDropdown, type GluuDropdownOption } from 'Components' +import Box from '@mui/material/Box' +import Switch from '@mui/material/Switch' +import { useCedarlingLogToggle } from '@/utils/hooks/useCedarlingLogToggle' import type { DropdownProfileProps } from './types' const DropdownProfile = ({ trigger, renderTrigger, position = 'bottom' }: DropdownProfileProps) => { const { t } = useTranslation() const dispatch = useAppDispatch() const { navigateToRoute } = useAppNavigation() + const { enabled: cedarLogsEnabled, toggle: toggleCedarLogs } = useCedarlingLogToggle() const handleLogout = useCallback(() => { dispatch(auditLogoutLogs({ message: MANUAL_LOGOUT })) @@ -25,6 +29,33 @@ const DropdownProfile = ({ trigger, renderTrigger, position = 'bottom' }: Dropdo navigateToRoute(ROUTES.PROFILE) }, }, + { + value: 'cedarLogs', + label: ( + + {t('fields.cedarlingLogs?')} + + + ), + searchValue: t('fields.cedarlingLogs?'), + keepOpen: true, + onClick: () => { + toggleCedarLogs() + }, + }, { value: 'logout', label: t('menus.signout'), @@ -33,7 +64,7 @@ const DropdownProfile = ({ trigger, renderTrigger, position = 'bottom' }: Dropdo }, }, ], - [t, navigateToRoute, handleLogout], + [t, navigateToRoute, handleLogout, cedarLogsEnabled, toggleCedarLogs], ) return ( @@ -43,6 +74,7 @@ const DropdownProfile = ({ trigger, renderTrigger, position = 'bottom' }: Dropdo options={options} position={position} minWidth={182} + optionPadding="12px" showArrow={true} /> ) diff --git a/admin-ui/app/routes/components/Dropdowns/MobileProfileDropdown.tsx b/admin-ui/app/routes/components/Dropdowns/MobileProfileDropdown.tsx index 1a2815040b..7eded657b8 100644 --- a/admin-ui/app/routes/components/Dropdowns/MobileProfileDropdown.tsx +++ b/admin-ui/app/routes/components/Dropdowns/MobileProfileDropdown.tsx @@ -19,6 +19,8 @@ import { THEME_LIGHT, THEME_DARK } from '@/context/theme/constants' import { useThemePersistence } from '@/hooks/useThemePersistence' import { useLangPersistence } from '@/hooks/useLangPersistence' import { LANG_CODES, DEFAULT_LANG } from '@/constants' +import Switch from '@mui/material/Switch' +import { useCedarlingLogToggle } from '@/utils/hooks/useCedarlingLogToggle' import { useStyles } from './styles/MobileProfileDropdown.style' import type { MobileProfileDropdownProps } from './types' @@ -89,6 +91,8 @@ const MobileProfileDropdown = ({ userInfo, renderTrigger }: MobileProfileDropdow const onChangeTheme = useThemePersistence(userInfo) + const { enabled: cedarLogsEnabled, toggle: toggleCedarLogs } = useCedarlingLogToggle() + const handleProfile = useCallback(() => { setIsOpen(false) navigateToRoute(ROUTES.PROFILE) @@ -219,6 +223,20 @@ const MobileProfileDropdown = ({ userInfo, renderTrigger }: MobileProfileDropdow /> +
+ +
+ + {t('fields.cedarlingLogs?')} + + +
+