Skip to content

Commit

Permalink
fix(ui): disabled button, improv secret input, http label (#2597)
Browse files Browse the repository at this point in the history
## Describe your changes

- Fix button disabled state
- Fix secret input buttons display
- Fix clicking copy/show in some secrets input would trigger form
- Fix http label to be generic
- Import tailwind colors from figma
  • Loading branch information
bodinsamuel authored Aug 9, 2024
1 parent c104877 commit f0e05be
Show file tree
Hide file tree
Showing 13 changed files with 169 additions and 176 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,7 @@ All notable changes to this project will be documented in this file.
- *(webapp)* Improve field values on refresh (#1782) by @hassan254-prog
- *(db)* Add index for isSyncJobRunning (#1793) by @bodinsamuel
- *(integrations)* Api support and integration template for zoho-mail (#1779) by @hassan254-prog
- *(SecretInput)* Handle null optionalvalue gracefully (#1781) by @hassan254-prog
- *(SecretInput)* Handle null optionalValue gracefully (#1781) by @hassan254-prog
- *(webapp)* Improve input integration ID on edit (#1783) by @hassan254-prog

### Changed
Expand Down
33 changes: 33 additions & 0 deletions packages/webapp/src/components/HttpLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { cva } from 'class-variance-authority';
import { cn } from '../utils/utils';
import type { HTTP_VERB } from '@nangohq/types';

const styles = cva('', {
variants: {
bg: {
GET: 'bg-green-base-35',
POST: 'bg-blue-base-35',
PUT: 'bg-yellow-base-35',
PATCH: 'bg-orange-base-35',
DELETE: 'bg-red-base-35'
},
text: {
GET: 'text-green-base',
POST: 'text-blue-base',
PUT: 'text-yellow-base',
PATCH: 'text-orange-base',
DELETE: 'text-red-base'
}
}
});

export const HttpLabel: React.FC<{ verb: HTTP_VERB; path: string }> = ({ verb, path }) => {
return (
<div className="flex items-center gap-2 text-[11px]">
<div className={cn(styles({ bg: verb }), 'py-0.5 px-2 rounded')}>
<span className={cn(styles({ text: verb }), 'font-semibold')}>{verb}</span>
</div>
<span className="break-all text-sm">{path}</span>
</div>
);
};
6 changes: 3 additions & 3 deletions packages/webapp/src/components/ui/button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { cn } from '../../../utils/utils';
export type ButtonVariants = VariantProps<typeof buttonStyles>['variant'];

export const buttonStyles = cva(
'rounded text-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none',
'rounded text-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-100',
{
variants: {
variant: {
primary: 'bg-white text-black hover:bg-gray-300 disabled:bg-pure-black disabled:text-text-light-gray',
primary: 'bg-white text-black hover:bg-gray-300 disabled:bg-active-gray disabled:text-white',
secondary: 'bg-[#282828] text-white hover:bg-gray-800',
success: 'bg-green-700 text-white hover:bg-green-500',
danger: 'bg-red-base text-white hover:bg-red-500',
Expand All @@ -26,7 +26,7 @@ export const buttonStyles = cva(
emptyFaded: 'border border-text-light-gray text-text-light-gray hover:text-white focus:text-white'
},
size: {
xs: 'h-8 py-1 px-3',
xs: 'h-8 py-1 px-2',
sm: 'h-9 px-2',
md: 'h-9 py-2 px-4',
lg: 'h-11 px-4'
Expand Down
18 changes: 15 additions & 3 deletions packages/webapp/src/components/ui/button/CopyButton.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { CopyIcon, Link2Icon } from '@radix-ui/react-icons';
import type { ClassValue } from 'clsx';
import { cn } from '../../../utils/utils';
Expand All @@ -14,10 +14,12 @@ interface ClipboardButtonProps {

export const CopyButton: React.FC<ClipboardButtonProps> = ({ text, iconType = 'clipboard', textPrompt = 'Copy', className }) => {
const [tooltipText, setTooltipText] = useState(textPrompt);
const triggerRef = useRef(null);

const copyToClipboard = async (e: React.MouseEvent) => {
try {
e.stopPropagation();
e.preventDefault();
await navigator.clipboard.writeText(text);
setTooltipText('Copied');
} catch (err) {
Expand All @@ -39,11 +41,21 @@ export const CopyButton: React.FC<ClipboardButtonProps> = ({ text, iconType = 'c
return (
<Tooltip delayDuration={0}>
<TooltipTrigger asChild>
<Button variant={'icon'} onClick={copyToClipboard} size={'sm'}>
<Button variant={'icon'} onClick={copyToClipboard} size={'xs'} ref={triggerRef}>
{iconType === 'link' ? <Link2Icon className={cn(`h-4`, className)} /> : <CopyIcon className={cn(`h-4 w-4`, className)} />}
</Button>
</TooltipTrigger>
<TooltipContent sideOffset={10} className="text-white">
<TooltipContent
sideOffset={10}
className="text-white"
onPointerDownOutside={(event) => {
// Radix assume a click on a tooltip should close it
// https://github.com/radix-ui/primitives/issues/2029
if (event.target === triggerRef.current || (event.target as any).parentNode === triggerRef.current) {
event.preventDefault();
}
}}
>
{tooltipText}
</TooltipContent>
</Tooltip>
Expand Down
51 changes: 27 additions & 24 deletions packages/webapp/src/components/ui/input/SecretInput.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,56 @@
import { forwardRef, useCallback, useState } from 'react';
import { forwardRef, useState } from 'react';
import { EyeIcon, EyeSlashIcon, ArrowPathIcon } from '@heroicons/react/24/outline';
import classNames from 'classnames';
import { CopyButton } from '../button/CopyButton';
import { Input } from './Input';
import Button from '../button/Button';
import { cn } from '../../../utils/utils';

type SecretInputProps = Omit<JSX.IntrinsicElements['input'], 'defaultValue'> & {
copy?: boolean;
defaultValue?: string;
optionalvalue?: string | null;
setoptionalvalue?: (value: string) => void;
additionalclass?: string;
optionalValue?: string | null;
setOptionalValue?: (value: string) => void;
additionalClass?: string;
tall?: boolean;
refresh?: () => void;
};

const SecretInput = forwardRef<HTMLInputElement, SecretInputProps>(function PasswordField(
{ className, copy, optionalvalue, setoptionalvalue, defaultValue, refresh, ...props },
{ className, copy, optionalValue, setOptionalValue, defaultValue, refresh, ...props },
ref
) {
const [isSecretVisible, setIsSecretVisible] = useState(false);

const [changedValue, setChangedValue] = useState(defaultValue);

const value = optionalvalue === null ? '' : optionalvalue || changedValue;
const updateValue = setoptionalvalue || setChangedValue;
const value = optionalValue === null ? '' : optionalValue || changedValue;
const updateValue = setOptionalValue || setChangedValue;

const top = props.tall ? 'top-2.5' : 'top-0.5';

const toggleSecretVisibility = useCallback(() => setIsSecretVisible(!isSecretVisible), [isSecretVisible, setIsSecretVisible]);
const toggleSecretVisibility: React.MouseEventHandler<HTMLButtonElement> = (e) => {
e.preventDefault();
setIsSecretVisible(!isSecretVisible);
};

return (
<div className={`relative flex ${props.additionalclass ?? ''}`}>
<input
<div className={`relative flex grow ${props.additionalClass ?? ''}`}>
<Input
type={isSecretVisible ? 'text' : 'password'}
ref={ref}
className={classNames(
'border-border-gray bg-active-gray text-text-light-gray focus:border-white focus:ring-white block w-full appearance-none rounded-md border px-3 py-1 text-sm placeholder-gray-400 shadow-sm focus:outline-none',
className
)}
variant={'flat'}
className={cn(className)}
value={value || ''}
onChange={(e) => updateValue(e.currentTarget.value)}
{...props}
after={
<div className={`flex items-center gap-1 bg-active-gray`}>
<Button variant="icon" size={'xs'} onClick={toggleSecretVisibility}>
{isSecretVisible ? <EyeSlashIcon className="w-4 h-4" /> : <EyeIcon className="w-4 h-4" />}
</Button>
{copy && <CopyButton text={(props.value || optionalValue || defaultValue)?.toString() || ''} />}
{refresh && <ArrowPathIcon className="flex h-4 w-4 cursor-pointer text-gray-500" onClick={refresh} />}
</div>
}
/>
<span className={`absolute right-2 ${top} flex items-center bg-active-gray border-border-gray gap-2 h-6`}>
<span onClick={toggleSecretVisibility} className="rounded text-sm text-gray-600 cursor-pointer">
{isSecretVisible ? <EyeSlashIcon className="w-4 h-4" /> : <EyeIcon className="w-4 h-4" />}
</span>
{copy && <CopyButton text={(props.value || optionalvalue || defaultValue)?.toString() || ''} />}
{refresh && <ArrowPathIcon className="flex h-4 w-4 cursor-pointer text-gray-500" onClick={refresh} />}
</span>
</div>
);
});
Expand Down
42 changes: 20 additions & 22 deletions packages/webapp/src/components/ui/input/SecretTextArea.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import type { ChangeEvent, TextareaHTMLAttributes } from 'react';
import { forwardRef, useCallback, useState } from 'react';
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
import classNames from 'classnames';
import { CopyButton } from '../button/CopyButton';
import { cn } from '../../../utils/utils';
import { EyeNoneIcon, EyeOpenIcon } from '@radix-ui/react-icons';
import Button from '../button/Button';
import { Input } from './Input';

interface SecretTextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
copy?: boolean;
optionalvalue?: string;
setoptionalvalue?: (value: string) => void;
additionalclass?: string;
optionalValue?: string;
setOptionalValue?: (value: string) => void;
additionalClass?: string;
}

const SecretTextarea = forwardRef<HTMLTextAreaElement, SecretTextareaProps>(function SecretTextarea(
{ className, copy, optionalvalue, setoptionalvalue, additionalclass, defaultValue, ...rest },
{ className, copy, optionalValue, setOptionalValue, additionalClass, defaultValue, ...rest },
ref
) {
const [isSecretVisible, setIsSecretVisible] = useState(false);
const [changedValue, setChangedValue] = useState(defaultValue);

const value = optionalvalue || changedValue;
const updateValue = setoptionalvalue || setChangedValue;
const value = optionalValue || changedValue;
const updateValue = setOptionalValue || setChangedValue;

const toggleSecretVisibility = useCallback(() => setIsSecretVisible(!isSecretVisible), [isSecretVisible]);

Expand All @@ -28,12 +30,12 @@ const SecretTextarea = forwardRef<HTMLTextAreaElement, SecretTextareaProps>(func
};

return (
<div className={`relative flex ${additionalclass ?? ''}`}>
<div className={cn('relative flex', additionalClass)}>
{isSecretVisible ? (
<textarea
ref={ref}
className={classNames(
'border-border-gray bg-bg-black text-text-light-gray focus:border-white focus:ring-white block w-full appearance-none rounded-md border px-3 py-2 text-base placeholder-gray-400 shadow-sm focus:outline-none',
className={cn(
'bg-active-gray border-dark-800 text-text-light-gray w-full appearance-none rounded-md px-3 py-2 text-base placeholder-gray-400 shadow-sm ',
className,
'h-48'
)}
Expand All @@ -42,25 +44,21 @@ const SecretTextarea = forwardRef<HTMLTextAreaElement, SecretTextareaProps>(func
{...rest}
/>
) : (
<input
<Input
type="password"
variant={'flat'}
value={value}
// @ts-expect-error we are mixing input and textarea props
onChange={(e) => updateValue(e.currentTarget.value)}
autoComplete="new-password"
className={classNames(
'border-border-gray bg-active-gray text-text-light-gray focus:border-white focus:ring-white block w-full appearance-none rounded-md border px-3 py-0.6 text-sm placeholder-gray-400 shadow-sm focus:outline-none',
className
)}
{...rest}
/>
)}
<span className="absolute right-1 top-1.5 flex items-center bg-active-gray border-border-gray">
<span onClick={toggleSecretVisibility} className="rounded px-2 py-1 text-sm text-gray-600 cursor-pointer">
{isSecretVisible ? <EyeSlashIcon className="w-4 h-4 ml-1" /> : <EyeIcon className="w-4 h-4 ml-1" />}
</span>
<div className="absolute right-1 top-1 flex items-center bg-active-gray">
<Button variant={'icon'} size={'xs'} onClick={toggleSecretVisibility} className="rounded px-2 py-1 text-sm text-gray-600 cursor-pointer">
{isSecretVisible ? <EyeNoneIcon /> : <EyeOpenIcon />}
</Button>
{copy && <CopyButton text={value as string} />}
</span>
</div>
</div>
);
});
Expand Down
72 changes: 0 additions & 72 deletions packages/webapp/src/components/ui/label/http.tsx

This file was deleted.

Loading

0 comments on commit f0e05be

Please sign in to comment.