Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion frontend/components/common/GenericDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ interface GenericDialogProps {
initialFocus?: MutableRefObject<null>
isStatic?: boolean
buttonProps?: ButtonProps
/** Extra classes for the trigger button's wrapper β€” e.g. `min-w-0` to let it shrink in a flex row. */
buttonWrapperClass?: string
}

const GenericDialog = forwardRef(
Expand All @@ -46,6 +48,7 @@ const GenericDialog = forwardRef(
initialFocus,
isStatic = false,
buttonProps,
buttonWrapperClass,
}: GenericDialogProps,
ref
) => {
Expand Down Expand Up @@ -79,7 +82,7 @@ const GenericDialog = forwardRef(
return (
<>
{buttonContent && (
<div className="flex items-center justify-center max-w-full">
<div className={clsx('flex items-center justify-center max-w-full', buttonWrapperClass)}>
<Button
variant={buttonVariant}
onClick={openModal}
Expand Down
11 changes: 7 additions & 4 deletions frontend/components/environments/secrets/CommentDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,17 @@ export const CommentDialog = (props: {
}
buttonVariant="outline"
buttonContent={
<div className="py-1 2xl:py-0 flex items-center gap-1">
<FaHashtag className={clsx(comment && 'text-emerald-500 ')} />{' '}
<>
<span className="py-0.5 2xl:py-1">
<FaHashtag className={clsx(comment && 'text-emerald-500 ')} />
</span>
<span className="hidden 2xl:block text-xs max-w-[24ch] truncate">
{comment || 'Comment'}
</span>
</div>
</>
}
buttonProps={{ tabIndex: -1 }}
buttonProps={{ tabIndex: -1, classString: 'min-w-0' }}
buttonWrapperClass="min-w-0"
>
<div className="pt-4 ph-no-capture">
<textarea
Expand Down
7 changes: 2 additions & 5 deletions frontend/components/environments/secrets/HistoryDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,7 @@ export const HistoryDialog = ({
dialogTitle={
<div>
<h3 className="text-sm font-medium leading-6 text-zinc-800 dark:text-zinc-200">
<span className="font-mono ph-no-capture">
{secret.key}
</span>{' '}
history
<span className="font-mono ph-no-capture">{secret.key}</span> history
</h3>
<div className="text-neutral-500 text-xs">
View the chronological history of changes made to this secret.
Expand All @@ -182,7 +179,7 @@ export const HistoryDialog = ({
buttonVariant="outline"
buttonContent={
<>
<span className="py-1">
<span className="py-0.5 2xl:py-1">
<FaHistory className="shrink-0" />
</span>
<span className="hidden 2xl:block text-xs">History</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const OverrideDialog = (props: {
buttonVariant="outline"
buttonContent={
<>
<span className="py-1">
<span className="py-0.5 2xl:py-1">
<FaUserEdit className={clsx('shrink-0', activeOverride && 'text-amber-500')} />
</span>
<span className={clsx('hidden 2xl:block text-xs', activeOverride && 'text-amber-500')}>
Expand Down
50 changes: 34 additions & 16 deletions frontend/components/environments/secrets/SecretRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,10 @@ function SecretRow(props: {
nextKeyInput?.focus()
}

// The menu shows on hover OR focus-within, so the input must adopt the menu's
// background (and drop its top-right rounding, below) in both states to blend seamlessly.
const INPUT_BASE_STYLE =
'w-full font-mono custom bg-transparent group-hover:bg-zinc-200 dark:group-hover:bg-zinc-700 transition ease ph-no-capture rounded-lg text-2xs 2xl:text-sm'
'w-full font-mono custom bg-transparent group-hover:bg-zinc-200 dark:group-hover:bg-zinc-700 group-focus-within:bg-zinc-200 dark:group-focus-within:bg-zinc-700 transition ease ph-no-capture rounded-lg text-2xs 2xl:text-sm'

const keyIsBlank = secret.key.length === 0

Expand Down Expand Up @@ -226,21 +228,30 @@ function SecretRow(props: {
</div>
<div
className={clsx(
'flex gap-1 items-center pt-1 px-1 rounded-t-lg',
// max-w-full caps the menu at the key input width; overflow-hidden is a hard
// backstop so nothing (tags, the icon-only row below 2xl) can spill past it. The
// comment (min-w-0, below) absorbs the slack first and truncates with an ellipsis.
'flex gap-1 items-center pt-1 px-1 rounded-t-lg max-w-full overflow-hidden',
'bg-zinc-200 dark:bg-zinc-700',
'z-10 group-hover:z-10 group-focus-within:z-10 absolute right-0 -top-9 translate-y-9 group-hover:translate-y-0 group-focus-within:translate-y-0 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100',
// Anchor by the bottom edge (bottom-full) so the menu is always flush with the
// input's top edge regardless of its own height β€” a fixed -top offset leaves a
// sub-pixel gap once the menu is shorter than that offset at smaller text sizes.
'z-10 group-hover:z-10 group-focus-within:z-10 absolute right-0 bottom-full translate-y-full group-hover:translate-y-0 group-focus-within:translate-y-0 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100',
'transition ease'
)}
>
{!stagedForDelete && userCanUpdateSecrets && (
<TypeSelector
currentType={secret.type}
onChange={(type) => handlePropertyChange(secret.id, 'type', type)}
disabled={isSealedAndSaved}
/>
<div className="shrink-0">
<TypeSelector
currentType={secret.type}
onChange={(type) => handlePropertyChange(secret.id, 'type', type)}
disabled={isSealedAndSaved}
/>
</div>
)}
<div
className={clsx(
'shrink-0',
secret.tags.length === 0 && 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity ease'
)}
>
Expand All @@ -256,6 +267,7 @@ function SecretRow(props: {
{!stagedForDelete && (
<div
className={clsx(
'min-w-0',
secret.comment.length === 0 &&
'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity ease'
)}
Expand All @@ -277,7 +289,10 @@ function SecretRow(props: {
className={clsx(
'flex gap-1 items-start pt-1 rounded-t-lg right-0 px-1 transition ease',
'bg-zinc-200 dark:bg-zinc-700',
'z-10 absolute -top-9 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 translate-y-9 group-hover:translate-y-0 group-focus-within:translate-y-0'
// Anchor by the bottom edge (bottom-full) so the menu is always flush with the
// input's top edge regardless of its own height β€” a fixed -top offset leaves a
// sub-pixel gap once the menu is shorter than that offset at smaller text sizes.
'z-10 absolute bottom-full opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 translate-y-full group-hover:translate-y-0 group-focus-within:translate-y-0'
)}
>
{isMultiLine && (
Expand All @@ -288,7 +303,7 @@ function SecretRow(props: {
onClick={() => toggleExpanded()}
title={expanded ? 'Collapse' : 'Expand'}
>
<div className="py-1">{expanded ? <FaCompressArrowsAlt /> : <FaExpandArrowsAlt />}</div>
<div className="py-0.5 2xl:py-1">{expanded ? <FaCompressArrowsAlt /> : <FaExpandArrowsAlt />}</div>
</Button>
</div>
)}
Expand All @@ -301,13 +316,13 @@ function SecretRow(props: {
onClick={toggleReveal}
title={isRevealed ? 'Mask value' : 'Reveal value'}
>
<span className="py-1">{isRevealed ? <FaEyeSlash /> : <FaEye />}</span>{' '}
<span className="py-0.5 2xl:py-1">{isRevealed ? <FaEyeSlash /> : <FaEye />}</span>{' '}
<span className="hidden 2xl:block text-xs">{isRevealed ? 'Mask' : 'Reveal'}</span>
</Button>
)}
{isSealedAndSaved && (
<span
className="text-xs text-neutral-500 px-2 py-1 flex items-center gap-1"
className="text-xs text-neutral-500 px-2 py-0.5 2xl:py-1 flex items-center gap-1"
title="This secret is sealed and cannot be revealed"
>
<FaLock /> Sealed
Expand Down Expand Up @@ -345,7 +360,7 @@ function SecretRow(props: {
onClick={() => handleDelete(secret.id)}
title={stagedForDelete ? 'Restore this secret' : 'Delete this secret'}
>
<div className="p-1">{stagedForDelete ? <FaUndo /> : <FaTrashAlt />}</div>
<div className="py-0.5 2xl:py-1">{stagedForDelete ? <FaUndo /> : <FaTrashAlt />}</div>
</Button>
)}
</div>
Expand All @@ -359,7 +374,7 @@ function SecretRow(props: {
disabled={stagedForDelete || !userCanUpdateSecrets}
className={clsx(
INPUT_BASE_STYLE,
'rounded-lg group-hover:rounded-tr-none',
'rounded-lg group-hover:rounded-tr-none group-focus-within:rounded-tr-none',
'',
keyIsBlank
? 'ring-1 ring-inset ring-red-500'
Expand Down Expand Up @@ -396,7 +411,7 @@ function SecretRow(props: {
/>
{keyActionMenu}
</div>
<div className={clsx("w-2/3 relative group flex justify-between gap-2 focus-within:ring-1 focus-within:ring-inset focus-within:ring-zinc-500 rounded-lg bg-transparent transition ease", autocomplete.isOpen && 'rounded-bl-none')}>
<div className={clsx("w-2/3 relative group flex justify-between gap-2 rounded-lg bg-transparent transition ease", autocomplete.isOpen && 'rounded-bl-none')}>
{isBoolean && !stagedForDelete && (
<div className="flex items-center px-2">
<Switch
Expand Down Expand Up @@ -426,7 +441,10 @@ function SecretRow(props: {
className={clsx(
INPUT_BASE_STYLE,
inputTextColor(),
'w-full group-hover:rounded-tr-none',
// Ring lives on the textarea (like the key input) so it paints above the
// focus background β€” an inset ring on the wrapper would be hidden by the
// now-opaque textarea covering it.
'w-full group-hover:rounded-tr-none group-focus-within:rounded-tr-none focus:ring-1 focus:ring-inset focus:ring-zinc-500',
autocomplete.isOpen && 'rounded-bl-none'
)}
value={isSealedAndSaved ? '' : secret.value}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export const ShareSecretDialog = ({ secret }: { secret: SecretType }) => {
title={box ? 'Share this link' : `Share ${secret.key}`}
buttonVariant="outline"
buttonContent={
<span className="py-1">
<span className="py-0.5 2xl:py-1">
<FaShareAlt />
</span>
}
Expand Down
5 changes: 4 additions & 1 deletion frontend/components/environments/secrets/TagsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,10 @@ export const TagsDialog = (props: {
buttonContent={
tags.length === 0 ? (
<>
<FaTags /> Tags
<span className="py-0.5 2xl:py-1">
<FaTags />
</span>{' '}
<span className="hidden 2xl:block text-xs">Tags</span>
</>
) : undefined
}
Expand Down
19 changes: 15 additions & 4 deletions frontend/components/environments/secrets/TypeSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApiSecretTypeChoices } from '@/apollo/graphql'
import clsx from 'clsx'
import { FaKey, FaLock, FaCog } from 'react-icons/fa'
import { useRef, useEffect, useState } from 'react'
import { useRef, useEffect, useState, useCallback } from 'react'

const SECRET_TYPES = [
{ value: ApiSecretTypeChoices.Config, label: 'Config', icon: FaCog },
Expand All @@ -24,7 +24,7 @@ export const TypeSelector = ({

const [indicator, setIndicator] = useState<{ left: number; width: number } | null>(null)

useEffect(() => {
const updateIndicator = useCallback(() => {
const activeIndex = SECRET_TYPES.findIndex((t) => t.value === currentType)
const btn = buttonRefs.current[activeIndex]
const container = containerRef.current
Expand All @@ -38,6 +38,17 @@ export const TypeSelector = ({
}
}, [currentType])

// Recompute on type change and whenever the control resizes β€” e.g. the labels
// collapse to icons at the 2xl breakpoint β€” so the active-pill stays aligned.
useEffect(() => {
updateIndicator()
const container = containerRef.current
if (!container || typeof ResizeObserver === 'undefined') return
const observer = new ResizeObserver(updateIndicator)
observer.observe(container)
return () => observer.disconnect()
}, [updateIndicator])

const activeBgColor =
currentType === ApiSecretTypeChoices.Sealed
? 'bg-red-500/20'
Expand Down Expand Up @@ -78,7 +89,7 @@ export const TypeSelector = ({
: `Change secret type to ${label}`
}
className={clsx(
'relative z-[1] flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium transition-colors duration-200',
'relative z-[1] flex items-center gap-1 px-2 py-1 2xl:py-1.5 rounded-full text-2xs font-medium transition-colors duration-200',
isActive
? value === ApiSecretTypeChoices.Sealed
? 'text-red-600 dark:text-red-400'
Expand All @@ -90,7 +101,7 @@ export const TypeSelector = ({
)}
>
<Icon className="text-3xs" />
{label}
<span className="hidden 2xl:block">{label}</span>
</button>
)
})}
Expand Down
Loading