Skip to content

Commit

Permalink
refactor(ui): rework dropdown component (#3634)
Browse files Browse the repository at this point in the history
Flipt uses headlessui for dropdown, but it had an issue that it's impossible to specify the direction for the items. It's visible in Console for copy curl/flipt commands. Recently headlessui released v2 which has different api and requires a full refactor of components. I decided to switch to radix-ui and gradually rewrite all components with headlessui. Dropdown is the first one. The new Button component is introduced but it is not migrated.
  • Loading branch information
erka authored Nov 21, 2024
1 parent a59fd2e commit 765849c
Show file tree
Hide file tree
Showing 24 changed files with 2,347 additions and 706 deletions.
2,052 changes: 1,696 additions & 356 deletions ui/package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,29 @@
"@headlessui/react": "^1.7.19",
"@heroicons/react": "^2.1.5",
"@loadable/component": "^5.16.4",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@reduxjs/toolkit": "^2.3.0",
"@tanstack/react-table": "^8.20.5",
"@uiw/codemirror-theme-tokyo-night": "^4.23.6",
"@uiw/react-codemirror": "^4.23.6",
"buffer": "^6.0.3",
"chart.js": "^4.4.4",
"chartjs-adapter-date-fns": "^3.0.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^0.2.1",
"date-fns": "^3.6.0",
"formik": "^2.4.6",
"lucide-react": "^0.460.0",
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.3.1",
"react-helmet": "^6.1.0",
"react-redux": "^9.1.2",
"react-router-dom": "^6.27.0",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"uuid": "^9.0.1",
"yup": "^1.4.0",
"zod": "^3.23.8"
Expand Down
73 changes: 36 additions & 37 deletions ui/src/app/console/Console.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { json } from '@codemirror/lang-json';
import { ArrowPathIcon } from '@heroicons/react/20/solid';
import { CodeBracketIcon, CommandLineIcon } from '@heroicons/react/24/outline';
import { CodeIcon, SquareTerminalIcon, RefreshCwIcon } from 'lucide-react';
import { tokyoNight } from '@uiw/codemirror-theme-tokyo-night';
import CodeMirror from '@uiw/react-codemirror';
import { Form, Formik, useFormikContext } from 'formik';
Expand All @@ -15,7 +14,7 @@ import { selectCurrentNamespace } from '~/app/namespaces/namespacesSlice';
import { selectCurrentRef } from '~/app/refs/refsSlice';
import { ContextEditor } from '~/components/console/ContextEditor';
import EmptyState from '~/components/EmptyState';
import Button from '~/components/forms/buttons/Button';
import { Button } from '~/components/ui/button';
import Combobox from '~/components/forms/Combobox';
import Dropdown from '~/components/forms/Dropdown';
import Input from '~/components/forms/Input';
Expand Down Expand Up @@ -268,17 +267,18 @@ export default function Console() {
id="entityId"
type="text"
/>
<button
<Button
aria-label="New Entity ID"
title="New Entity ID"
className="hidden md:block"
variant="ghost"
size="icon"
onClick={(e) => {
e.preventDefault();
formik.setFieldValue('entityId', uuidv4());
}}
>
<ArrowPathIcon className="m-auto h-5 w-5 justify-center align-middle text-gray-400 transition-opacity duration-300 ease-in-out hover:text-gray-500" />
</button>
<RefreshCwIcon className="text-gray-400" />
</Button>
</div>
</div>
<div className="col-span-3">
Expand All @@ -298,36 +298,35 @@ export default function Console() {
</div>
</div>
</div>
<div className="flex justify-end">
<div className="absolute">
<Dropdown
label="Copy"
actions={[
{
id: 'curl',
disabled: !(formik.dirty && formik.isValid),
label: 'Curl Request',
onClick: () => handleCopyAsCurl(formik.values),
icon: CodeBracketIcon
},
{
id: 'cli',
disabled: !(formik.dirty && formik.isValid),
label: 'Flipt CLI',
onClick: () => handleCopyAsCli(formik.values),
icon: CommandLineIcon
}
]}
/>
<Button
variant="primary"
className="ml-3"
type="submit"
disabled={!(formik.dirty && formik.isValid)}
>
Evaluate
</Button>
</div>
<div className="flex justify-end gap-2">
<Dropdown
disabled={!(formik.dirty && formik.isValid)}
label="Copy"
side="top"
actions={[
{
id: 'curl',
disabled: !(formik.dirty && formik.isValid),
label: 'Curl Request',
onClick: () => handleCopyAsCurl(formik.values),
icon: CodeIcon
},
{
id: 'cli',
disabled: !(formik.dirty && formik.isValid),
label: 'Flipt CLI',
onClick: () => handleCopyAsCli(formik.values),
icon: SquareTerminalIcon
}
]}
/>
<Button
variant="default"
type="submit"
disabled={!(formik.dirty && formik.isValid)}
>
Evaluate
</Button>
</div>
</div>
<ResetOnNamespaceChange namespace={namespace} />
Expand Down
17 changes: 6 additions & 11 deletions ui/src/app/flags/Flag.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
CalendarIcon,
DocumentDuplicateIcon,
TrashIcon
} from '@heroicons/react/24/outline';
import { CalendarIcon, FilesIcon, Trash2Icon } from 'lucide-react';
import 'chartjs-adapter-date-fns';
import { formatDistanceToNowStrict, parseISO } from 'date-fns';
import { useEffect, useState } from 'react';
Expand Down Expand Up @@ -160,22 +156,21 @@ export default function Flag() {
label="Actions"
actions={[
{
id: 'copy',
id: 'flag-copy',
label: 'Copy to Namespace',
disabled: readOnly || namespaces.length < 2,
onClick: () => {
setShowCopyFlagModal(true);
},
icon: DocumentDuplicateIcon
icon: FilesIcon
},
{
id: 'delete',
id: 'flag-delete',
label: 'Delete',
disabled: readOnly,
onClick: () => setShowDeleteFlagModal(true),
icon: TrashIcon,
activeClassName: readOnly ? 'text-red-500' : 'text-red-700',
inActiveClassName: readOnly ? 'text-red-400' : 'text-red-600'
icon: Trash2Icon,
variant: 'destructive'
}
]}
/>
Expand Down
17 changes: 6 additions & 11 deletions ui/src/app/segments/Segment.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
CalendarIcon,
DocumentDuplicateIcon,
TrashIcon
} from '@heroicons/react/24/outline';
import { CalendarIcon, FilesIcon, Trash2Icon } from 'lucide-react';
import { formatDistanceToNowStrict, parseISO } from 'date-fns';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
Expand Down Expand Up @@ -245,20 +241,19 @@ export default function Segment() {
label="Actions"
actions={[
{
id: 'copy',
id: 'segment-copy',
label: 'Copy to Namespace',
disabled: readOnly || namespaces.length < 2,
onClick: () => setShowCopySegmentModal(true),
icon: DocumentDuplicateIcon
icon: FilesIcon
},
{
id: 'delete',
id: 'segement-delete',
label: 'Delete',
disabled: readOnly,
onClick: () => setShowDeleteSegmentModal(true),
icon: TrashIcon,
activeClassName: readOnly ? 'text-red-500' : 'text-red-700',
inActiveClassName: readOnly ? 'text-red-400' : 'text-red-600'
icon: Trash2Icon,
variant: 'destructive'
}
]}
/>
Expand Down
2 changes: 0 additions & 2 deletions ui/src/assets/colors/dark.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@
--red-100: 127 29 29;
--red-50: 127 29 29;

color-scheme: dark;

.shadow {
--tw-shadow: 0 1px 3px 0 rgba(255, 255, 255, 0.1),
0 1px 2px 0 rgba(255, 255, 255, 0.06);
Expand Down
1 change: 1 addition & 0 deletions ui/src/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function NavItem(props: NavItemProps) {
<NavLink
key={name}
to={to}
aria-label={name}
className={({ isActive }) =>
cls(
'flex items-center rounded-md px-2 py-2 text-sm font-medium text-white dark:text-black',
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/flags/forms/FlagForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export default function FlagForm(props: { flag?: IFlag }) {
/>
{!isNew && (
<button
aria-label="Copy"
aria-label="Copy to clipboard"
title="Copy to Clipboard"
className="hidden md:block"
onClick={(e) => {
Expand Down
Loading

0 comments on commit 765849c

Please sign in to comment.