mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-22 08:38:29 +02:00
Theme system refactor (#31)
This commit is contained in:
2
package-lock.json
generated
2
package-lock.json
generated
@@ -81,7 +81,7 @@
|
|||||||
"prettier": "^2.8.4",
|
"prettier": "^2.8.4",
|
||||||
"react-devtools": "^4.27.2",
|
"react-devtools": "^4.27.2",
|
||||||
"tailwindcss": "^3.2.7",
|
"tailwindcss": "^3.2.7",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.4.5",
|
||||||
"vite": "^5.0.0",
|
"vite": "^5.0.0",
|
||||||
"vite-plugin-svgr": "^4.2.0",
|
"vite-plugin-svgr": "^4.2.0",
|
||||||
"vite-plugin-top-level-await": "^1.4.1",
|
"vite-plugin-top-level-await": "^1.4.1",
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
"prettier": "^2.8.4",
|
"prettier": "^2.8.4",
|
||||||
"react-devtools": "^4.27.2",
|
"react-devtools": "^4.27.2",
|
||||||
"tailwindcss": "^3.2.7",
|
"tailwindcss": "^3.2.7",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.4.5",
|
||||||
"vite": "^5.0.0",
|
"vite": "^5.0.0",
|
||||||
"vite-plugin-svgr": "^4.2.0",
|
"vite-plugin-svgr": "^4.2.0",
|
||||||
"vite-plugin-top-level-await": "^1.4.1",
|
"vite-plugin-top-level-await": "^1.4.1",
|
||||||
|
|||||||
@@ -42,13 +42,13 @@
|
|||||||
"icons/release/icon.icns",
|
"icons/release/icon.icns",
|
||||||
"icons/release/icon.ico"
|
"icons/release/icon.ico"
|
||||||
],
|
],
|
||||||
"longDescription": "The best cross-platform visual API client",
|
"longDescription": "A cross-platform desktop app for interacting with REST, GraphQL, and gRPC",
|
||||||
"resources": [
|
"resources": [
|
||||||
"migrations/*",
|
"migrations/*",
|
||||||
"plugins/*",
|
"plugins/*",
|
||||||
"protoc-vendored/include/*"
|
"protoc-vendored/include/*"
|
||||||
],
|
],
|
||||||
"shortDescription": "The best API client",
|
"shortDescription": "Desktop API client",
|
||||||
"targets": [
|
"targets": [
|
||||||
"deb",
|
"deb",
|
||||||
"appimage",
|
"appimage",
|
||||||
|
|||||||
@@ -46,10 +46,10 @@ export function BinaryFileEditor({
|
|||||||
return (
|
return (
|
||||||
<VStack space={2}>
|
<VStack space={2}>
|
||||||
<HStack space={2} alignItems="center">
|
<HStack space={2} alignItems="center">
|
||||||
<Button variant="border" color="gray" size="sm" onClick={handleClick}>
|
<Button variant="border" color="secondary" size="sm" onClick={handleClick}>
|
||||||
Choose File
|
Choose File
|
||||||
</Button>
|
</Button>
|
||||||
<div className="text-xs font-mono truncate rtl pr-3 text-gray-800">
|
<div className="text-xs font-mono truncate rtl pr-3 text-fg">
|
||||||
{/* Special character to insert ltr text in rtl element without making things wonky */}
|
{/* Special character to insert ltr text in rtl element without making things wonky */}
|
||||||
‎
|
‎
|
||||||
{filePath ?? 'Select File'}
|
{filePath ?? 'Select File'}
|
||||||
@@ -64,7 +64,7 @@ export function BinaryFileEditor({
|
|||||||
<HStack space={1.5} justifyContent="center">
|
<HStack space={1.5} justifyContent="center">
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="gray"
|
color="secondary"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => onChangeContentType(mimeType)}
|
onClick={() => onChangeContentType(mimeType)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useMemo, useCallback, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
||||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
|
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||||
@@ -117,8 +117,8 @@ function CommandPaletteItem({
|
|||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'w-full h-xs flex items-center rounded px-1.5 text-gray-600',
|
'w-full h-xs flex items-center rounded px-1.5 text-fg-subtle',
|
||||||
active && 'bg-highlightSecondary text-gray-800',
|
active && 'bg-background-highlight-secondary text-fg',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const CookieDialog = function ({ cookieJarId }: Props) {
|
|||||||
<td className="py-2 select-text cursor-text font-mono font-semibold max-w-0">
|
<td className="py-2 select-text cursor-text font-mono font-semibold max-w-0">
|
||||||
{cookieDomain(c)}
|
{cookieDomain(c)}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 pl-4 select-text cursor-text font-mono text-gray-700 whitespace-nowrap overflow-x-auto max-w-[200px] hide-scrollbars">
|
<td className="py-2 pl-4 select-text cursor-text font-mono text-fg-subtle whitespace-nowrap overflow-x-auto max-w-[200px] hide-scrollbars">
|
||||||
{c.raw_cookie}
|
{c.raw_cookie}
|
||||||
</td>
|
</td>
|
||||||
<td className="max-w-0 w-10">
|
<td className="max-w-0 w-10">
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const DropMarker = memo(
|
|||||||
'relative w-full h-0 overflow-visible pointer-events-none',
|
'relative w-full h-0 overflow-visible pointer-events-none',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="absolute z-50 left-2 right-2 -bottom-[0.1rem] h-[0.2rem] bg-blue-500/50 rounded-full" />
|
<div className="absolute z-50 left-2 right-2 -bottom-[0.1rem] h-[0.2rem] bg-fg-primary rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -11,7 +12,8 @@ export function EmptyStateText({ children, className }: Props) {
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'rounded-lg border border-dashed border-highlight h-full py-2 text-gray-400 flex items-center justify-center',
|
'rounded-lg border border-dashed border-background-highlight',
|
||||||
|
'h-full py-2 text-fg-subtler flex items-center justify-center italic',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
|||||||
size="sm"
|
size="sm"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'text-gray-800 !px-2 truncate',
|
'text-fg !px-2 truncate',
|
||||||
activeEnvironment == null && 'text-opacity-disabled italic',
|
activeEnvironment == null && 'text-fg-subtler italic',
|
||||||
)}
|
)}
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
|||||||
color="custom"
|
color="custom"
|
||||||
title="Add sub environment"
|
title="Add sub environment"
|
||||||
icon="plusCircle"
|
icon="plusCircle"
|
||||||
iconClassName="text-gray-500 group-hover:text-gray-700"
|
iconClassName="text-fg-subtler group-hover:text-fg-subtle"
|
||||||
className="group"
|
className="group"
|
||||||
onClick={handleCreateEnvironment}
|
onClick={handleCreateEnvironment}
|
||||||
/>
|
/>
|
||||||
@@ -97,7 +97,7 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
|||||||
secondSlot={() =>
|
secondSlot={() =>
|
||||||
activeWorkspace != null && (
|
activeWorkspace != null && (
|
||||||
<EnvironmentEditor
|
<EnvironmentEditor
|
||||||
className="pt-2 border-l border-highlight"
|
className="pt-2 border-l border-background-highlight-secondary"
|
||||||
environment={selectedEnvironment}
|
environment={selectedEnvironment}
|
||||||
workspace={activeWorkspace}
|
workspace={activeWorkspace}
|
||||||
/>
|
/>
|
||||||
@@ -175,7 +175,7 @@ const EnvironmentEditor = function ({
|
|||||||
<Heading className="w-full flex items-center gap-1">
|
<Heading className="w-full flex items-center gap-1">
|
||||||
<div>{environment?.name ?? 'Global Variables'}</div>
|
<div>{environment?.name ?? 'Global Variables'}</div>
|
||||||
<IconButton
|
<IconButton
|
||||||
iconClassName="text-gray-600"
|
iconClassName="text-fg-subtler"
|
||||||
size="sm"
|
size="sm"
|
||||||
icon={valueVisibility.value ? 'eye' : 'eyeClosed'}
|
icon={valueVisibility.value ? 'eye' : 'eyeClosed'}
|
||||||
title={valueVisibility.value ? 'Hide Values' : 'Reveal Values'}
|
title={valueVisibility.value ? 'Hide Values' : 'Reveal Values'}
|
||||||
@@ -244,7 +244,7 @@ function SidebarButton({
|
|||||||
size="xs"
|
size="xs"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'w-full',
|
'w-full',
|
||||||
active ? 'text-gray-800 bg-highlightSecondary' : 'text-gray-600 hover:text-gray-700',
|
active ? 'text-fg bg-background-active' : 'text-fg-subtle hover:text-fg',
|
||||||
)}
|
)}
|
||||||
justify="start"
|
justify="start"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export function ExportDataDialog({
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
className="py-1 pl-4 text-gray-700 whitespace-nowrap overflow-x-auto hide-scrollbars"
|
className="py-1 pl-4 text-fg whitespace-nowrap overflow-x-auto hide-scrollbars"
|
||||||
onClick={() => setSelectedWorkspaces((prev) => ({ ...prev, [w.id]: !prev[w.id] }))}
|
onClick={() => setSelectedWorkspaces((prev) => ({ ...prev, [w.id]: !prev[w.id] }))}
|
||||||
>
|
>
|
||||||
{w.name} {w.id === activeWorkspace.id ? '(current workspace)' : ''}
|
{w.name} {w.id === activeWorkspace.id ? '(current workspace)' : ''}
|
||||||
@@ -100,7 +100,7 @@ export function ExportDataDialog({
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<HStack space={2} justifyContent="end">
|
<HStack space={2} justifyContent="end">
|
||||||
<Button className="focus" color="gray" onClick={onHide}>
|
<Button className="focus" variant="border" onClick={onHide}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { getCurrent } from '@tauri-apps/api/webviewWindow';
|
import { getCurrent } from '@tauri-apps/api/webviewWindow';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useCommandPalette } from '../hooks/useCommandPalette';
|
import { useCommandPalette } from '../hooks/useCommandPalette';
|
||||||
import { cookieJarsQueryKey } from '../hooks/useCookieJars';
|
import { cookieJarsQueryKey } from '../hooks/useCookieJars';
|
||||||
@@ -13,6 +13,7 @@ import { httpRequestsQueryKey } from '../hooks/useHttpRequests';
|
|||||||
import { httpResponsesQueryKey } from '../hooks/useHttpResponses';
|
import { httpResponsesQueryKey } from '../hooks/useHttpResponses';
|
||||||
import { keyValueQueryKey } from '../hooks/useKeyValue';
|
import { keyValueQueryKey } from '../hooks/useKeyValue';
|
||||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||||
|
import { useNotificationToast } from '../hooks/useNotificationToast';
|
||||||
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||||
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||||
@@ -24,7 +25,6 @@ import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
|||||||
import type { Model } from '../lib/models';
|
import type { Model } from '../lib/models';
|
||||||
import { modelsEq } from '../lib/models';
|
import { modelsEq } from '../lib/models';
|
||||||
import { setPathname } from '../lib/persistPathname';
|
import { setPathname } from '../lib/persistPathname';
|
||||||
import { useNotificationToast } from '../hooks/useNotificationToast';
|
|
||||||
|
|
||||||
const DEFAULT_FONT_SIZE = 16;
|
const DEFAULT_FONT_SIZE = 16;
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ export function GlobalHooks() {
|
|||||||
useRecentEnvironments();
|
useRecentEnvironments();
|
||||||
useRecentRequests();
|
useRecentRequests();
|
||||||
|
|
||||||
|
// Other useful things
|
||||||
useSyncAppearance();
|
useSyncAppearance();
|
||||||
useSyncWindowTitle();
|
useSyncWindowTitle();
|
||||||
useGlobalCommands();
|
useGlobalCommands();
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
|||||||
<Button
|
<Button
|
||||||
key="introspection"
|
key="introspection"
|
||||||
size="xs"
|
size="xs"
|
||||||
color={error ? 'danger' : 'gray'}
|
color={error ? 'danger' : 'secondary'}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dialog.show({
|
dialog.show({
|
||||||
@@ -105,7 +105,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
|||||||
refetch();
|
refetch();
|
||||||
}}
|
}}
|
||||||
className="ml-auto"
|
className="ml-auto"
|
||||||
color="secondary"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
Try Again
|
Try Again
|
||||||
|
|||||||
@@ -98,9 +98,10 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
<div
|
<div
|
||||||
style={style}
|
style={style}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
'x-theme-responsePane',
|
||||||
'max-h-full h-full grid grid-rows-[minmax(0,1fr)] grid-cols-1',
|
'max-h-full h-full grid grid-rows-[minmax(0,1fr)] grid-cols-1',
|
||||||
'bg-gray-50 dark:bg-gray-100 rounded-md border border-highlight',
|
'bg-background rounded-md border border-background-highlight',
|
||||||
'shadow shadow-gray-100 dark:shadow-gray-0 relative',
|
'shadow relative',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{grpc.go.error ? (
|
{grpc.go.error ? (
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import classNames from 'classnames';
|
|||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { useGrpcConnections } from '../hooks/useGrpcConnections';
|
|
||||||
import { useGrpcEvents } from '../hooks/useGrpcEvents';
|
import { useGrpcEvents } from '../hooks/useGrpcEvents';
|
||||||
|
import { usePinnedGrpcConnection } from '../hooks/usePinnedGrpcConnection';
|
||||||
|
import { useStateWithDeps } from '../hooks/useStateWithDeps';
|
||||||
import type { GrpcEvent, GrpcRequest } from '../lib/models';
|
import type { GrpcEvent, GrpcRequest } from '../lib/models';
|
||||||
|
import { Button } from './core/Button';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
import { JsonAttributeTree } from './core/JsonAttributeTree';
|
import { JsonAttributeTree } from './core/JsonAttributeTree';
|
||||||
import { KeyValueRow, KeyValueRows } from './core/KeyValueRow';
|
import { KeyValueRow, KeyValueRows } from './core/KeyValueRow';
|
||||||
@@ -13,8 +15,6 @@ import { SplitLayout } from './core/SplitLayout';
|
|||||||
import { HStack, VStack } from './core/Stacks';
|
import { HStack, VStack } from './core/Stacks';
|
||||||
import { EmptyStateText } from './EmptyStateText';
|
import { EmptyStateText } from './EmptyStateText';
|
||||||
import { RecentConnectionsDropdown } from './RecentConnectionsDropdown';
|
import { RecentConnectionsDropdown } from './RecentConnectionsDropdown';
|
||||||
import { Button } from './core/Button';
|
|
||||||
import { useStateWithDeps } from '../hooks/useStateWithDeps';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
@@ -31,11 +31,11 @@ interface Props {
|
|||||||
|
|
||||||
export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }: Props) {
|
export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }: Props) {
|
||||||
const [activeEventId, setActiveEventId] = useState<string | null>(null);
|
const [activeEventId, setActiveEventId] = useState<string | null>(null);
|
||||||
const connections = useGrpcConnections(activeRequest.id ?? null);
|
|
||||||
const activeConnection = connections[0] ?? null;
|
|
||||||
const events = useGrpcEvents(activeConnection?.id ?? null);
|
|
||||||
const [showLarge, setShowLarge] = useStateWithDeps<boolean>(false, [activeRequest.id]);
|
const [showLarge, setShowLarge] = useStateWithDeps<boolean>(false, [activeRequest.id]);
|
||||||
const [showingLarge, setShowingLarge] = useState<boolean>(false);
|
const [showingLarge, setShowingLarge] = useState<boolean>(false);
|
||||||
|
const { activeConnection, connections, setPinnedConnectionId } =
|
||||||
|
usePinnedGrpcConnection(activeRequest);
|
||||||
|
const events = useGrpcEvents(activeConnection?.id ?? null);
|
||||||
|
|
||||||
const activeEvent = useMemo(
|
const activeEvent = useMemo(
|
||||||
() => events.find((m) => m.id === activeEventId) ?? null,
|
() => events.find((m) => m.id === activeEventId) ?? null,
|
||||||
@@ -65,15 +65,13 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
|
|||||||
<HStack alignItems="center" space={2}>
|
<HStack alignItems="center" space={2}>
|
||||||
<span>{events.length} messages</span>
|
<span>{events.length} messages</span>
|
||||||
{activeConnection.elapsed === 0 && (
|
{activeConnection.elapsed === 0 && (
|
||||||
<Icon icon="refresh" size="sm" spin className="text-gray-600" />
|
<Icon icon="refresh" size="sm" spin className="text-fg-subtler" />
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
<RecentConnectionsDropdown
|
<RecentConnectionsDropdown
|
||||||
connections={connections}
|
connections={connections}
|
||||||
activeConnection={activeConnection}
|
activeConnection={activeConnection}
|
||||||
onPinned={() => {
|
onPinnedConnectionId={setPinnedConnectionId}
|
||||||
// todo
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
<div className="overflow-y-auto h-full">
|
<div className="overflow-y-auto h-full">
|
||||||
@@ -107,7 +105,7 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
|
|||||||
Message {activeEvent.eventType === 'client_message' ? 'Sent' : 'Received'}
|
Message {activeEvent.eventType === 'client_message' ? 'Sent' : 'Received'}
|
||||||
</div>
|
</div>
|
||||||
{!showLarge && activeEvent.content.length > 1000 * 1000 ? (
|
{!showLarge && activeEvent.content.length > 1000 * 1000 ? (
|
||||||
<VStack space={2} className="text-sm italic text-gray-500">
|
<VStack space={2} className="text-sm italic text-fg-subtler">
|
||||||
Message previews larger than 1MB are hidden
|
Message previews larger than 1MB are hidden
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
@@ -119,7 +117,7 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
|
|||||||
}, 500);
|
}, 500);
|
||||||
}}
|
}}
|
||||||
isLoading={showingLarge}
|
isLoading={showingLarge}
|
||||||
color="gray"
|
color="secondary"
|
||||||
variant="border"
|
variant="border"
|
||||||
size="xs"
|
size="xs"
|
||||||
>
|
>
|
||||||
@@ -138,7 +136,7 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
|
|||||||
{activeEvent.content}
|
{activeEvent.content}
|
||||||
</div>
|
</div>
|
||||||
{activeEvent.error && (
|
{activeEvent.error && (
|
||||||
<div className="select-text cursor-text text-xs font-mono py-1 text-orange-700">
|
<div className="select-text cursor-text text-xs font-mono py-1 text-fg-warning">
|
||||||
{activeEvent.error}
|
{activeEvent.error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -183,21 +181,21 @@ function EventRow({
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
'w-full grid grid-cols-[auto_minmax(0,3fr)_auto] gap-2 items-center text-left',
|
'w-full grid grid-cols-[auto_minmax(0,3fr)_auto] gap-2 items-center text-left',
|
||||||
'px-1.5 py-1 font-mono cursor-default group focus:outline-none rounded',
|
'px-1.5 py-1 font-mono cursor-default group focus:outline-none rounded',
|
||||||
isActive && '!bg-highlight text-gray-900',
|
isActive && '!bg-background-highlight-secondary !text-fg',
|
||||||
'text-gray-800 hover:text-gray-900',
|
'text-fg-subtle hover:text-fg',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
className={
|
className={
|
||||||
eventType === 'server_message'
|
eventType === 'server_message'
|
||||||
? 'text-blue-600'
|
? 'text-fg-info'
|
||||||
: eventType === 'client_message'
|
: eventType === 'client_message'
|
||||||
? 'text-violet-600'
|
? 'text-fg-primary'
|
||||||
: eventType === 'error' || (status != null && status > 0)
|
: eventType === 'error' || (status != null && status > 0)
|
||||||
? 'text-orange-600'
|
? 'text-fg-danger'
|
||||||
: eventType === 'connection_end'
|
: eventType === 'connection_end'
|
||||||
? 'text-green-600'
|
? 'text-fg-success'
|
||||||
: 'text-gray-700'
|
: 'text-fg-subtle'
|
||||||
}
|
}
|
||||||
title={
|
title={
|
||||||
eventType === 'server_message'
|
eventType === 'server_message'
|
||||||
@@ -224,7 +222,7 @@ function EventRow({
|
|||||||
/>
|
/>
|
||||||
<div className={classNames('w-full truncate text-2xs')}>
|
<div className={classNames('w-full truncate text-2xs')}>
|
||||||
{content.slice(0, 1000)}
|
{content.slice(0, 1000)}
|
||||||
{error && <span className="text-orange-600"> ({error})</span>}
|
{error && <span className="text-fg-warning"> ({error})</span>}
|
||||||
</div>
|
</div>
|
||||||
<div className={classNames('opacity-50 text-2xs')}>
|
<div className={classNames('opacity-50 text-2xs')}>
|
||||||
{format(createdAt + 'Z', 'HH:mm:ss.SSS')}
|
{format(createdAt + 'Z', 'HH:mm:ss.SSS')}
|
||||||
|
|||||||
@@ -199,14 +199,14 @@ export function GrpcConnectionSetupPane({
|
|||||||
label: 'Refresh',
|
label: 'Refresh',
|
||||||
type: 'default',
|
type: 'default',
|
||||||
key: 'custom',
|
key: 'custom',
|
||||||
leftSlot: <Icon className="text-gray-600" size="sm" icon="refresh" />,
|
leftSlot: <Icon className="text-fg-subtler" size="sm" icon="refresh" />,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="border"
|
variant="border"
|
||||||
rightSlot={<Icon className="text-gray-600" size="sm" icon="chevronDown" />}
|
rightSlot={<Icon className="text-fg-subtler" size="sm" icon="chevronDown" />}
|
||||||
disabled={isStreaming || services == null}
|
disabled={isStreaming || services == null}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'font-mono text-xs min-w-[5rem] !ring-0',
|
'font-mono text-xs min-w-[5rem] !ring-0',
|
||||||
@@ -221,14 +221,14 @@ export function GrpcConnectionSetupPane({
|
|||||||
{isStreaming && (
|
{isStreaming && (
|
||||||
<>
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
className="border border-highlight"
|
className="border border-background-highlight-secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
title="Cancel"
|
title="Cancel"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
icon="x"
|
icon="x"
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
className="border border-highlight"
|
className="border border-background-highlight-secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
title="Commit"
|
title="Commit"
|
||||||
onClick={onCommit}
|
onClick={onCommit}
|
||||||
@@ -237,7 +237,7 @@ export function GrpcConnectionSetupPane({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<IconButton
|
<IconButton
|
||||||
className="border border-highlight"
|
className="border border-background-highlight-secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
title={isStreaming ? 'Connect' : 'Send'}
|
title={isStreaming ? 'Connect' : 'Send'}
|
||||||
hotkeyAction="grpc_request.send"
|
hotkeyAction="grpc_request.send"
|
||||||
@@ -247,7 +247,7 @@ export function GrpcConnectionSetupPane({
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<IconButton
|
<IconButton
|
||||||
className="border border-highlight"
|
className="border border-background-highlight-secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
title={methodType === 'unary' ? 'Send' : 'Connect'}
|
title={methodType === 'unary' ? 'Send' : 'Connect'}
|
||||||
hotkeyAction="grpc_request.send"
|
hotkeyAction="grpc_request.send"
|
||||||
@@ -275,7 +275,6 @@ export function GrpcConnectionSetupPane({
|
|||||||
<GrpcEditor
|
<GrpcEditor
|
||||||
onChange={handleChangeMessage}
|
onChange={handleChangeMessage}
|
||||||
services={services}
|
services={services}
|
||||||
className="bg-gray-50"
|
|
||||||
reflectionError={reflectionError}
|
reflectionError={reflectionError}
|
||||||
reflectionLoading={reflectionLoading}
|
reflectionLoading={reflectionLoading}
|
||||||
request={activeRequest}
|
request={activeRequest}
|
||||||
|
|||||||
@@ -133,12 +133,12 @@ export function GrpcEditor({
|
|||||||
size="xs"
|
size="xs"
|
||||||
color={
|
color={
|
||||||
reflectionLoading
|
reflectionLoading
|
||||||
? 'gray'
|
|
||||||
: reflectionUnavailable
|
|
||||||
? 'secondary'
|
? 'secondary'
|
||||||
|
: reflectionUnavailable
|
||||||
|
? 'info'
|
||||||
: reflectionError
|
: reflectionError
|
||||||
? 'danger'
|
? 'danger'
|
||||||
: 'gray'
|
: 'secondary'
|
||||||
}
|
}
|
||||||
isLoading={reflectionLoading}
|
isLoading={reflectionLoading}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export function GrpcProtoSelection({ requestId }: Props) {
|
|||||||
<Button
|
<Button
|
||||||
isLoading={grpc.reflect.isFetching}
|
isLoading={grpc.reflect.isFetching}
|
||||||
disabled={grpc.reflect.isFetching}
|
disabled={grpc.reflect.isFetching}
|
||||||
color="gray"
|
color="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => grpc.reflect.refetch()}
|
onClick={() => grpc.reflect.refetch()}
|
||||||
>
|
>
|
||||||
@@ -106,7 +106,7 @@ export function GrpcProtoSelection({ requestId }: Props) {
|
|||||||
<table className="w-full divide-y">
|
<table className="w-full divide-y">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="text-gray-600">
|
<th className="text-fg-subtler">
|
||||||
<span className="font-mono text-sm">*.proto</span> Files
|
<span className="font-mono text-sm">*.proto</span> Files
|
||||||
</th>
|
</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Button } from './core/Button';
|
|
||||||
import { useClipboardText } from '../hooks/useClipboardText';
|
import { useClipboardText } from '../hooks/useClipboardText';
|
||||||
import { useImportCurl } from '../hooks/useImportCurl';
|
import { useImportCurl } from '../hooks/useImportCurl';
|
||||||
|
import { Button } from './core/Button';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
import { motion } from 'framer-motion';
|
|
||||||
|
|
||||||
export function ImportCurlButton() {
|
export function ImportCurlButton() {
|
||||||
const [clipboardText] = useClipboardText();
|
const [clipboardText] = useClipboardText();
|
||||||
@@ -23,7 +23,7 @@ export function ImportCurlButton() {
|
|||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
variant="border"
|
variant="border"
|
||||||
color="secondary"
|
color="primary"
|
||||||
leftSlot={<Icon icon="paste" size="sm" />}
|
leftSlot={<Icon icon="paste" size="sm" />}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ export function Overlay({
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'absolute inset-0',
|
'absolute inset-0',
|
||||||
variant === 'default' && 'bg-gray-600/30 dark:bg-black/30 backdrop-blur-sm',
|
variant === 'default' && 'bg-background-backdrop backdrop-blur-sm',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="bg-red-100">{children}</div>
|
{children}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -11,12 +11,17 @@ import { HStack } from './core/Stacks';
|
|||||||
interface Props {
|
interface Props {
|
||||||
connections: GrpcConnection[];
|
connections: GrpcConnection[];
|
||||||
activeConnection: GrpcConnection;
|
activeConnection: GrpcConnection;
|
||||||
onPinned: (r: GrpcConnection) => void;
|
onPinnedConnectionId: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RecentConnectionsDropdown({ activeConnection, connections, onPinned }: Props) {
|
export function RecentConnectionsDropdown({
|
||||||
|
activeConnection,
|
||||||
|
connections,
|
||||||
|
onPinnedConnectionId,
|
||||||
|
}: Props) {
|
||||||
const deleteConnection = useDeleteGrpcConnection(activeConnection?.id ?? null);
|
const deleteConnection = useDeleteGrpcConnection(activeConnection?.id ?? null);
|
||||||
const deleteAllConnections = useDeleteGrpcConnections(activeConnection?.requestId);
|
const deleteAllConnections = useDeleteGrpcConnections(activeConnection?.requestId);
|
||||||
|
const latestConnectionId = connections[0]?.id ?? 'n/a';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -44,13 +49,13 @@ export function RecentConnectionsDropdown({ activeConnection, connections, onPin
|
|||||||
</HStack>
|
</HStack>
|
||||||
),
|
),
|
||||||
leftSlot: activeConnection?.id === c.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
leftSlot: activeConnection?.id === c.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||||
onSelect: () => onPinned(c),
|
onSelect: () => onPinnedConnectionId(c.id),
|
||||||
})),
|
})),
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
title="Show connection history"
|
title="Show connection history"
|
||||||
icon="chevronDown"
|
icon={activeConnection?.id === latestConnectionId ? 'chevronDown' : 'pin'}
|
||||||
className="ml-auto"
|
className="ml-auto"
|
||||||
size="sm"
|
size="sm"
|
||||||
iconSize="md"
|
iconSize="md"
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
|
|||||||
hotkeyAction="request_switcher.toggle"
|
hotkeyAction="request_switcher.toggle"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'text-gray-800 text-sm truncate pointer-events-auto',
|
'text-fg text-sm truncate pointer-events-auto',
|
||||||
activeRequest === null && 'text-opacity-disabled italic',
|
activeRequest === null && 'text-fg-subtler italic',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{fallbackRequestName(activeRequest)}
|
{fallbackRequestName(activeRequest)}
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
import { useDeleteHttpResponse } from '../hooks/useDeleteHttpResponse';
|
import { useDeleteHttpResponse } from '../hooks/useDeleteHttpResponse';
|
||||||
import { useDeleteHttpResponses } from '../hooks/useDeleteHttpResponses';
|
import { useDeleteHttpResponses } from '../hooks/useDeleteHttpResponses';
|
||||||
import type { HttpResponse } from '../lib/models';
|
import type { HttpResponse } from '../lib/models';
|
||||||
import { Dropdown } from './core/Dropdown';
|
|
||||||
import { pluralize } from '../lib/pluralize';
|
import { pluralize } from '../lib/pluralize';
|
||||||
import { HStack } from './core/Stacks';
|
import { Dropdown } from './core/Dropdown';
|
||||||
import { StatusTag } from './core/StatusTag';
|
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
|
import { HStack } from './core/Stacks';
|
||||||
|
import { StatusTag } from './core/StatusTag';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
responses: HttpResponse[];
|
responses: HttpResponse[];
|
||||||
activeResponse: HttpResponse;
|
activeResponse: HttpResponse;
|
||||||
onPinnedResponse: (r: HttpResponse) => void;
|
onPinnedResponseId: (id: string) => void;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RecentResponsesDropdown = function ResponsePane({
|
export const RecentResponsesDropdown = function ResponsePane({
|
||||||
activeResponse,
|
activeResponse,
|
||||||
responses,
|
responses,
|
||||||
onPinnedResponse,
|
onPinnedResponseId,
|
||||||
|
className,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const deleteResponse = useDeleteHttpResponse(activeResponse?.id ?? null);
|
const deleteResponse = useDeleteHttpResponse(activeResponse?.id ?? null);
|
||||||
const deleteAllResponses = useDeleteHttpResponses(activeResponse?.requestId);
|
const deleteAllResponses = useDeleteHttpResponses(activeResponse?.requestId);
|
||||||
|
const latestResponseId = responses[0]?.id ?? 'n/a';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -44,18 +48,19 @@ export const RecentResponsesDropdown = function ResponsePane({
|
|||||||
label: (
|
label: (
|
||||||
<HStack space={2} alignItems="center">
|
<HStack space={2} alignItems="center">
|
||||||
<StatusTag className="text-xs" response={r} />
|
<StatusTag className="text-xs" response={r} />
|
||||||
<span>•</span> <span className="font-mono text-xs">{r.elapsed}ms</span>
|
<span>→</span>{' '}
|
||||||
|
<span className="font-mono text-xs">{r.elapsed >= 0 ? `${r.elapsed}ms` : 'n/a'}</span>
|
||||||
</HStack>
|
</HStack>
|
||||||
),
|
),
|
||||||
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||||
onSelect: () => onPinnedResponse(r),
|
onSelect: () => onPinnedResponseId(r.id),
|
||||||
})),
|
})),
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
title="Show response history"
|
title="Show response history"
|
||||||
icon="chevronDown"
|
icon={activeResponse?.id === latestResponseId ? 'chevronDown' : 'pin'}
|
||||||
className="ml-auto"
|
className={classNames(className, 'm-0.5')}
|
||||||
size="sm"
|
size="sm"
|
||||||
iconSize="md"
|
iconSize="md"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { usePrompt } from '../hooks/usePrompt';
|
import { usePrompt } from '../hooks/usePrompt';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
@@ -57,7 +58,7 @@ export const RequestMethodDropdown = memo(function RequestMethodDropdown({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RadioDropdown value={method} items={radioItems} extraItems={extraItems} onChange={onChange}>
|
<RadioDropdown value={method} items={radioItems} extraItems={extraItems} onChange={onChange}>
|
||||||
<Button size="xs" className={className}>
|
<Button size="xs" className={classNames(className, 'text-fg-subtle hover:text-fg')}>
|
||||||
{method.toUpperCase()}
|
{method.toUpperCase()}
|
||||||
</Button>
|
</Button>
|
||||||
</RadioDropdown>
|
</RadioDropdown>
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import type { CSSProperties } from 'react';
|
|||||||
import React, { memo, useCallback, useMemo, useState } from 'react';
|
import React, { memo, useCallback, useMemo, useState } from 'react';
|
||||||
import { createGlobalState } from 'react-use';
|
import { createGlobalState } from 'react-use';
|
||||||
import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
|
import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
|
||||||
|
import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders';
|
||||||
|
import { useImportCurl } from '../hooks/useImportCurl';
|
||||||
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
|
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
|
||||||
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
||||||
|
import { useRequests } from '../hooks/useRequests';
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||||
import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders';
|
|
||||||
import { useSendRequest } from '../hooks/useSendRequest';
|
import { useSendRequest } from '../hooks/useSendRequest';
|
||||||
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
|
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
|
||||||
import { tryFormatJson } from '../lib/formatters';
|
import { tryFormatJson } from '../lib/formatters';
|
||||||
@@ -29,6 +31,7 @@ import { BearerAuth } from './BearerAuth';
|
|||||||
import { BinaryFileEditor } from './BinaryFileEditor';
|
import { BinaryFileEditor } from './BinaryFileEditor';
|
||||||
import { CountBadge } from './core/CountBadge';
|
import { CountBadge } from './core/CountBadge';
|
||||||
import { Editor } from './core/Editor';
|
import { Editor } from './core/Editor';
|
||||||
|
import type { GenericCompletionOption } from './core/Editor/genericCompletion';
|
||||||
import type { TabItem } from './core/Tabs/Tabs';
|
import type { TabItem } from './core/Tabs/Tabs';
|
||||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||||
import { EmptyStateText } from './EmptyStateText';
|
import { EmptyStateText } from './EmptyStateText';
|
||||||
@@ -38,9 +41,6 @@ import { GraphQLEditor } from './GraphQLEditor';
|
|||||||
import { HeadersEditor } from './HeadersEditor';
|
import { HeadersEditor } from './HeadersEditor';
|
||||||
import { UrlBar } from './UrlBar';
|
import { UrlBar } from './UrlBar';
|
||||||
import { UrlParametersEditor } from './UrlParameterEditor';
|
import { UrlParametersEditor } from './UrlParameterEditor';
|
||||||
import { useImportCurl } from '../hooks/useImportCurl';
|
|
||||||
import { useRequests } from '../hooks/useRequests';
|
|
||||||
import type { GenericCompletionOption } from './core/Editor/genericCompletion';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
style: CSSProperties;
|
style: CSSProperties;
|
||||||
@@ -317,7 +317,6 @@ export const RequestPane = memo(function RequestPane({
|
|||||||
useTemplating
|
useTemplating
|
||||||
autocompleteVariables
|
autocompleteVariables
|
||||||
placeholder="..."
|
placeholder="..."
|
||||||
className="!bg-gray-50"
|
|
||||||
heightMode={fullHeight ? 'full' : 'auto'}
|
heightMode={fullHeight ? 'full' : 'auto'}
|
||||||
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
||||||
contentType="application/json"
|
contentType="application/json"
|
||||||
@@ -330,7 +329,6 @@ export const RequestPane = memo(function RequestPane({
|
|||||||
useTemplating
|
useTemplating
|
||||||
autocompleteVariables
|
autocompleteVariables
|
||||||
placeholder="..."
|
placeholder="..."
|
||||||
className="!bg-gray-50"
|
|
||||||
heightMode={fullHeight ? 'full' : 'auto'}
|
heightMode={fullHeight ? 'full' : 'auto'}
|
||||||
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
||||||
contentType="text/xml"
|
contentType="text/xml"
|
||||||
@@ -340,7 +338,6 @@ export const RequestPane = memo(function RequestPane({
|
|||||||
<GraphQLEditor
|
<GraphQLEditor
|
||||||
forceUpdateKey={forceUpdateKey}
|
forceUpdateKey={forceUpdateKey}
|
||||||
baseRequest={activeRequest}
|
baseRequest={activeRequest}
|
||||||
className="!bg-gray-50"
|
|
||||||
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
||||||
onChange={handleBodyTextChange}
|
onChange={handleBodyTextChange}
|
||||||
/>
|
/>
|
||||||
@@ -370,7 +367,6 @@ export const RequestPane = memo(function RequestPane({
|
|||||||
useTemplating
|
useTemplating
|
||||||
autocompleteVariables
|
autocompleteVariables
|
||||||
placeholder="..."
|
placeholder="..."
|
||||||
className="!bg-gray-50"
|
|
||||||
heightMode={fullHeight ? 'full' : 'auto'}
|
heightMode={fullHeight ? 'full' : 'auto'}
|
||||||
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
||||||
onChange={handleBodyTextChange}
|
onChange={handleBodyTextChange}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function ResizeHandle({
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'group z-10 flex',
|
'group z-10 flex',
|
||||||
// 'bg-blue-100/10', // For debugging
|
// 'bg-fg-info', // For debugging
|
||||||
vertical ? 'w-full h-3 cursor-row-resize' : 'h-full w-3 cursor-col-resize',
|
vertical ? 'w-full h-3 cursor-row-resize' : 'h-full w-3 cursor-col-resize',
|
||||||
justify === 'center' && 'justify-center',
|
justify === 'center' && 'justify-center',
|
||||||
justify === 'end' && 'justify-end',
|
justify === 'end' && 'justify-end',
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import classNames from 'classnames';
|
|||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { createGlobalState } from 'react-use';
|
import { createGlobalState } from 'react-use';
|
||||||
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
|
||||||
import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders';
|
import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders';
|
||||||
|
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
||||||
import { useResponseViewMode } from '../hooks/useResponseViewMode';
|
import { useResponseViewMode } from '../hooks/useResponseViewMode';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { isResponseLoading } from '../lib/models';
|
import { isResponseLoading } from '../lib/models';
|
||||||
@@ -34,7 +34,7 @@ interface Props {
|
|||||||
const useActiveTab = createGlobalState<string>('body');
|
const useActiveTab = createGlobalState<string>('body');
|
||||||
|
|
||||||
export const ResponsePane = memo(function ResponsePane({ style, className, activeRequest }: Props) {
|
export const ResponsePane = memo(function ResponsePane({ style, className, activeRequest }: Props) {
|
||||||
const { activeResponse, setPinnedResponse, responses } = usePinnedHttpResponse(activeRequest);
|
const { activeResponse, setPinnedResponseId, responses } = usePinnedHttpResponse(activeRequest);
|
||||||
const [viewMode, setViewMode] = useResponseViewMode(activeResponse?.requestId);
|
const [viewMode, setViewMode] = useResponseViewMode(activeResponse?.requestId);
|
||||||
const [activeTab, setActiveTab] = useActiveTab();
|
const [activeTab, setActiveTab] = useActiveTab();
|
||||||
const contentType = useContentTypeFromHeaders(activeResponse?.headers ?? null);
|
const contentType = useContentTypeFromHeaders(activeResponse?.headers ?? null);
|
||||||
@@ -73,9 +73,10 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
|||||||
style={style}
|
style={style}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
|
'x-theme-responsePane',
|
||||||
'max-h-full h-full',
|
'max-h-full h-full',
|
||||||
'bg-gray-50 dark:bg-gray-100 rounded-md border border-highlight',
|
'bg-background rounded-md border border-background-highlight',
|
||||||
'shadow shadow-gray-100 dark:shadow-gray-0 relative',
|
'shadow relative',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{activeResponse == null ? (
|
{activeResponse == null ? (
|
||||||
@@ -91,39 +92,41 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
|||||||
<HStack
|
<HStack
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'text-gray-700 text-sm w-full flex-shrink-0',
|
'text-fg-subtle text-sm w-full flex-shrink-0',
|
||||||
// Remove a bit of space because the tabs have lots too
|
// Remove a bit of space because the tabs have lots too
|
||||||
'-mb-1.5',
|
'-mb-1.5',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{activeResponse && (
|
{activeResponse && (
|
||||||
<HStack alignItems="center" className="w-full">
|
<HStack
|
||||||
<div className="whitespace-nowrap px-3">
|
space={2}
|
||||||
<HStack space={2}>
|
alignItems="center"
|
||||||
<StatusTag showReason response={activeResponse} />
|
className="whitespace-nowrap w-full pl-3 overflow-x-auto font-mono text-sm"
|
||||||
{activeResponse.elapsed > 0 && (
|
>
|
||||||
<>
|
<StatusTag showReason response={activeResponse} />
|
||||||
<span>•</span>
|
{activeResponse.elapsed > 0 && (
|
||||||
<DurationTag
|
<>
|
||||||
headers={activeResponse.elapsedHeaders}
|
<span>•</span>
|
||||||
total={activeResponse.elapsed}
|
<DurationTag
|
||||||
/>
|
headers={activeResponse.elapsedHeaders}
|
||||||
</>
|
total={activeResponse.elapsed}
|
||||||
)}
|
/>
|
||||||
{!!activeResponse.contentLength && (
|
</>
|
||||||
<>
|
)}
|
||||||
<span>•</span>
|
{!!activeResponse.contentLength && (
|
||||||
<SizeTag contentLength={activeResponse.contentLength} />
|
<>
|
||||||
</>
|
<span>•</span>
|
||||||
)}
|
<SizeTag contentLength={activeResponse.contentLength} />
|
||||||
</HStack>
|
</>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<RecentResponsesDropdown
|
<div className="ml-auto">
|
||||||
responses={responses}
|
<RecentResponsesDropdown
|
||||||
activeResponse={activeResponse}
|
responses={responses}
|
||||||
onPinnedResponse={setPinnedResponse}
|
activeResponse={activeResponse}
|
||||||
/>
|
onPinnedResponseId={setPinnedResponseId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</HStack>
|
</HStack>
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
@@ -138,7 +141,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
|||||||
onChangeValue={setActiveTab}
|
onChangeValue={setActiveTab}
|
||||||
label="Response"
|
label="Response"
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
className="ml-3 mr-1"
|
className="ml-3 mr-3 mb-3"
|
||||||
tabListClassName="mt-1.5"
|
tabListClassName="mt-1.5"
|
||||||
>
|
>
|
||||||
<TabContent value="headers">
|
<TabContent value="headers">
|
||||||
@@ -146,13 +149,13 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
|||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value="body">
|
<TabContent value="body">
|
||||||
{!activeResponse.contentLength ? (
|
{!activeResponse.contentLength ? (
|
||||||
<EmptyStateText>Empty Body</EmptyStateText>
|
<div className="pb-2 h-full">
|
||||||
|
<EmptyStateText>Empty Body</EmptyStateText>
|
||||||
|
</div>
|
||||||
) : contentType?.startsWith('image') ? (
|
) : contentType?.startsWith('image') ? (
|
||||||
<ImageViewer className="pb-2" response={activeResponse} />
|
<ImageViewer className="pb-2" response={activeResponse} />
|
||||||
) : activeResponse.contentLength > 2 * 1000 * 1000 ? (
|
) : activeResponse.contentLength > 2 * 1000 * 1000 ? (
|
||||||
<div className="text-sm italic text-gray-500">
|
<EmptyStateText>Cannot preview text responses larger than 2MB</EmptyStateText>
|
||||||
Cannot preview text responses larger than 2MB
|
|
||||||
</div>
|
|
||||||
) : viewMode === 'pretty' && contentType?.includes('html') ? (
|
) : viewMode === 'pretty' && contentType?.includes('html') ? (
|
||||||
<WebPageViewer response={activeResponse} />
|
<WebPageViewer response={activeResponse} />
|
||||||
) : contentType?.match(/csv|tab-separated/) ? (
|
) : contentType?.match(/csv|tab-separated/) ? (
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default function RouteError() {
|
|||||||
>
|
>
|
||||||
Go Home
|
Go Home
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="secondary" onClick={() => window.location.reload()}>
|
<Button color="info" onClick={() => window.location.reload()}>
|
||||||
Refresh
|
Refresh
|
||||||
</Button>
|
</Button>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export const SettingsDialog = () => {
|
|||||||
|
|
||||||
<Heading size={2}>
|
<Heading size={2}>
|
||||||
Workspace{' '}
|
Workspace{' '}
|
||||||
<div className="inline-block ml-1 bg-gray-500 dark:bg-gray-300 px-2 py-0.5 text-sm rounded text-white dark:text-gray-900">
|
<div className="inline-block ml-1 bg-background-highlight px-2 py-0.5 text-sm rounded text-fg">
|
||||||
{workspace.name}
|
{workspace.name}
|
||||||
</div>
|
</div>
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
|||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
|
import { useCopyAsCurl } from '../hooks/useCopyAsCurl';
|
||||||
import { useCreateDropdownItems } from '../hooks/useCreateDropdownItems';
|
import { useCreateDropdownItems } from '../hooks/useCreateDropdownItems';
|
||||||
import { useDeleteFolder } from '../hooks/useDeleteFolder';
|
import { useDeleteFolder } from '../hooks/useDeleteFolder';
|
||||||
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
||||||
@@ -40,7 +41,6 @@ import { InlineCode } from './core/InlineCode';
|
|||||||
import { VStack } from './core/Stacks';
|
import { VStack } from './core/Stacks';
|
||||||
import { StatusTag } from './core/StatusTag';
|
import { StatusTag } from './core/StatusTag';
|
||||||
import { DropMarker } from './DropMarker';
|
import { DropMarker } from './DropMarker';
|
||||||
import { useCopyAsCurl } from '../hooks/useCopyAsCurl';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -468,6 +468,47 @@ export function Sidebar({ className }: Props) {
|
|||||||
handleEnd={handleEnd}
|
handleEnd={handleEnd}
|
||||||
handleDragStart={handleDragStart}
|
handleDragStart={handleDragStart}
|
||||||
/>
|
/>
|
||||||
|
{/*<div className="p-2 flex flex-col gap-1">*/}
|
||||||
|
{/* <div className="flex flex-wrap gap-1">*/}
|
||||||
|
{/* <Button color="primary">Primary</Button>*/}
|
||||||
|
{/* <Button color="secondary">Secondary</Button>*/}
|
||||||
|
{/* <Button color="info">Info</Button>*/}
|
||||||
|
{/* <Button color="success">Success</Button>*/}
|
||||||
|
{/* <Button color="warning">Warning</Button>*/}
|
||||||
|
{/* <Button color="danger">Danger</Button>*/}
|
||||||
|
{/* <Button color="default">Default</Button>*/}
|
||||||
|
{/* </div>*/}
|
||||||
|
{/* <div className="flex flex-wrap gap-1">*/}
|
||||||
|
{/* <Button variant="border" color="primary">*/}
|
||||||
|
{/* Primary*/}
|
||||||
|
{/* </Button>*/}
|
||||||
|
{/* <Button variant="border" color="secondary">*/}
|
||||||
|
{/* Secondary*/}
|
||||||
|
{/* </Button>*/}
|
||||||
|
{/* <Button variant="border" color="info">*/}
|
||||||
|
{/* Info*/}
|
||||||
|
{/* </Button>*/}
|
||||||
|
{/* <Button variant="border" color="success">*/}
|
||||||
|
{/* Success*/}
|
||||||
|
{/* </Button>*/}
|
||||||
|
{/* <Button variant="border" color="warning">*/}
|
||||||
|
{/* Warning*/}
|
||||||
|
{/* </Button>*/}
|
||||||
|
{/* <Button variant="border" color="danger">*/}
|
||||||
|
{/* Danger*/}
|
||||||
|
{/* </Button>*/}
|
||||||
|
{/* <Button variant="border" color="default">*/}
|
||||||
|
{/* Default*/}
|
||||||
|
{/* </Button>*/}
|
||||||
|
{/* </div>*/}
|
||||||
|
{/* <div className="flex flex-col gap-1">*/}
|
||||||
|
{/* <Banner color="primary">Primary banner</Banner>*/}
|
||||||
|
{/* <Banner color="secondary">Secondary banner</Banner>*/}
|
||||||
|
{/* <Banner color="danger">Danger banner</Banner>*/}
|
||||||
|
{/* <Banner color="warning">Warning banner</Banner>*/}
|
||||||
|
{/* <Banner color="success">Success banner</Banner>*/}
|
||||||
|
{/* </div>*/}
|
||||||
|
{/*</div>*/}
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -510,60 +551,66 @@ function SidebarItems({
|
|||||||
aria-orientation="vertical"
|
aria-orientation="vertical"
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
tree.depth > 0 && 'border-l border-highlight',
|
tree.depth > 0 && 'border-l border-background-highlight-secondary',
|
||||||
tree.depth === 0 && 'ml-0',
|
tree.depth === 0 && 'ml-0',
|
||||||
tree.depth >= 1 && 'ml-[1.2em]',
|
tree.depth >= 1 && 'ml-[1.2em]',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{tree.children.map((child, i) => (
|
{tree.children.map((child, i) => {
|
||||||
<Fragment key={child.item.id}>
|
const selected = selectedId === child.item.id;
|
||||||
{hoveredIndex === i && hoveredTree?.item.id === tree.item.id && <DropMarker />}
|
return (
|
||||||
<DraggableSidebarItem
|
<Fragment key={child.item.id}>
|
||||||
draggable
|
{hoveredIndex === i && hoveredTree?.item.id === tree.item.id && <DropMarker />}
|
||||||
selected={selectedId === child.item.id}
|
<DraggableSidebarItem
|
||||||
itemId={child.item.id}
|
draggable
|
||||||
itemName={child.item.name}
|
selected={selected}
|
||||||
itemFallbackName={
|
itemId={child.item.id}
|
||||||
child.item.model === 'http_request' || child.item.model === 'grpc_request'
|
itemName={child.item.name}
|
||||||
? fallbackRequestName(child.item)
|
itemFallbackName={
|
||||||
: 'New Folder'
|
child.item.model === 'http_request' || child.item.model === 'grpc_request'
|
||||||
}
|
? fallbackRequestName(child.item)
|
||||||
itemModel={child.item.model}
|
: 'New Folder'
|
||||||
itemPrefix={
|
}
|
||||||
(child.item.model === 'http_request' || child.item.model === 'grpc_request') && (
|
itemModel={child.item.model}
|
||||||
<HttpMethodTag request={child.item} />
|
itemPrefix={
|
||||||
)
|
(child.item.model === 'http_request' || child.item.model === 'grpc_request') && (
|
||||||
}
|
<HttpMethodTag
|
||||||
onMove={handleMove}
|
request={child.item}
|
||||||
onEnd={handleEnd}
|
className={classNames(!selected && 'text-fg-subtler')}
|
||||||
onSelect={onSelect}
|
/>
|
||||||
onDragStart={handleDragStart}
|
)
|
||||||
useProminentStyles={focused}
|
}
|
||||||
isCollapsed={isCollapsed}
|
onMove={handleMove}
|
||||||
child={child}
|
onEnd={handleEnd}
|
||||||
>
|
onSelect={onSelect}
|
||||||
{child.item.model === 'folder' &&
|
onDragStart={handleDragStart}
|
||||||
!isCollapsed(child.item.id) &&
|
useProminentStyles={focused}
|
||||||
draggingId !== child.item.id && (
|
isCollapsed={isCollapsed}
|
||||||
<SidebarItems
|
child={child}
|
||||||
treeParentMap={treeParentMap}
|
>
|
||||||
tree={child}
|
{child.item.model === 'folder' &&
|
||||||
isCollapsed={isCollapsed}
|
!isCollapsed(child.item.id) &&
|
||||||
draggingId={draggingId}
|
draggingId !== child.item.id && (
|
||||||
hoveredTree={hoveredTree}
|
<SidebarItems
|
||||||
hoveredIndex={hoveredIndex}
|
treeParentMap={treeParentMap}
|
||||||
focused={focused}
|
tree={child}
|
||||||
selectedId={selectedId}
|
isCollapsed={isCollapsed}
|
||||||
selectedTree={selectedTree}
|
draggingId={draggingId}
|
||||||
onSelect={onSelect}
|
hoveredTree={hoveredTree}
|
||||||
handleMove={handleMove}
|
hoveredIndex={hoveredIndex}
|
||||||
handleEnd={handleEnd}
|
focused={focused}
|
||||||
handleDragStart={handleDragStart}
|
selectedId={selectedId}
|
||||||
/>
|
selectedTree={selectedTree}
|
||||||
)}
|
onSelect={onSelect}
|
||||||
</DraggableSidebarItem>
|
handleMove={handleMove}
|
||||||
</Fragment>
|
handleEnd={handleEnd}
|
||||||
))}
|
handleDragStart={handleDragStart}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DraggableSidebarItem>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
{hoveredIndex === tree.children.length && hoveredTree?.item.id === tree.item.id && (
|
{hoveredIndex === tree.children.length && hoveredTree?.item.id === tree.item.id && (
|
||||||
<DropMarker />
|
<DropMarker />
|
||||||
)}
|
)}
|
||||||
@@ -802,12 +849,12 @@ const SidebarItem = forwardRef(function SidebarItem(
|
|||||||
data-active={isActive}
|
data-active={isActive}
|
||||||
data-selected={selected}
|
data-selected={selected}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'w-full flex gap-1.5 items-center text-sm h-xs px-1.5 rounded-md transition-colors',
|
'w-full flex gap-1.5 items-center text-sm h-xs px-1.5 rounded-md',
|
||||||
editing && 'ring-1 focus-within:ring-focus',
|
editing && 'ring-1 focus-within:ring-focus',
|
||||||
isActive && 'bg-highlightSecondary text-gray-800',
|
isActive && 'bg-background-highlight-secondary text-fg',
|
||||||
!isActive &&
|
!isActive &&
|
||||||
'text-gray-600 group-hover/item:text-gray-800 active:bg-highlightSecondary',
|
'text-fg-subtle group-hover/item:text-fg active:bg-background-highlight-secondary',
|
||||||
selected && useProminentStyles && '!bg-violet-400/20',
|
selected && useProminentStyles && '!bg-background-active',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{itemModel === 'folder' && (
|
{itemModel === 'folder' && (
|
||||||
@@ -815,7 +862,8 @@ const SidebarItem = forwardRef(function SidebarItem(
|
|||||||
size="sm"
|
size="sm"
|
||||||
icon="chevronRight"
|
icon="chevronRight"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'transition-transform opacity-50',
|
'text-fg-subtler',
|
||||||
|
'transition-transform',
|
||||||
!isCollapsed(itemId) && 'transform rotate-90',
|
!isCollapsed(itemId) && 'transform rotate-90',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -837,13 +885,13 @@ const SidebarItem = forwardRef(function SidebarItem(
|
|||||||
{latestGrpcConnection ? (
|
{latestGrpcConnection ? (
|
||||||
<div className="ml-auto">
|
<div className="ml-auto">
|
||||||
{latestGrpcConnection.elapsed === 0 && (
|
{latestGrpcConnection.elapsed === 0 && (
|
||||||
<Icon spin size="sm" icon="update" className="text-gray-400" />
|
<Icon spin size="sm" icon="update" className="text-fg-subtler" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : latestHttpResponse ? (
|
) : latestHttpResponse ? (
|
||||||
<div className="ml-auto">
|
<div className="ml-auto">
|
||||||
{isResponseLoading(latestHttpResponse) ? (
|
{isResponseLoading(latestHttpResponse) ? (
|
||||||
<Icon spin size="sm" icon="update" className="text-gray-400" />
|
<Icon spin size="sm" icon="refresh" className="text-fg-subtler" />
|
||||||
) : (
|
) : (
|
||||||
<StatusTag className="text-2xs dark:opacity-80" response={latestHttpResponse} />
|
<StatusTag className="text-2xs dark:opacity-80" response={latestHttpResponse} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
import type { EditorView } from 'codemirror';
|
import type { EditorView } from 'codemirror';
|
||||||
import type { FormEvent, ReactNode } from 'react';
|
import type { FormEvent, ReactNode } from 'react';
|
||||||
import { memo, useRef, useState } from 'react';
|
import { memo, useRef, useState } from 'react';
|
||||||
@@ -58,7 +59,7 @@ export const UrlBar = memo(function UrlBar({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className={className}>
|
<form onSubmit={handleSubmit} className={classNames(className)}>
|
||||||
<Input
|
<Input
|
||||||
autocompleteVariables
|
autocompleteVariables
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
@@ -75,7 +76,7 @@ export const UrlBar = memo(function UrlBar({
|
|||||||
onFocus={() => setIsFocused(true)}
|
onFocus={() => setIsFocused(true)}
|
||||||
onBlur={() => setIsFocused(false)}
|
onBlur={() => setIsFocused(false)}
|
||||||
onPaste={onPaste}
|
onPaste={onPaste}
|
||||||
containerClassName="shadow shadow-gray-100 dark:shadow-gray-50"
|
containerClassName="shadow bg-background border"
|
||||||
onChange={onUrlChange}
|
onChange={onUrlChange}
|
||||||
defaultValue={url}
|
defaultValue={url}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
|||||||
@@ -143,7 +143,8 @@ export default function Workspace() {
|
|||||||
initial={{ opacity: 0, x: -20 }}
|
initial={{ opacity: 0, x: -20 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'absolute top-0 left-0 bottom-0 bg-gray-100 border-r border-highlight w-[14rem]',
|
'x-theme-sidebar',
|
||||||
|
'absolute top-0 left-0 bottom-0 bg-background border-r border-background-highlight w-[14rem]',
|
||||||
'grid grid-rows-[auto_1fr]',
|
'grid grid-rows-[auto_1fr]',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -155,8 +156,11 @@ export default function Workspace() {
|
|||||||
</Overlay>
|
</Overlay>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div style={side} className={classNames('overflow-hidden bg-gray-100')}>
|
<div
|
||||||
<Sidebar className="border-r border-highlight" />
|
style={side}
|
||||||
|
className={classNames('x-theme-sidebar', 'overflow-hidden bg-background')}
|
||||||
|
>
|
||||||
|
<Sidebar className="border-r border-background-highlight" />
|
||||||
</div>
|
</div>
|
||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
className="-translate-x-3"
|
className="-translate-x-3"
|
||||||
@@ -168,14 +172,14 @@ export default function Workspace() {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<HeaderSize data-tauri-drag-region style={head}>
|
<HeaderSize data-tauri-drag-region className="x-theme-appHeader bg-background" style={head}>
|
||||||
<WorkspaceHeader className="pointer-events-none" />
|
<WorkspaceHeader className="pointer-events-none" />
|
||||||
</HeaderSize>
|
</HeaderSize>
|
||||||
{activeWorkspace == null ? (
|
{activeWorkspace == null ? (
|
||||||
<div className="m-auto">
|
<div className="m-auto">
|
||||||
<Banner color="warning" className="max-w-[30rem]">
|
<Banner color="warning" className="max-w-[30rem]">
|
||||||
The active workspace{' '}
|
The active workspace{' '}
|
||||||
<InlineCode className="text-orange-800">{activeWorkspaceId}</InlineCode> was not found.
|
<InlineCode className="text-fg-warning">{activeWorkspaceId}</InlineCode> was not found.
|
||||||
Select a workspace from the header menu or report this bug to <FeedbackLink />
|
Select a workspace from the header menu or report this bug to <FeedbackLink />
|
||||||
</Banner>
|
</Banner>
|
||||||
</div>
|
</div>
|
||||||
@@ -214,10 +218,11 @@ function HeaderSize({ className, style, ...props }: HeaderSizeProps) {
|
|||||||
const stoplightsVisible = platform?.osType === 'macos' && !fullscreen;
|
const stoplightsVisible = platform?.osType === 'macos' && !fullscreen;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
data-tauri-drag-region
|
||||||
style={style}
|
style={style}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'h-md pt-[1px] w-full border-b min-w-0',
|
'h-md pt-[1px] w-full border-b border-background-highlight min-w-0',
|
||||||
stoplightsVisible ? 'pl-20 pr-1' : 'pl-1',
|
stoplightsVisible ? 'pl-20 pr-1' : 'pl-1',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="focus"
|
className="focus"
|
||||||
color="gray"
|
color="primary"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
hide();
|
hide();
|
||||||
const environmentId = (await getRecentEnvironments(w.id))[0];
|
const environmentId = (await getRecentEnvironments(w.id))[0];
|
||||||
@@ -76,7 +76,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="focus"
|
className="focus"
|
||||||
color="gray"
|
color="secondary"
|
||||||
rightSlot={<Icon icon="externalLink" />}
|
rightSlot={<Icon icon="externalLink" />}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
hide();
|
hide();
|
||||||
@@ -169,7 +169,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
size="sm"
|
size="sm"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'text-gray-800 !px-2 truncate',
|
'text-fg !px-2 truncate',
|
||||||
activeWorkspace === null && 'italic opacity-disabled',
|
activeWorkspace === null && 'italic opacity-disabled',
|
||||||
)}
|
)}
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import { Button } from './core/Button';
|
|||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
import { HStack } from './core/Stacks';
|
import { HStack } from './core/Stacks';
|
||||||
import { EnvironmentActionsDropdown } from './EnvironmentActionsDropdown';
|
import { EnvironmentActionsDropdown } from './EnvironmentActionsDropdown';
|
||||||
|
import { ImportCurlButton } from './ImportCurlButton';
|
||||||
import { RecentRequestsDropdown } from './RecentRequestsDropdown';
|
import { RecentRequestsDropdown } from './RecentRequestsDropdown';
|
||||||
import { SettingsDropdown } from './SettingsDropdown';
|
import { SettingsDropdown } from './SettingsDropdown';
|
||||||
import { SidebarActions } from './SidebarActions';
|
import { SidebarActions } from './SidebarActions';
|
||||||
import { WorkspaceActionsDropdown } from './WorkspaceActionsDropdown';
|
import { WorkspaceActionsDropdown } from './WorkspaceActionsDropdown';
|
||||||
import { ImportCurlButton } from './ImportCurlButton';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -33,7 +33,7 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
|
|||||||
<CookieDropdown />
|
<CookieDropdown />
|
||||||
<HStack alignItems="center">
|
<HStack alignItems="center">
|
||||||
<WorkspaceActionsDropdown />
|
<WorkspaceActionsDropdown />
|
||||||
<Icon icon="chevronRight" className="text-gray-900 text-opacity-disabled" />
|
<Icon icon="chevronRight" className="text-fg-subtle" />
|
||||||
<EnvironmentActionsDropdown className="w-auto pointer-events-auto" />
|
<EnvironmentActionsDropdown className="w-auto pointer-events-auto" />
|
||||||
</HStack>
|
</HStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
@@ -46,7 +46,8 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
|
|||||||
{(osInfo?.osType === 'linux' || osInfo?.osType === 'windows') && (
|
{(osInfo?.osType === 'linux' || osInfo?.osType === 'windows') && (
|
||||||
<HStack className="ml-4" alignItems="center">
|
<HStack className="ml-4" alignItems="center">
|
||||||
<Button
|
<Button
|
||||||
className="px-4 !text-gray-600 rounded-none"
|
className="px-4 text-fg-subtle hocus:text-fg hocus:bg-background-highlight-secondary rounded-none"
|
||||||
|
color="custom"
|
||||||
onClick={() => getCurrent().minimize()}
|
onClick={() => getCurrent().minimize()}
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
@@ -54,7 +55,8 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
|
|||||||
</svg>
|
</svg>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="px-4 !text-gray-600 rounded-none"
|
className="px-4 text-fg-subtle hocus:text-fg hocus:bg-background-highlight rounded-none"
|
||||||
|
color="custom"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const w = getCurrent();
|
const w = getCurrent();
|
||||||
await w.toggleMaximize();
|
await w.toggleMaximize();
|
||||||
@@ -76,7 +78,7 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="custom"
|
color="custom"
|
||||||
className="px-4 text-gray-600 rounded-none hocus:bg-red-200 hocus:text-gray-800"
|
className="px-4 text-fg-subtle rounded-none hocus:bg-fg-danger hocus:text-fg"
|
||||||
onClick={() => getCurrent().close()}
|
onClick={() => getCurrent().close()}
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
|||||||
@@ -4,19 +4,18 @@ import type { ReactNode } from 'react';
|
|||||||
interface Props {
|
interface Props {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
color?: 'danger' | 'warning' | 'success' | 'gray';
|
color?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger';
|
||||||
}
|
}
|
||||||
export function Banner({ children, className, color = 'gray' }: Props) {
|
|
||||||
|
export function Banner({ children, className, color = 'secondary' }: Props) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
|
`x-theme-banner--${color}`,
|
||||||
'border border-dashed italic px-3 py-2 rounded select-auto cursor-text',
|
'border border-dashed italic px-3 py-2 rounded select-auto cursor-text',
|
||||||
color === 'gray' && 'border-gray-500/60 bg-gray-300/10 text-gray-800',
|
'border-background-highlight bg-background-highlight-secondary text-fg',
|
||||||
color === 'warning' && 'border-orange-500/60 bg-orange-300/10 text-orange-800',
|
|
||||||
color === 'danger' && 'border-red-500/60 bg-red-300/10 text-red-800',
|
|
||||||
color === 'success' && 'border-violet-500/60 bg-violet-300/10 text-violet-800',
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -7,7 +7,15 @@ import { Icon } from './Icon';
|
|||||||
|
|
||||||
export type ButtonProps = Omit<HTMLAttributes<HTMLButtonElement>, 'color'> & {
|
export type ButtonProps = Omit<HTMLAttributes<HTMLButtonElement>, 'color'> & {
|
||||||
innerClassName?: string;
|
innerClassName?: string;
|
||||||
color?: 'custom' | 'default' | 'gray' | 'primary' | 'secondary' | 'warning' | 'danger';
|
color?:
|
||||||
|
| 'custom'
|
||||||
|
| 'default'
|
||||||
|
| 'secondary'
|
||||||
|
| 'primary'
|
||||||
|
| 'info'
|
||||||
|
| 'success'
|
||||||
|
| 'warning'
|
||||||
|
| 'danger';
|
||||||
variant?: 'border' | 'solid';
|
variant?: 'border' | 'solid';
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
size?: 'xs' | 'sm' | 'md';
|
size?: 'xs' | 'sm' | 'md';
|
||||||
@@ -48,6 +56,10 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
|||||||
|
|
||||||
const classes = classNames(
|
const classes = classNames(
|
||||||
className,
|
className,
|
||||||
|
'x-theme-button',
|
||||||
|
`x-theme-button--${variant}`,
|
||||||
|
`x-theme-button--${variant}--${color}`,
|
||||||
|
'text-fg',
|
||||||
'max-w-full min-w-0', // Help with truncation
|
'max-w-full min-w-0', // Help with truncation
|
||||||
'hocus:opacity-100', // Force opacity for certain hover effects
|
'hocus:opacity-100', // Force opacity for certain hover effects
|
||||||
'whitespace-nowrap outline-none',
|
'whitespace-nowrap outline-none',
|
||||||
@@ -59,46 +71,26 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
|||||||
size === 'md' && 'h-md px-3',
|
size === 'md' && 'h-md px-3',
|
||||||
size === 'sm' && 'h-sm px-2.5 text-sm',
|
size === 'sm' && 'h-sm px-2.5 text-sm',
|
||||||
size === 'xs' && 'h-xs px-2 text-sm',
|
size === 'xs' && 'h-xs px-2 text-sm',
|
||||||
|
|
||||||
// Solids
|
// Solids
|
||||||
|
variant === 'solid' &&
|
||||||
|
color !== 'custom' &&
|
||||||
|
color !== 'default' &&
|
||||||
|
'bg-background enabled:hocus:bg-background-highlight ring-background-highlight-secondary',
|
||||||
variant === 'solid' && color === 'custom' && 'ring-blue-400',
|
variant === 'solid' && color === 'custom' && 'ring-blue-400',
|
||||||
variant === 'solid' &&
|
variant === 'solid' &&
|
||||||
color === 'default' &&
|
color === 'default' &&
|
||||||
'text-gray-700 enabled:hocus:bg-gray-700/10 enabled:hocus:text-gray-800 ring-blue-400',
|
'enabled:hocus:bg-background-highlight ring-fg-info',
|
||||||
variant === 'solid' &&
|
|
||||||
color === 'gray' &&
|
|
||||||
'text-gray-800 bg-gray-200/70 enabled:hocus:bg-gray-200 ring-blue-400',
|
|
||||||
variant === 'solid' &&
|
|
||||||
color === 'primary' &&
|
|
||||||
'bg-blue-400 text-white ring-blue-700 enabled:hocus:bg-blue-500',
|
|
||||||
variant === 'solid' &&
|
|
||||||
color === 'secondary' &&
|
|
||||||
'bg-violet-400 text-white ring-violet-700 enabled:hocus:bg-violet-500',
|
|
||||||
variant === 'solid' &&
|
|
||||||
color === 'warning' &&
|
|
||||||
'bg-orange-400 text-white ring-orange-700 enabled:hocus:bg-orange-500',
|
|
||||||
variant === 'solid' &&
|
|
||||||
color === 'danger' &&
|
|
||||||
'bg-red-400 text-white ring-red-700 enabled:hocus:bg-red-500',
|
|
||||||
// Borders
|
// Borders
|
||||||
variant === 'border' && 'border',
|
variant === 'border' && 'border',
|
||||||
|
variant === 'border' &&
|
||||||
|
color !== 'custom' &&
|
||||||
|
color !== 'default' &&
|
||||||
|
'border-fg-subtler text-fg-subtle enabled:hocus:border-fg-subtle enabled:hocus:bg-background-highlight enabled:hocus:text-fg ring-fg-subtler',
|
||||||
variant === 'border' &&
|
variant === 'border' &&
|
||||||
color === 'default' &&
|
color === 'default' &&
|
||||||
'border-highlight text-gray-700 enabled:hocus:border-focus enabled:hocus:text-gray-800 ring-blue-500/50',
|
'border-background-highlight enabled:hocus:border-fg-subtler enabled:hocus:bg-background-highlight-secondary',
|
||||||
variant === 'border' &&
|
|
||||||
color === 'gray' &&
|
|
||||||
'border-gray-500/70 text-gray-700 enabled:hocus:bg-gray-500/20 enabled:hocus:text-gray-800 ring-blue-500/50',
|
|
||||||
variant === 'border' &&
|
|
||||||
color === 'primary' &&
|
|
||||||
'border-blue-500/70 text-blue-700 enabled:hocus:border-blue-500 ring-blue-500/50',
|
|
||||||
variant === 'border' &&
|
|
||||||
color === 'secondary' &&
|
|
||||||
'border-violet-500/70 text-violet-700 enabled:hocus:border-violet-500 ring-violet-500/50',
|
|
||||||
variant === 'border' &&
|
|
||||||
color === 'warning' &&
|
|
||||||
'border-orange-500/70 text-orange-700 enabled:hocus:border-orange-500 ring-orange-500/50',
|
|
||||||
variant === 'border' &&
|
|
||||||
color === 'danger' &&
|
|
||||||
'border-red-500/70 text-red-700 enabled:hocus:border-red-500 ring-red-500/50',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ export function Checkbox({
|
|||||||
as="label"
|
as="label"
|
||||||
space={2}
|
space={2}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
className={classNames(className, 'text-gray-900 text-sm', disabled && 'opacity-disabled')}
|
className={classNames(className, 'text-fg text-sm', disabled && 'opacity-disabled')}
|
||||||
>
|
>
|
||||||
<div className={classNames(inputWrapperClassName, 'relative flex')}>
|
<div className={classNames(inputWrapperClassName, 'relative flex')}>
|
||||||
<input
|
<input
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'appearance-none w-4 h-4 flex-shrink-0 border border-highlight',
|
'appearance-none w-4 h-4 flex-shrink-0 border border-background-highlight',
|
||||||
'rounded hocus:border-focus hocus:bg-focus/[5%] outline-none ring-0',
|
'rounded hocus:border-border-focus hocus:bg-focus/[5%] outline-none ring-0',
|
||||||
)}
|
)}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function CountBadge({ count, className }: Props) {
|
|||||||
aria-hidden
|
aria-hidden
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'opacity-70 border border-highlight text-4xs rounded mb-0.5 px-1 ml-1 h-4 font-mono',
|
'opacity-70 border border-background-highlight-secondary text-4xs rounded mb-0.5 px-1 ml-1 h-4 font-mono',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{count}
|
{count}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function Dialog({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Overlay open={open} onClose={onClose} portalName="dialog">
|
<Overlay open={open} onClose={onClose} portalName="dialog">
|
||||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
<div className="x-theme-dialog absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||||
<div
|
<div
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-labelledby={titleId}
|
aria-labelledby={titleId}
|
||||||
@@ -63,9 +63,9 @@ export function Dialog({
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'grid grid-rows-[auto_auto_minmax(0,1fr)]',
|
'grid grid-rows-[auto_auto_minmax(0,1fr)]',
|
||||||
'relative bg-gray-50 pointer-events-auto',
|
'relative bg-background pointer-events-auto',
|
||||||
'rounded-lg',
|
'rounded-lg',
|
||||||
'dark:border border-highlight shadow shadow-black/10',
|
'border border-background-highlight shadow-lg shadow-[rgba(0,0,0,0.1)]',
|
||||||
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-6rem)]',
|
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-6rem)]',
|
||||||
size === 'sm' && 'w-[25rem] max-h-[80vh]',
|
size === 'sm' && 'w-[25rem] max-h-[80vh]',
|
||||||
size === 'md' && 'w-[45rem] max-h-[80vh]',
|
size === 'md' && 'w-[45rem] max-h-[80vh]',
|
||||||
@@ -83,7 +83,7 @@ export function Dialog({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{description ? (
|
{description ? (
|
||||||
<p className="px-6 text-gray-700" id={descriptionId}>
|
<p className="px-6 text-fg-subtle" id={descriptionId}>
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ import React, {
|
|||||||
import { useKey, useWindowSize } from 'react-use';
|
import { useKey, useWindowSize } from 'react-use';
|
||||||
import type { HotkeyAction } from '../../hooks/useHotKey';
|
import type { HotkeyAction } from '../../hooks/useHotKey';
|
||||||
import { useHotKey } from '../../hooks/useHotKey';
|
import { useHotKey } from '../../hooks/useHotKey';
|
||||||
|
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
|
||||||
import { getNodeText } from '../../lib/getNodeText';
|
import { getNodeText } from '../../lib/getNodeText';
|
||||||
import { Overlay } from '../Overlay';
|
import { Overlay } from '../Overlay';
|
||||||
import { Button } from './Button';
|
import { Button } from './Button';
|
||||||
import { HotKey } from './HotKey';
|
import { HotKey } from './HotKey';
|
||||||
|
import { Icon } from './Icon';
|
||||||
import { Separator } from './Separator';
|
import { Separator } from './Separator';
|
||||||
import { HStack, VStack } from './Stacks';
|
import { HStack, VStack } from './Stacks';
|
||||||
import { Icon } from './Icon';
|
|
||||||
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
|
|
||||||
|
|
||||||
export type DropdownItemSeparator = {
|
export type DropdownItemSeparator = {
|
||||||
type: 'separator';
|
type: 'separator';
|
||||||
@@ -413,7 +413,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
|||||||
)}
|
)}
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Overlay open variant="transparent" portalName="dropdown" zIndex={50}>
|
<Overlay open variant="transparent" portalName="dropdown" zIndex={50}>
|
||||||
<div>
|
<div className="x-theme-dialog">
|
||||||
<div tabIndex={-1} aria-hidden className="fixed inset-0 z-30" onClick={handleClose} />
|
<div tabIndex={-1} aria-hidden className="fixed inset-0 z-30" onClick={handleClose} />
|
||||||
<motion.div
|
<motion.div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
@@ -431,7 +431,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
|||||||
<span
|
<span
|
||||||
aria-hidden
|
aria-hidden
|
||||||
style={triangleStyles}
|
style={triangleStyles}
|
||||||
className="bg-gray-50 absolute rotate-45 border-gray-200 border-t border-l"
|
className="bg-background absolute rotate-45 border-background-highlight border-t border-l"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{containerStyles && (
|
{containerStyles && (
|
||||||
@@ -440,22 +440,24 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
|||||||
style={menuStyles}
|
style={menuStyles}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'h-auto bg-gray-50 rounded-md shadow-lg dark:shadow-gray-0 py-1.5 border',
|
'h-auto bg-background rounded-md shadow-lg py-1.5 border',
|
||||||
'border-gray-200 overflow-auto mb-1 mx-0.5',
|
'border-background-highlight overflow-auto mb-1 mx-0.5',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{filter && (
|
{filter && (
|
||||||
<HStack
|
<HStack
|
||||||
space={2}
|
space={2}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
className="pb-0.5 px-1.5 mb-2 text-xs border border-highlight mx-2 rounded font-mono h-2xs"
|
className="pb-0.5 px-1.5 mb-2 text-xs border border-background-highlight-secondary mx-2 rounded font-mono h-2xs"
|
||||||
>
|
>
|
||||||
<Icon icon="search" size="xs" className="text-gray-700" />
|
<Icon icon="search" size="xs" className="text-fg-subtle" />
|
||||||
<div className="text-gray-800">{filter}</div>
|
<div className="text-fg">{filter}</div>
|
||||||
</HStack>
|
</HStack>
|
||||||
)}
|
)}
|
||||||
{filteredItems.length === 0 && (
|
{filteredItems.length === 0 && (
|
||||||
<span className="text-gray-500 text-sm text-center px-2 py-1">No matches</span>
|
<span className="text-fg-subtler text-sm text-center px-2 py-1">
|
||||||
|
No matches
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
{filteredItems.map((item, i) => {
|
{filteredItems.map((item, i) => {
|
||||||
if (item.type === 'separator') {
|
if (item.type === 'separator') {
|
||||||
@@ -531,15 +533,17 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
|||||||
justify="start"
|
justify="start"
|
||||||
leftSlot={item.leftSlot && <div className="pr-2 flex justify-start">{item.leftSlot}</div>}
|
leftSlot={item.leftSlot && <div className="pr-2 flex justify-start">{item.leftSlot}</div>}
|
||||||
rightSlot={rightSlot && <div className="ml-auto pl-3">{rightSlot}</div>}
|
rightSlot={rightSlot && <div className="ml-auto pl-3">{rightSlot}</div>}
|
||||||
|
innerClassName="!text-left"
|
||||||
|
color="custom"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'h-xs', // More compact
|
'h-xs', // More compact
|
||||||
'min-w-[8rem] outline-none px-2 mx-1.5 flex text-sm text-gray-700 whitespace-nowrap',
|
'min-w-[8rem] outline-none px-2 mx-1.5 flex text-sm whitespace-nowrap',
|
||||||
'focus:bg-highlight focus:text-gray-800 rounded',
|
'focus:bg-background-highlight focus:text-fg rounded',
|
||||||
item.variant === 'danger' && 'text-red-600',
|
item.variant === 'default' && 'text-fg-subtle',
|
||||||
item.variant === 'notify' && 'text-pink-600',
|
item.variant === 'danger' && 'text-fg-danger',
|
||||||
|
item.variant === 'notify' && 'text-fg-primary',
|
||||||
)}
|
)}
|
||||||
innerClassName="!text-left"
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ interface Props {
|
|||||||
|
|
||||||
export function DurationTag({ total, headers }: Props) {
|
export function DurationTag({ total, headers }: Props) {
|
||||||
return (
|
return (
|
||||||
<span title={`HEADER: ${formatMillis(headers)}\nTOTAL: ${formatMillis(total)}`}>
|
<span
|
||||||
|
className="font-mono"
|
||||||
|
title={`HEADER: ${formatMillis(headers)}\nTOTAL: ${formatMillis(total)}`}
|
||||||
|
>
|
||||||
{formatMillis(total)}
|
{formatMillis(total)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
@apply w-full block text-base;
|
@apply w-full block text-base;
|
||||||
|
|
||||||
.cm-cursor {
|
.cm-cursor {
|
||||||
@apply border-gray-800 !important;
|
@apply border-fg !important;
|
||||||
|
/* Widen the cursor */
|
||||||
|
@apply border-l-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.cm-focused {
|
&.cm-focused {
|
||||||
@@ -17,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-line {
|
.cm-line {
|
||||||
@apply text-gray-800 pl-1 pr-1.5;
|
@apply text-fg pl-1 pr-1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-placeholder {
|
.cm-placeholder {
|
||||||
@@ -47,7 +49,7 @@
|
|||||||
/* Style gutters */
|
/* Style gutters */
|
||||||
|
|
||||||
.cm-gutters {
|
.cm-gutters {
|
||||||
@apply border-0 text-gray-500/50;
|
@apply border-0 text-fg-subtler bg-transparent;
|
||||||
|
|
||||||
.cm-gutterElement {
|
.cm-gutterElement {
|
||||||
@apply cursor-default;
|
@apply cursor-default;
|
||||||
@@ -55,19 +57,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-widget {
|
.placeholder-widget {
|
||||||
@apply text-xs text-violet-700 dark:text-violet-700 px-1 mx-[0.5px] rounded cursor-default dark:shadow;
|
/* Colors */
|
||||||
|
@apply bg-background text-fg-subtle border-background-highlight-secondary;
|
||||||
|
@apply hover:border-background-highlight hover:text-fg hover:bg-background-highlight-secondary;
|
||||||
|
|
||||||
/* NOTE: Background and border are translucent so we can see text selection through it */
|
@apply border px-1 mx-[0.5px] rounded cursor-default dark:shadow;
|
||||||
@apply bg-violet-500/20 border border-violet-500/20 border-opacity-40;
|
|
||||||
|
|
||||||
/* Bring above on hover */
|
|
||||||
@apply hover:z-10 relative;
|
|
||||||
|
|
||||||
-webkit-text-security: none;
|
-webkit-text-security: none;
|
||||||
|
|
||||||
&.placeholder-widget-error {
|
|
||||||
@apply text-red-700 dark:text-red-800 bg-red-300/30 border-red-300/80 border-opacity-40 hover:border-red-300 hover:bg-red-300/40;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hyperlink-widget {
|
.hyperlink-widget {
|
||||||
@@ -137,7 +133,7 @@
|
|||||||
|
|
||||||
.cm-editor .fold-gutter-icon::after {
|
.cm-editor .fold-gutter-icon::after {
|
||||||
@apply block w-1.5 h-1.5 border-transparent -rotate-45
|
@apply block w-1.5 h-1.5 border-transparent -rotate-45
|
||||||
border-l border-b border-l-[currentColor] border-b-[currentColor] content-[''];
|
border-l border-b border-l-[currentColor] border-b-[currentColor] content-[''];
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor .fold-gutter-icon[data-open] {
|
.cm-editor .fold-gutter-icon[data-open] {
|
||||||
@@ -149,12 +145,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor .fold-gutter-icon:hover {
|
.cm-editor .fold-gutter-icon:hover {
|
||||||
@apply text-gray-900 bg-gray-300/50;
|
@apply text-fg bg-background-highlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor .cm-foldPlaceholder {
|
.cm-editor .cm-foldPlaceholder {
|
||||||
@apply px-2 border border-gray-400/50 bg-gray-300/50;
|
@apply px-2 border border-fg-subtler bg-background-highlight;
|
||||||
@apply hover:text-gray-800 hover:border-gray-400;
|
@apply hover:text-fg hover:border-fg-subtle;
|
||||||
@apply cursor-default !important;
|
@apply cursor-default !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,11 +160,13 @@
|
|||||||
|
|
||||||
.cm-wrapper:not(.cm-readonly) .cm-editor {
|
.cm-wrapper:not(.cm-readonly) .cm-editor {
|
||||||
&.cm-focused .cm-activeLineGutter {
|
&.cm-focused .cm-activeLineGutter {
|
||||||
@apply text-gray-600;
|
@apply text-fg-subtle;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-wrapper.cm-readonly .cm-editor {
|
||||||
.cm-cursor {
|
.cm-cursor {
|
||||||
@apply border-l-2 border-gray-800;
|
@apply border-fg-danger !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,18 +185,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-tooltip.cm-tooltip-hover {
|
.cm-tooltip.cm-tooltip-hover {
|
||||||
@apply shadow-lg bg-gray-100 rounded text-gray-700 border border-gray-500 z-50 pointer-events-auto text-xs;
|
@apply shadow-lg bg-background rounded text-fg-subtle border border-fg-subtler z-50 pointer-events-auto text-xs;
|
||||||
@apply px-2 py-1;
|
@apply px-2 py-1;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@apply text-gray-800;
|
@apply text-fg;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply underline;
|
@apply underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
@apply text-gray-800 bg-gray-800 h-3 w-3 ml-1;
|
@apply text-fg bg-fg-secondary h-3 w-3 ml-1;
|
||||||
content: '';
|
content: '';
|
||||||
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='black' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z'/%3E%3C/svg%3E");
|
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='black' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z'/%3E%3C/svg%3E");
|
||||||
-webkit-mask-size: contain;
|
-webkit-mask-size: contain;
|
||||||
@@ -210,7 +208,7 @@
|
|||||||
/* NOTE: Extra selector required to override default styles */
|
/* NOTE: Extra selector required to override default styles */
|
||||||
.cm-tooltip.cm-tooltip-autocomplete,
|
.cm-tooltip.cm-tooltip-autocomplete,
|
||||||
.cm-tooltip.cm-completionInfo {
|
.cm-tooltip.cm-completionInfo {
|
||||||
@apply shadow-lg bg-gray-100 rounded text-gray-700 border border-gray-300 z-50 pointer-events-auto text-xs;
|
@apply shadow-lg bg-background rounded text-fg-subtle border border-background-highlight z-50 pointer-events-auto text-xs;
|
||||||
|
|
||||||
.cm-completionIcon {
|
.cm-completionIcon {
|
||||||
@apply italic font-mono;
|
@apply italic font-mono;
|
||||||
@@ -286,11 +284,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
& > ul > li {
|
& > ul > li {
|
||||||
@apply cursor-default px-2 rounded-sm text-gray-600 h-7 flex items-center;
|
@apply cursor-default px-2 rounded-sm text-fg-subtle h-7 flex items-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > ul > li[aria-selected] {
|
& > ul > li[aria-selected] {
|
||||||
@apply bg-highlight text-gray-900;
|
@apply bg-background-highlight-secondary text-fg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-completionIcon {
|
.cm-completionIcon {
|
||||||
@@ -298,7 +296,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-completionLabel {
|
.cm-completionLabel {
|
||||||
@apply text-gray-700;
|
@apply text-fg-subtle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-completionDetail {
|
.cm-completionDetail {
|
||||||
@@ -308,7 +306,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor .cm-panels {
|
.cm-editor .cm-panels {
|
||||||
@apply bg-gray-100 backdrop-blur-sm p-1 mb-1 text-gray-800 z-20 rounded-md;
|
@apply bg-background-highlight-secondary backdrop-blur-sm p-1 mb-1 text-fg z-20 rounded-md;
|
||||||
|
|
||||||
input,
|
input,
|
||||||
button {
|
button {
|
||||||
@@ -316,19 +314,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@apply appearance-none bg-none bg-gray-200 hocus:bg-gray-300 hocus:text-gray-950 border-0 text-gray-800 cursor-default;
|
@apply border-fg-subtler bg-background-highlight text-fg hover:border-fg-info;
|
||||||
|
@apply appearance-none bg-none cursor-default;
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name='close'] {
|
button[name='close'] {
|
||||||
@apply text-gray-600 hocus:text-gray-900 px-2 -mr-1.5 !important;
|
@apply text-fg-subtle hocus:text-fg px-2 -mr-1.5 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
@apply bg-gray-50 border border-gray-500/50 focus:border-focus outline-none;
|
@apply bg-background border-background-highlight focus:border-border-focus;
|
||||||
|
@apply border outline-none cursor-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
@apply focus-within:text-gray-950;
|
@apply focus-within:text-fg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide the "All" button */
|
/* Hide the "All" button */
|
||||||
|
|||||||
@@ -206,7 +206,6 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
|||||||
|
|
||||||
view = new EditorView({ state, parent: container });
|
view = new EditorView({ state, parent: container });
|
||||||
cm.current = { view, languageCompartment };
|
cm.current = { view, languageCompartment };
|
||||||
syncGutterBg({ parent: container, bgClassList });
|
|
||||||
if (autoFocus) {
|
if (autoFocus) {
|
||||||
view.focus();
|
view.focus();
|
||||||
}
|
}
|
||||||
@@ -270,7 +269,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
|||||||
ref={initEditorRef}
|
ref={initEditorRef}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'cm-wrapper text-base bg-gray-50',
|
'cm-wrapper text-base',
|
||||||
type === 'password' && 'cm-obscure-text',
|
type === 'password' && 'cm-obscure-text',
|
||||||
heightMode === 'auto' ? 'cm-auto-height' : 'cm-full-height',
|
heightMode === 'auto' ? 'cm-auto-height' : 'cm-full-height',
|
||||||
singleLine ? 'cm-singleline' : 'cm-multiline',
|
singleLine ? 'cm-singleline' : 'cm-multiline',
|
||||||
@@ -361,19 +360,6 @@ function getExtensions({
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncGutterBg = ({
|
|
||||||
parent,
|
|
||||||
bgClassList,
|
|
||||||
}: {
|
|
||||||
parent: HTMLDivElement;
|
|
||||||
bgClassList: string[];
|
|
||||||
}) => {
|
|
||||||
const gutterEl = parent.querySelector<HTMLDivElement>('.cm-gutters');
|
|
||||||
if (gutterEl) {
|
|
||||||
gutterEl?.classList.add(...bgClassList);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const placeholderElFromText = (text: string) => {
|
const placeholderElFromText = (text: string) => {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
el.innerHTML = text.replace('\n', '<br/>');
|
el.innerHTML = text.replace('\n', '<br/>');
|
||||||
|
|||||||
@@ -42,23 +42,23 @@ import { url } from './url/extension';
|
|||||||
export const myHighlightStyle = HighlightStyle.define([
|
export const myHighlightStyle = HighlightStyle.define([
|
||||||
{
|
{
|
||||||
tag: [t.documentMeta, t.blockComment, t.lineComment, t.docComment, t.comment],
|
tag: [t.documentMeta, t.blockComment, t.lineComment, t.docComment, t.comment],
|
||||||
color: 'hsl(var(--color-gray-600))',
|
color: 'var(--fg-subtler)',
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.paren],
|
tag: [t.paren],
|
||||||
color: 'hsl(var(--color-gray-900))',
|
color: 'var(--fg)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.name, t.tagName, t.angleBracket, t.docString, t.number],
|
tag: [t.name, t.tagName, t.angleBracket, t.docString, t.number],
|
||||||
color: 'hsl(var(--color-blue-600))',
|
color: 'var(--fg-info)',
|
||||||
},
|
},
|
||||||
{ tag: [t.variableName], color: 'hsl(var(--color-green-600))' },
|
{ tag: [t.variableName], color: 'var(--fg-success)' },
|
||||||
{ tag: [t.bool], color: 'hsl(var(--color-pink-600))' },
|
{ tag: [t.bool], color: 'var(--fg-info)' }, // TODO: Should be pink
|
||||||
{ tag: [t.attributeName, t.propertyName], color: 'hsl(var(--color-violet-600))' },
|
{ tag: [t.attributeName, t.propertyName], color: 'var(--fg-primary)' },
|
||||||
{ tag: [t.attributeValue], color: 'hsl(var(--color-orange-600))' },
|
{ tag: [t.attributeValue], color: 'var(--fg-warning)' },
|
||||||
{ tag: [t.string], color: 'hsl(var(--color-yellow-600))' },
|
{ tag: [t.string], color: 'var(--fg-warning)' }, // TODO: Should be yellow
|
||||||
{ tag: [t.keyword, t.meta, t.operator], color: 'hsl(var(--color-red-600))' },
|
{ tag: [t.keyword, t.meta, t.operator], color: 'var(--fg-danger)' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const myTheme = EditorView.theme({}, { dark: true });
|
const myTheme = EditorView.theme({}, { dark: true });
|
||||||
@@ -123,6 +123,7 @@ export const baseExtensions = [
|
|||||||
dropCursor(),
|
dropCursor(),
|
||||||
drawSelection(),
|
drawSelection(),
|
||||||
autocompletion({
|
autocompletion({
|
||||||
|
tooltipClass: () => 'x-theme-dialog',
|
||||||
closeOnBlur: true, // Set to `false` for debugging in devtools without closing it
|
closeOnBlur: true, // Set to `false` for debugging in devtools without closing it
|
||||||
compareCompletions: (a, b) => {
|
compareCompletions: (a, b) => {
|
||||||
// Don't sort completions at all, only on boost
|
// Don't sort completions at all, only on boost
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ class PlaceholderWidget extends WidgetType {
|
|||||||
}
|
}
|
||||||
toDOM() {
|
toDOM() {
|
||||||
const elt = document.createElement('span');
|
const elt = document.createElement('span');
|
||||||
elt.className = `placeholder-widget ${
|
elt.className = `x-theme-placeholder-widget placeholder-widget ${
|
||||||
!this.isExistingVariable ? 'placeholder-widget-error' : ''
|
this.isExistingVariable
|
||||||
|
? 'x-theme-placeholder-widget--primary'
|
||||||
|
: 'x-theme-placeholder-widget--danger'
|
||||||
}`;
|
}`;
|
||||||
elt.title = !this.isExistingVariable ? 'Variable not found in active environment' : '';
|
elt.title = !this.isExistingVariable ? 'Variable not found in active environment' : '';
|
||||||
elt.textContent = this.name;
|
elt.textContent = this.name;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export function FormattedError({ children }: Props) {
|
|||||||
<pre
|
<pre
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'w-full text-sm select-auto cursor-text bg-gray-100 p-3 rounded',
|
'w-full text-sm select-auto cursor-text bg-gray-100 p-3 rounded',
|
||||||
'whitespace-pre-wrap border border-red-500 border-dashed overflow-x-auto',
|
'whitespace-pre-wrap border border-fg-danger border-dashed overflow-x-auto',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function Heading({ className, size = 1, ...props }: Props) {
|
|||||||
<Component
|
<Component
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'font-semibold text-gray-900',
|
'font-semibold text-fg',
|
||||||
size === 1 && 'text-2xl',
|
size === 1 && 'text-2xl',
|
||||||
size === 2 && 'text-xl',
|
size === 2 && 'text-xl',
|
||||||
size === 3 && 'text-lg',
|
size === 3 && 'text-lg',
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function HotKey({ action, className, variant }: Props) {
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
variant === 'with-bg' && 'rounded border',
|
variant === 'with-bg' && 'rounded border',
|
||||||
'text-gray-1000 text-opacity-disabled',
|
'text-fg-subtler',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{labelParts.map((char, index) => (
|
{labelParts.map((char, index) => (
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ interface Props {
|
|||||||
|
|
||||||
export function HotKeyLabel({ action }: Props) {
|
export function HotKeyLabel({ action }: Props) {
|
||||||
const label = useHotKeyLabel(action);
|
const label = useHotKeyLabel(action);
|
||||||
return <span>{label}</span>;
|
return <span className="text-fg-subtle">{label}</span>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface Props {
|
|||||||
|
|
||||||
export const HotKeyList = ({ hotkeys, bottomSlot }: Props) => {
|
export const HotKeyList = ({ hotkeys, bottomSlot }: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex items-center justify-center text-gray-700 text-sm">
|
<div className="h-full flex items-center justify-center text-sm">
|
||||||
<VStack space={2}>
|
<VStack space={2}>
|
||||||
{hotkeys.map((hotkey) => (
|
{hotkeys.map((hotkey) => (
|
||||||
<HStack key={hotkey} className="grid grid-cols-2">
|
<HStack key={hotkey} className="grid grid-cols-2">
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function HttpMethodTag({ request, className }: Props) {
|
|||||||
|
|
||||||
const m = method.toLowerCase();
|
const m = method.toLowerCase();
|
||||||
return (
|
return (
|
||||||
<span className={classNames(className, 'text-2xs font-mono opacity-50')}>
|
<span className={classNames(className, 'text-2xs font-mono text-fg-subtle')}>
|
||||||
{methodMap[m] ?? m.slice(0, 3).toUpperCase()}
|
{methodMap[m] ?? m.slice(0, 3).toUpperCase()}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as lucide from 'lucide-react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import * as lucide from 'lucide-react';
|
||||||
import type { HTMLAttributes } from 'react';
|
import type { HTMLAttributes } from 'react';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
@@ -44,6 +44,7 @@ const icons = {
|
|||||||
moreVertical: lucide.MoreVerticalIcon,
|
moreVertical: lucide.MoreVerticalIcon,
|
||||||
paste: lucide.ClipboardPasteIcon,
|
paste: lucide.ClipboardPasteIcon,
|
||||||
pencil: lucide.PencilIcon,
|
pencil: lucide.PencilIcon,
|
||||||
|
pin: lucide.PinIcon,
|
||||||
plug: lucide.Plug,
|
plug: lucide.Plug,
|
||||||
plus: lucide.PlusIcon,
|
plus: lucide.PlusIcon,
|
||||||
plusCircle: lucide.PlusCircleIcon,
|
plusCircle: lucide.PlusCircleIcon,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
|
|||||||
size={size}
|
size={size}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'relative flex-shrink-0 text-gray-700 hover:text-gray-1000',
|
'group/button relative flex-shrink-0 text-fg-subtle',
|
||||||
'!px-0',
|
'!px-0',
|
||||||
size === 'md' && 'w-9',
|
size === 'md' && 'w-9',
|
||||||
size === 'sm' && 'w-8',
|
size === 'sm' && 'w-8',
|
||||||
@@ -71,6 +71,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
|
|||||||
spin={spin}
|
spin={spin}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
iconClassName,
|
iconClassName,
|
||||||
|
'group-hover/button:text-fg',
|
||||||
props.disabled && 'opacity-70',
|
props.disabled && 'opacity-70',
|
||||||
confirmed && 'text-green-600',
|
confirmed && 'text-green-600',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ export function InlineCode({ className, ...props }: HTMLAttributes<HTMLSpanEleme
|
|||||||
<code
|
<code
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'font-mono text-xs bg-highlight border-0 border-gray-200/30',
|
'font-mono text-xs bg-background-highlight-secondary',
|
||||||
'px-1.5 py-0.5 rounded text-gray-800 shadow-inner',
|
'px-1.5 py-0.5 rounded text-fg shadow-inner',
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
|||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
labelClassName,
|
labelClassName,
|
||||||
'text-sm text-gray-900 whitespace-nowrap',
|
'text-sm text-fg whitespace-nowrap',
|
||||||
hideLabel && 'sr-only',
|
hideLabel && 'sr-only',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -145,10 +145,10 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
|||||||
alignItems="stretch"
|
alignItems="stretch"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
containerClassName,
|
containerClassName,
|
||||||
'relative w-full rounded-md text-gray-900',
|
'relative w-full rounded-md text-fg',
|
||||||
'border',
|
'border',
|
||||||
focused ? 'border-focus' : 'border-highlight',
|
focused ? 'border-border-focus' : 'border-background-highlight',
|
||||||
!isValid && '!border-invalid',
|
!isValid && '!border-fg-danger',
|
||||||
size === 'md' && 'min-h-md',
|
size === 'md' && 'min-h-md',
|
||||||
size === 'sm' && 'min-h-sm',
|
size === 'sm' && 'min-h-sm',
|
||||||
size === 'xs' && 'min-h-xs',
|
size === 'xs' && 'min-h-xs',
|
||||||
@@ -186,7 +186,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
|||||||
title={obscured ? `Show ${label}` : `Obscure ${label}`}
|
title={obscured ? `Show ${label}` : `Obscure ${label}`}
|
||||||
size="xs"
|
size="xs"
|
||||||
className="mr-0.5 group/obscure !h-auto my-0.5"
|
className="mr-0.5 group/obscure !h-auto my-0.5"
|
||||||
iconClassName="text-gray-500 group-hover/obscure:text-gray-800"
|
iconClassName="text-fg-subtle group-hover/obscure:text-fg"
|
||||||
iconSize="sm"
|
iconSize="sm"
|
||||||
icon={obscured ? 'eye' : 'eyeClosed'}
|
icon={obscured ? 'eye' : 'eyeClosed'}
|
||||||
onClick={() => setObscured((o) => !o)}
|
onClick={() => setObscured((o) => !o)}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const JsonAttributeTree = ({ depth = 0, attrKey, attrValue, attrKeyJsonPa
|
|||||||
: null,
|
: null,
|
||||||
isExpandable: Object.keys(attrValue).length > 0,
|
isExpandable: Object.keys(attrValue).length > 0,
|
||||||
label: isExpanded ? `{${Object.keys(attrValue).length || ' '}}` : `{⋯}`,
|
label: isExpanded ? `{${Object.keys(attrValue).length || ' '}}` : `{⋯}`,
|
||||||
labelClassName: 'text-gray-600',
|
labelClassName: 'text-fg-subtler',
|
||||||
};
|
};
|
||||||
} else if (jsonType === '[object Array]') {
|
} else if (jsonType === '[object Array]') {
|
||||||
return {
|
return {
|
||||||
@@ -59,7 +59,7 @@ export const JsonAttributeTree = ({ depth = 0, attrKey, attrValue, attrKeyJsonPa
|
|||||||
: null,
|
: null,
|
||||||
isExpandable: attrValue.length > 0,
|
isExpandable: attrValue.length > 0,
|
||||||
label: isExpanded ? `[${attrValue.length || ' '}]` : `[⋯]`,
|
label: isExpanded ? `[${attrValue.length || ' '}]` : `[⋯]`,
|
||||||
labelClassName: 'text-gray-600',
|
labelClassName: 'text-subtler',
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
@@ -67,19 +67,17 @@ export const JsonAttributeTree = ({ depth = 0, attrKey, attrValue, attrKeyJsonPa
|
|||||||
isExpandable: false,
|
isExpandable: false,
|
||||||
label: jsonType === '[object String]' ? `"${attrValue}"` : `${attrValue}`,
|
label: jsonType === '[object String]' ? `"${attrValue}"` : `${attrValue}`,
|
||||||
labelClassName: classNames(
|
labelClassName: classNames(
|
||||||
jsonType === '[object Boolean]' && 'text-pink-600',
|
jsonType === '[object Boolean]' && 'text-fg-primary',
|
||||||
jsonType === '[object Number]' && 'text-blue-600',
|
jsonType === '[object Number]' && 'text-fg-info',
|
||||||
jsonType === '[object String]' && 'text-yellow-600',
|
jsonType === '[object String]' && 'text-fg-notice',
|
||||||
jsonType === '[object Null]' && 'text-red-600',
|
jsonType === '[object Null]' && 'text-fg-danger',
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [attrValue, attrKeyJsonPath, isExpanded, depth]);
|
}, [attrValue, attrKeyJsonPath, isExpanded, depth]);
|
||||||
|
|
||||||
const labelEl = (
|
const labelEl = (
|
||||||
<span className={classNames(labelClassName, 'select-text group-hover:text-gray-800')}>
|
<span className={classNames(labelClassName, 'select-text group-hover:text-fg')}>{label}</span>
|
||||||
{label}
|
|
||||||
</span>
|
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className={classNames(/*depth === 0 && '-ml-4',*/ 'font-mono text-2xs')}>
|
<div className={classNames(/*depth === 0 && '-ml-4',*/ 'font-mono text-2xs')}>
|
||||||
@@ -91,18 +89,18 @@ export const JsonAttributeTree = ({ depth = 0, attrKey, attrValue, attrKeyJsonPa
|
|||||||
icon="chevronRight"
|
icon="chevronRight"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'left-0 absolute transition-transform flex items-center',
|
'left-0 absolute transition-transform flex items-center',
|
||||||
'text-gray-600 group-hover:text-gray-900',
|
'text-fg-subtler group-hover:text-fg-subtle',
|
||||||
isExpanded ? 'rotate-90' : '',
|
isExpanded ? 'rotate-90' : '',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span className="text-violet-600 group-hover:text-violet-700 mr-1.5 whitespace-nowrap">
|
<span className="text-fg-primary group-hover:text-fg-primary mr-1.5 whitespace-nowrap">
|
||||||
{attrKey === undefined ? '$' : attrKey}:
|
{attrKey === undefined ? '$' : attrKey}:
|
||||||
</span>
|
</span>
|
||||||
{labelEl}
|
{labelEl}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<span className="text-violet-600 mr-1.5 pl-4 whitespace-nowrap select-text">
|
<span className="text-fg-primary mr-1.5 pl-4 whitespace-nowrap select-text">
|
||||||
{attrKey}:
|
{attrKey}:
|
||||||
</span>
|
</span>
|
||||||
{labelEl}
|
{labelEl}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function KeyValueRow({ label, value, labelClassName }: Props) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<td
|
<td
|
||||||
className={classNames('py-0.5 pr-2 text-gray-700 select-text cursor-text', labelClassName)}
|
className={classNames('py-0.5 pr-2 text-fg-subtle select-text cursor-text', labelClassName)}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ function PairEditorRow({
|
|||||||
{pairContainer.pair.isFile ? (
|
{pairContainer.pair.isFile ? (
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
color="gray"
|
color="secondary"
|
||||||
className="font-mono text-xs"
|
className="font-mono text-xs"
|
||||||
onClick={async (e) => {
|
onClick={async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function Select<T extends string>({
|
|||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
labelClassName,
|
labelClassName,
|
||||||
'text-sm text-gray-900 whitespace-nowrap',
|
'text-sm text-fg whitespace-nowrap',
|
||||||
hideLabel && 'sr-only',
|
hideLabel && 'sr-only',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -52,7 +52,7 @@ export function Select<T extends string>({
|
|||||||
onChange={(e) => onChange(e.target.value as T)}
|
onChange={(e) => onChange(e.target.value as T)}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'font-mono text-xs border w-full outline-none bg-transparent pl-2 pr-7',
|
'font-mono text-xs border w-full outline-none bg-transparent pl-2 pr-7',
|
||||||
'border-highlight focus:border-focus',
|
'bg-background-highlight-secondary border-background-highlight focus:border-border-focus',
|
||||||
size === 'xs' && 'h-xs',
|
size === 'xs' && 'h-xs',
|
||||||
size === 'sm' && 'h-sm',
|
size === 'sm' && 'h-sm',
|
||||||
size === 'md' && 'h-md',
|
size === 'md' && 'h-md',
|
||||||
|
|||||||
@@ -8,19 +8,13 @@ interface Props {
|
|||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Separator({
|
export function Separator({ className, orientation = 'horizontal', children }: Props) {
|
||||||
className,
|
|
||||||
variant = 'primary',
|
|
||||||
orientation = 'horizontal',
|
|
||||||
children,
|
|
||||||
}: Props) {
|
|
||||||
return (
|
return (
|
||||||
<div role="separator" className={classNames(className, 'flex items-center')}>
|
<div role="separator" className={classNames(className, 'flex items-center')}>
|
||||||
{children && <div className="text-xs text-gray-500 mr-2 whitespace-nowrap">{children}</div>}
|
{children && <div className="text-xs text-fg-subtler mr-2 whitespace-nowrap">{children}</div>}
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
variant === 'primary' && 'bg-highlight',
|
'bg-background-highlight',
|
||||||
variant === 'secondary' && 'bg-highlightSecondary',
|
|
||||||
orientation === 'horizontal' && 'w-full h-[1px]',
|
orientation === 'horizontal' && 'w-full h-[1px]',
|
||||||
orientation === 'vertical' && 'h-full w-[1px]',
|
orientation === 'vertical' && 'h-full w-[1px]',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function SizeTag({ contentLength }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span title={`${contentLength} bytes`}>
|
<span className="font-mono" title={`${contentLength} bytes`}>
|
||||||
{Math.round(num * 10) / 10} {unit}
|
{Math.round(num * 10) / 10} {unit}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,17 +10,18 @@ interface Props {
|
|||||||
export function StatusTag({ response, className, showReason }: Props) {
|
export function StatusTag({ response, className, showReason }: Props) {
|
||||||
const { status } = response;
|
const { status } = response;
|
||||||
const label = status < 100 ? 'ERR' : status;
|
const label = status < 100 ? 'ERR' : status;
|
||||||
|
const category = `${status}`[0];
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'font-mono',
|
'font-mono',
|
||||||
status >= 0 && status < 100 && 'text-red-600',
|
category === '0' && 'text-fg-danger',
|
||||||
status >= 100 && status < 200 && 'text-green-600',
|
category === '1' && 'text-fg-info',
|
||||||
status >= 200 && status < 300 && 'text-green-600',
|
category === '2' && 'text-fg-success',
|
||||||
status >= 300 && status < 400 && 'text-pink-600',
|
category === '3' && 'text-fg-primary',
|
||||||
status >= 400 && status < 500 && 'text-orange-600',
|
category === '4' && 'text-fg-warning',
|
||||||
status >= 500 && 'text-red-600',
|
category === '5' && 'text-fg-danger',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{label} {showReason && response.statusReason && response.statusReason}
|
{label} {showReason && response.statusReason && response.statusReason}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export function Tabs({
|
|||||||
{tabs.map((t) => {
|
{tabs.map((t) => {
|
||||||
const isActive = t.value === value;
|
const isActive = t.value === value;
|
||||||
const btnClassName = classNames(
|
const btnClassName = classNames(
|
||||||
isActive ? 'text-gray-800' : 'text-gray-600 hover:text-gray-700',
|
isActive ? 'text-fg' : 'text-fg-subtler hover:text-fg-subtle',
|
||||||
'!px-2 ml-[1px]',
|
'!px-2 ml-[1px]',
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ export function Tabs({
|
|||||||
icon="chevronDown"
|
icon="chevronDown"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'-mr-1.5 mt-0.5',
|
'-mr-1.5 mt-0.5',
|
||||||
isActive ? 'opacity-100' : 'opacity-20',
|
isActive ? 'text-fg-subtle' : 'opacity-50',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { motion } from 'framer-motion';
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useKey } from 'react-use';
|
import { useKey } from 'react-use';
|
||||||
import { IconButton } from './IconButton';
|
|
||||||
import type { IconProps } from './Icon';
|
import type { IconProps } from './Icon';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
|
import { IconButton } from './IconButton';
|
||||||
|
|
||||||
export interface ToastProps {
|
export interface ToastProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -52,14 +52,15 @@ export function Toast({
|
|||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
|
'x-theme-dialog',
|
||||||
'pointer-events-auto',
|
'pointer-events-auto',
|
||||||
'relative bg-gray-50 dark:bg-gray-100 pointer-events-auto',
|
'relative bg-background pointer-events-auto',
|
||||||
'rounded-lg',
|
'rounded-lg',
|
||||||
'border border-highlightSecondary dark:border-highlight shadow-xl',
|
'border border-background-highlight dark:border-background-highlight-secondary shadow-xl',
|
||||||
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-6rem)]',
|
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-6rem)]',
|
||||||
'w-[22rem] max-h-[80vh]',
|
'w-[22rem] max-h-[80vh]',
|
||||||
'm-2 grid grid-cols-[1fr_auto]',
|
'm-2 grid grid-cols-[1fr_auto]',
|
||||||
'text-gray-700',
|
'text-fg',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="px-3 py-2 flex items-center gap-2">
|
<div className="px-3 py-2 flex items-center gap-2">
|
||||||
@@ -67,10 +68,10 @@ export function Toast({
|
|||||||
<Icon
|
<Icon
|
||||||
icon={ICONS[variant]}
|
icon={ICONS[variant]}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
variant === 'success' && 'text-green-500',
|
variant === 'success' && 'text-fg-success',
|
||||||
variant === 'warning' && 'text-orange-500',
|
variant === 'warning' && 'text-fg-warning',
|
||||||
variant === 'error' && 'text-red-500',
|
variant === 'error' && 'text-fg-danger',
|
||||||
variant === 'copied' && 'text-violet-500',
|
variant === 'copied' && 'text-fg-primary',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -82,7 +83,7 @@ export function Toast({
|
|||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
color="custom"
|
color="custom"
|
||||||
className="opacity-50"
|
className="opacity-60"
|
||||||
title="Dismiss"
|
title="Dismiss"
|
||||||
icon="x"
|
icon="x"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
@@ -91,7 +92,7 @@ export function Toast({
|
|||||||
{timeout != null && (
|
{timeout != null && (
|
||||||
<div className="w-full absolute bottom-0 left-0 right-0">
|
<div className="w-full absolute bottom-0 left-0 right-0">
|
||||||
<motion.div
|
<motion.div
|
||||||
className="bg-highlight h-0.5"
|
className="bg-background-highlight h-0.5"
|
||||||
initial={{ width: '100%' }}
|
initial={{ width: '100%' }}
|
||||||
animate={{ width: '0%', opacity: 0.2 }}
|
animate={{ width: '0%', opacity: 0.2 }}
|
||||||
transition={{ duration: timeout / 1000, ease: 'linear' }}
|
transition={{ duration: timeout / 1000, ease: 'linear' }}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import classNames from 'classnames';
|
|
||||||
import type { ReactNode } from 'react';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
className?: string;
|
|
||||||
children?: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function WindowDragRegion({ className, ...props }: Props) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-tauri-drag-region
|
|
||||||
className={classNames(className, 'w-full flex-shrink-0')}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ import { convertFileSrc } from '@tauri-apps/api/core';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { HttpResponse } from '../../lib/models';
|
import type { HttpResponse } from '../../lib/models';
|
||||||
|
import { Button } from '../core/Button';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
response: HttpResponse;
|
response: HttpResponse;
|
||||||
@@ -20,15 +21,15 @@ export function ImageViewer({ response, className }: Props) {
|
|||||||
if (!show) {
|
if (!show) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="text-sm italic text-gray-500">
|
<div className="text-sm italic text-fg-subtler">
|
||||||
Response body is too large to preview.{' '}
|
Response body is too large to preview.{' '}
|
||||||
<button
|
<Button
|
||||||
className="cursor-pointer underline hover:text-gray-800"
|
className="cursor-pointer underline hover:text-fg"
|
||||||
color="gray"
|
color="secondary"
|
||||||
onClick={() => setShow(true)}
|
onClick={() => setShow(true)}
|
||||||
>
|
>
|
||||||
Show anyway
|
Show anyway
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ export function TextViewer({ response, pretty }: Props) {
|
|||||||
return (
|
return (
|
||||||
<Editor
|
<Editor
|
||||||
readOnly
|
readOnly
|
||||||
className="bg-gray-50 dark:!bg-gray-100"
|
|
||||||
forceUpdateKey={body}
|
forceUpdateKey={body}
|
||||||
defaultValue={body}
|
defaultValue={body}
|
||||||
contentType={contentType}
|
contentType={contentType}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function WebPageViewer({ response }: Props) {
|
|||||||
title="Response preview"
|
title="Response preview"
|
||||||
srcDoc={contentForIframe}
|
srcDoc={contentForIframe}
|
||||||
sandbox="allow-scripts allow-same-origin"
|
sandbox="allow-scripts allow-same-origin"
|
||||||
className="h-full w-full rounded border border-highlightSecondary"
|
className="h-full w-full rounded border border-background-highlight-secondary"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ export function Confirm({ onHide, onResult, confirmText, variant = 'confirm' }:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack space={2} justifyContent="start" className="mt-2 mb-4 flex-row-reverse">
|
<HStack space={2} justifyContent="start" className="mt-2 mb-4 flex-row-reverse">
|
||||||
<Button className="focus" color={colors[variant]} onClick={handleSuccess}>
|
<Button color={colors[variant]} onClick={handleSuccess}>
|
||||||
{confirmText ?? confirmButtonTexts[variant]}
|
{confirmText ?? confirmButtonTexts[variant]}
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="focus" color="gray" onClick={handleHide}>
|
<Button onClick={handleHide} variant="border">
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|||||||
@@ -52,10 +52,10 @@ export function Prompt({
|
|||||||
onChange={setValue}
|
onChange={setValue}
|
||||||
/>
|
/>
|
||||||
<HStack space={2} justifyContent="end">
|
<HStack space={2} justifyContent="end">
|
||||||
<Button className="focus" color="gray" onClick={onHide}>
|
<Button onClick={onHide} variant="border">
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" className="focus" color="primary">
|
<Button type="submit" color="primary">
|
||||||
{confirmLabel}
|
{confirmLabel}
|
||||||
</Button>
|
</Button>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { buildKeyValueKey, getKeyValue, setKeyValue } from '../lib/keyValueStore';
|
import { buildKeyValueKey, getKeyValue, setKeyValue } from '../lib/keyValueStore';
|
||||||
|
|
||||||
const DEFAULT_NAMESPACE = 'app';
|
const DEFAULT_NAMESPACE = 'global';
|
||||||
|
|
||||||
export function keyValueQueryKey({
|
export function keyValueQueryKey({
|
||||||
namespace = DEFAULT_NAMESPACE,
|
namespace = DEFAULT_NAMESPACE,
|
||||||
@@ -20,7 +20,7 @@ export function useKeyValue<T extends Object | null>({
|
|||||||
key,
|
key,
|
||||||
fallback,
|
fallback,
|
||||||
}: {
|
}: {
|
||||||
namespace?: 'app' | 'no_sync' | 'global';
|
namespace?: 'global' | 'no_sync';
|
||||||
key: string | string[];
|
key: string | string[];
|
||||||
fallback: T;
|
fallback: T;
|
||||||
}) {
|
}) {
|
||||||
@@ -51,7 +51,8 @@ export function useKeyValue<T extends Object | null>({
|
|||||||
await mutate.mutateAsync(value);
|
await mutate.mutateAsync(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[fallback, key, mutate, namespace],
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[fallback, key, namespace],
|
||||||
);
|
);
|
||||||
|
|
||||||
const reset = useCallback(async () => mutate.mutateAsync(fallback), [mutate, fallback]);
|
const reset = useCallback(async () => mutate.mutateAsync(fallback), [mutate, fallback]);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { open } from '@tauri-apps/plugin-shell';
|
||||||
|
import { Button } from '../components/core/Button';
|
||||||
import { useToast } from '../components/ToastContext';
|
import { useToast } from '../components/ToastContext';
|
||||||
import { useListenToTauriEvent } from './useListenToTauriEvent';
|
import { useListenToTauriEvent } from './useListenToTauriEvent';
|
||||||
import { Button } from '../components/core/Button';
|
|
||||||
import { open } from '@tauri-apps/plugin-shell';
|
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
|
||||||
|
|
||||||
export function useNotificationToast() {
|
export function useNotificationToast() {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
@@ -31,7 +31,7 @@ export function useNotificationToast() {
|
|||||||
actionLabel && actionUrl ? (
|
actionLabel && actionUrl ? (
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
color="gray"
|
color="secondary"
|
||||||
className="mr-auto min-w-[5rem]"
|
className="mr-auto min-w-[5rem]"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
toast.hide(payload.id);
|
toast.hide(payload.id);
|
||||||
|
|||||||
19
src-web/hooks/usePinnedGrpcConnection.ts
Normal file
19
src-web/hooks/usePinnedGrpcConnection.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { GrpcConnection, GrpcRequest } from '../lib/models';
|
||||||
|
import { useGrpcConnections } from './useGrpcConnections';
|
||||||
|
import { useKeyValue } from './useKeyValue';
|
||||||
|
import { useLatestGrpcConnection } from './useLatestGrpcConnection';
|
||||||
|
|
||||||
|
export function usePinnedGrpcConnection(activeRequest: GrpcRequest) {
|
||||||
|
const latestConnection = useLatestGrpcConnection(activeRequest.id);
|
||||||
|
const { set: setPinnedConnectionId, value: pinnedConnectionId } = useKeyValue<string | null>({
|
||||||
|
// Key on latest connection instead of activeRequest because connections change out of band of active request
|
||||||
|
key: ['pinned_grpc_connection_id', latestConnection?.id ?? 'n/a'],
|
||||||
|
fallback: null,
|
||||||
|
namespace: 'global',
|
||||||
|
});
|
||||||
|
const connections = useGrpcConnections(activeRequest.id);
|
||||||
|
const activeConnection: GrpcConnection | null =
|
||||||
|
connections.find((r) => r.id === pinnedConnectionId) ?? latestConnection;
|
||||||
|
|
||||||
|
return { activeConnection, setPinnedConnectionId, pinnedConnectionId, connections } as const;
|
||||||
|
}
|
||||||
@@ -1,28 +1,19 @@
|
|||||||
import { useCallback, useEffect } from 'react';
|
|
||||||
import { createGlobalState } from 'react-use';
|
|
||||||
import type { HttpRequest, HttpResponse } from '../lib/models';
|
import type { HttpRequest, HttpResponse } from '../lib/models';
|
||||||
import { useHttpResponses } from './useHttpResponses';
|
import { useHttpResponses } from './useHttpResponses';
|
||||||
|
import { useKeyValue } from './useKeyValue';
|
||||||
import { useLatestHttpResponse } from './useLatestHttpResponse';
|
import { useLatestHttpResponse } from './useLatestHttpResponse';
|
||||||
|
|
||||||
const usePinnedResponseIdState = createGlobalState<string | null>(null);
|
|
||||||
|
|
||||||
export function usePinnedHttpResponse(activeRequest: HttpRequest) {
|
export function usePinnedHttpResponse(activeRequest: HttpRequest) {
|
||||||
const [pinnedResponseId, setPinnedResponseId] = usePinnedResponseIdState();
|
|
||||||
const latestResponse = useLatestHttpResponse(activeRequest.id);
|
const latestResponse = useLatestHttpResponse(activeRequest.id);
|
||||||
|
const { set: setPinnedResponseId, value: pinnedResponseId } = useKeyValue<string | null>({
|
||||||
|
// Key on latest response instead of activeRequest because responses change out of band of active request
|
||||||
|
key: ['pinned_http_response_id', latestResponse?.id ?? 'n/a'],
|
||||||
|
fallback: null,
|
||||||
|
namespace: 'global',
|
||||||
|
});
|
||||||
const responses = useHttpResponses(activeRequest.id);
|
const responses = useHttpResponses(activeRequest.id);
|
||||||
const activeResponse: HttpResponse | null = pinnedResponseId
|
const activeResponse: HttpResponse | null =
|
||||||
? responses.find((r) => r.id === pinnedResponseId) ?? null
|
responses.find((r) => r.id === pinnedResponseId) ?? latestResponse;
|
||||||
: latestResponse ?? null;
|
|
||||||
|
|
||||||
// Unset pinned response when a new one comes in
|
return { activeResponse, setPinnedResponseId, pinnedResponseId, responses } as const;
|
||||||
useEffect(() => setPinnedResponseId(null), [responses.length, setPinnedResponseId]);
|
|
||||||
|
|
||||||
const setPinnedResponse = useCallback(
|
|
||||||
(r: HttpResponse) => {
|
|
||||||
setPinnedResponseId(r.id);
|
|
||||||
},
|
|
||||||
[setPinnedResponseId],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { activeResponse, setPinnedResponse, pinnedResponseId, responses } as const;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ export function fallbackRequestName(r: HttpRequest | GrpcRequest | null): string
|
|||||||
return r.name;
|
return r.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
const withoutVariables = r.url.replace(/\$\{\[[^\]]+]}/g, '');
|
const withoutVariables = r.url.replace(/\$\{\[\s*([^\]]+)\s*]}/g, '$1');
|
||||||
if (withoutVariables.trim() === '') {
|
if (withoutVariables.trim() === '') {
|
||||||
return r.model === 'http_request' ? 'New HTTP Request' : 'new gRPC Request';
|
return r.model === 'http_request' ? 'New HTTP Request' : 'new gRPC Request';
|
||||||
}
|
}
|
||||||
|
|
||||||
const fixedUrl = r.url.match(/^https?:\/\//) ? r.url : 'http://' + r.url;
|
const fixedUrl = withoutVariables.match(/^https?:\/\//)
|
||||||
|
? withoutVariables
|
||||||
|
: 'http://' + withoutVariables;
|
||||||
|
|
||||||
if (r.model === 'grpc_request' && r.service != null && r.method != null) {
|
if (r.model === 'grpc_request' && r.service != null && r.method != null) {
|
||||||
const shortService = r.service.split('.').pop();
|
const shortService = r.service.split('.').pop();
|
||||||
@@ -21,6 +23,7 @@ export function fallbackRequestName(r: HttpRequest | GrpcRequest | null): string
|
|||||||
try {
|
try {
|
||||||
const url = new URL(fixedUrl);
|
const url = new URL(fixedUrl);
|
||||||
const pathname = url.pathname === '/' ? '' : url.pathname;
|
const pathname = url.pathname === '/' ? '' : url.pathname;
|
||||||
|
console.log('hello', fixedUrl);
|
||||||
return `${url.host}${pathname}`;
|
return `${url.host}${pathname}`;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Nothing
|
// Nothing
|
||||||
|
|||||||
6
src-web/lib/indent.ts
Normal file
6
src-web/lib/indent.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export function indent(text: string, space = ' '): string {
|
||||||
|
return text
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => space + line)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
94
src-web/lib/theme/color.ts
Normal file
94
src-web/lib/theme/color.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import parseColor from 'parse-color';
|
||||||
|
|
||||||
|
export class Color {
|
||||||
|
private theme: 'dark' | 'light' = 'light';
|
||||||
|
|
||||||
|
private hue: number = 0;
|
||||||
|
private saturation: number = 0;
|
||||||
|
private lightness: number = 0;
|
||||||
|
private alpha: number = 1;
|
||||||
|
|
||||||
|
constructor(cssColor: string, theme: 'dark' | 'light') {
|
||||||
|
try {
|
||||||
|
const { hsla } = parseColor(cssColor || '');
|
||||||
|
this.hue = hsla[0];
|
||||||
|
this.saturation = hsla[1];
|
||||||
|
this.lightness = hsla[2];
|
||||||
|
this.alpha = hsla[3] ?? 1;
|
||||||
|
this.theme = theme;
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Failed to parse CSS color', cssColor, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static transparent(): Color {
|
||||||
|
return new Color('rgba(0, 0, 0, 0.1)', 'light');
|
||||||
|
}
|
||||||
|
|
||||||
|
private clone(): Color {
|
||||||
|
return new Color(this.css(), this.theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
lower(mod: number): Color {
|
||||||
|
return this.theme === 'dark' ? this._darken(mod) : this._lighten(mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
lowerTo(value: number): Color {
|
||||||
|
return this.theme === 'dark'
|
||||||
|
? this._darken(1)._lighten(value)
|
||||||
|
: this._lighten(1)._darken(1 - value);
|
||||||
|
}
|
||||||
|
|
||||||
|
lift(mod: number): Color {
|
||||||
|
return this.theme === 'dark' ? this._lighten(mod) : this._darken(mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
liftTo(value: number): Color {
|
||||||
|
return this.theme === 'dark'
|
||||||
|
? this._lighten(1)._darken(1 - value)
|
||||||
|
: this._darken(1)._lighten(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
translucify(mod: number): Color {
|
||||||
|
const c = this.clone();
|
||||||
|
c.alpha = c.alpha - c.alpha * mod;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
desaturate(mod: number): Color {
|
||||||
|
const c = this.clone();
|
||||||
|
c.saturation = c.saturation - c.saturation * mod;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
saturate(mod: number): Color {
|
||||||
|
const c = this.clone();
|
||||||
|
c.saturation = this.saturation + (100 - this.saturation) * mod;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
lighterThan(c: Color): boolean {
|
||||||
|
return this.lightness > c.lightness;
|
||||||
|
}
|
||||||
|
|
||||||
|
css(): string {
|
||||||
|
// If opacity is 1, allow for Tailwind modification
|
||||||
|
const h = Math.round(this.hue);
|
||||||
|
const s = Math.round(this.saturation);
|
||||||
|
const l = Math.round(this.lightness);
|
||||||
|
const a = Math.round(this.alpha * 100) / 100;
|
||||||
|
return `hsla(${h}, ${s}%, ${l}%, ${a})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _lighten(mod: number): Color {
|
||||||
|
const c = this.clone();
|
||||||
|
c.lightness = this.lightness + (100 - this.lightness) * mod;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _darken(mod: number): Color {
|
||||||
|
const c = this.clone();
|
||||||
|
c.lightness = this.lightness - this.lightness * mod;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
|
||||||
import { generateColorVariant, toTailwindVariable } from './theme';
|
|
||||||
|
|
||||||
describe('Generate colors', () => {
|
|
||||||
it('Generates dark colors', () => {
|
|
||||||
expect(generateColorVariant('hsl(0,0%,50%)', 50, 'dark', 0.2, 0.8)).toBe('hsl(0,0%,14.0%)');
|
|
||||||
expect(generateColorVariant('hsl(0,0%,50%)', 950, 'dark', 0.2, 0.8)).toBe('hsl(0,0%,77.0%)');
|
|
||||||
expect(generateColorVariant('hsl(0,0%,50%)', 50, 'dark', 0.4, 0.6)).toBe('hsl(0,0%,23.0%)');
|
|
||||||
expect(generateColorVariant('hsl(0,0%,50%)', 950, 'dark', 0.4, 0.6)).toBe('hsl(0,0%,59.0%)');
|
|
||||||
});
|
|
||||||
it('Generates light colors', () => {
|
|
||||||
expect(generateColorVariant('hsl(0,0%,50%)', 50, 'light', 0.2, 0.8)).toBe('hsl(0,0%,80.0%)');
|
|
||||||
expect(generateColorVariant('hsl(0,0%,50%)', 950, 'light', 0.2, 0.8)).toBe('hsl(0,0%,14.0%)');
|
|
||||||
expect(generateColorVariant('hsl(0,0%,50%)', 50, 'light', 0.4, 0.6)).toBe('hsl(0,0%,60.0%)');
|
|
||||||
expect(generateColorVariant('hsl(0,0%,50%)', 950, 'light', 0.4, 0.6)).toBe('hsl(0,0%,23.0%)');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Generates Tailwind color', () => {
|
|
||||||
it('Does it', () => {
|
|
||||||
expect(
|
|
||||||
toTailwindVariable({ name: 'blue', cssColor: 'hsl(10, 20%, 30%)', variant: 100 }),
|
|
||||||
).toEqual('--color-blue-100: 10 20% 30%;');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
import parseColor from 'parse-color';
|
|
||||||
import type { Appearance } from './window';
|
|
||||||
|
|
||||||
export type AppThemeColor =
|
|
||||||
| 'gray'
|
|
||||||
| 'red'
|
|
||||||
| 'orange'
|
|
||||||
| 'yellow'
|
|
||||||
| 'green'
|
|
||||||
| 'blue'
|
|
||||||
| 'pink'
|
|
||||||
| 'violet';
|
|
||||||
const colorNames: AppThemeColor[] = [
|
|
||||||
'gray',
|
|
||||||
'red',
|
|
||||||
'orange',
|
|
||||||
'yellow',
|
|
||||||
'green',
|
|
||||||
'blue',
|
|
||||||
'pink',
|
|
||||||
'violet',
|
|
||||||
];
|
|
||||||
export type AppThemeColorVariant =
|
|
||||||
| 0
|
|
||||||
| 50
|
|
||||||
| 100
|
|
||||||
| 200
|
|
||||||
| 300
|
|
||||||
| 400
|
|
||||||
| 500
|
|
||||||
| 600
|
|
||||||
| 700
|
|
||||||
| 800
|
|
||||||
| 900
|
|
||||||
| 950
|
|
||||||
| 1000;
|
|
||||||
|
|
||||||
export const appThemeVariants: AppThemeColorVariant[] = [
|
|
||||||
0, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950, 1000,
|
|
||||||
];
|
|
||||||
|
|
||||||
export type AppThemeLayer = 'root' | 'sidebar' | 'titlebar' | 'content' | 'above';
|
|
||||||
export type AppThemeColors = Record<AppThemeColor, string>;
|
|
||||||
|
|
||||||
export interface AppThemeLayerStyle {
|
|
||||||
colors: AppThemeColors;
|
|
||||||
blackPoint?: number;
|
|
||||||
whitePoint?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ThemeColorObj {
|
|
||||||
name: AppThemeColor;
|
|
||||||
variant: AppThemeColorVariant;
|
|
||||||
cssColor: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AppTheme {
|
|
||||||
name: string;
|
|
||||||
appearance: Appearance;
|
|
||||||
layers: Partial<Record<AppThemeLayer, AppThemeLayerStyle>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateCSS(t: AppTheme): ThemeColorObj[] {
|
|
||||||
const rootColors = t.layers.root?.colors;
|
|
||||||
if (rootColors === undefined) return [];
|
|
||||||
|
|
||||||
const colors: ThemeColorObj[] = [];
|
|
||||||
for (const color of colorNames) {
|
|
||||||
const rawValue = rootColors[color];
|
|
||||||
if (!rawValue) continue;
|
|
||||||
colors.push(
|
|
||||||
...generateColors(
|
|
||||||
color,
|
|
||||||
rawValue,
|
|
||||||
t.appearance,
|
|
||||||
t.layers.root?.blackPoint,
|
|
||||||
t.layers.root?.whitePoint,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return colors;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateColors(
|
|
||||||
name: AppThemeColor,
|
|
||||||
color: string,
|
|
||||||
appearance: Appearance,
|
|
||||||
blackPoint = 0,
|
|
||||||
whitePoint = 1,
|
|
||||||
): ThemeColorObj[] {
|
|
||||||
const colors = [];
|
|
||||||
for (const variant of appThemeVariants) {
|
|
||||||
colors.push({
|
|
||||||
name,
|
|
||||||
variant,
|
|
||||||
cssColor: generateColorVariant(color, variant, appearance, blackPoint, whitePoint),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return colors;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lightnessMap: Record<Appearance, Record<AppThemeColorVariant, number>> = {
|
|
||||||
system: {
|
|
||||||
// Not actually used
|
|
||||||
0: 1,
|
|
||||||
50: 1,
|
|
||||||
100: 0.9,
|
|
||||||
200: 0.7,
|
|
||||||
300: 0.4,
|
|
||||||
400: 0.2,
|
|
||||||
500: 0,
|
|
||||||
600: -0.2,
|
|
||||||
700: -0.4,
|
|
||||||
800: -0.6,
|
|
||||||
900: -0.8,
|
|
||||||
950: -0.9,
|
|
||||||
1000: -1,
|
|
||||||
},
|
|
||||||
light: {
|
|
||||||
0: 1,
|
|
||||||
50: 1,
|
|
||||||
100: 0.9,
|
|
||||||
200: 0.7,
|
|
||||||
300: 0.4,
|
|
||||||
400: 0.2,
|
|
||||||
500: 0,
|
|
||||||
600: -0.2,
|
|
||||||
700: -0.4,
|
|
||||||
800: -0.6,
|
|
||||||
900: -0.8,
|
|
||||||
950: -0.9,
|
|
||||||
1000: -1,
|
|
||||||
},
|
|
||||||
dark: {
|
|
||||||
0: -1,
|
|
||||||
50: -0.9,
|
|
||||||
100: -0.8,
|
|
||||||
200: -0.6,
|
|
||||||
300: -0.4,
|
|
||||||
400: -0.2,
|
|
||||||
500: 0,
|
|
||||||
600: 0.2,
|
|
||||||
700: 0.4,
|
|
||||||
800: 0.6,
|
|
||||||
900: 0.8,
|
|
||||||
950: 0.9,
|
|
||||||
1000: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export function generateColorVariant(
|
|
||||||
color: string,
|
|
||||||
variant: AppThemeColorVariant,
|
|
||||||
appearance: Appearance,
|
|
||||||
blackPoint = 0,
|
|
||||||
whitePoint = 1,
|
|
||||||
): string {
|
|
||||||
const { hsl } = parseColor(color || '');
|
|
||||||
const lightnessMod = lightnessMap[appearance][variant];
|
|
||||||
// const lightnessMod = (appearance === 'dark' ? 1 : -1) * ((variant / 1000) * 2 - 1);
|
|
||||||
const newL =
|
|
||||||
lightnessMod > 0
|
|
||||||
? hsl[2] + (100 * whitePoint - hsl[2]) * lightnessMod
|
|
||||||
: hsl[2] + hsl[2] * (1 - blackPoint) * lightnessMod;
|
|
||||||
return `hsl(${hsl[0]},${hsl[1]}%,${newL.toFixed(1)}%)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toTailwindVariable({ name, variant, cssColor }: ThemeColorObj): string {
|
|
||||||
const { hsl } = parseColor(cssColor || '');
|
|
||||||
return `--color-${name}-${variant}: ${hsl[0]} ${hsl[1]}% ${hsl[2]}%;`;
|
|
||||||
}
|
|
||||||
@@ -1,76 +1,313 @@
|
|||||||
import type { AppTheme, AppThemeColors } from './theme';
|
import { indent } from '../indent';
|
||||||
import { generateCSS, toTailwindVariable } from './theme';
|
import { Color } from './color';
|
||||||
|
|
||||||
export type Appearance = 'dark' | 'light' | 'system';
|
export type Appearance = 'dark' | 'light' | 'system';
|
||||||
|
|
||||||
const DEFAULT_APPEARANCE: Appearance = 'system';
|
const DEFAULT_APPEARANCE: Appearance = 'system';
|
||||||
|
|
||||||
enum Theme {
|
interface ThemeComponent {
|
||||||
yaak = 'yaak',
|
background?: Color;
|
||||||
catppuccin = 'catppuccin',
|
backgroundHighlight?: Color;
|
||||||
|
backgroundHighlightSecondary?: Color;
|
||||||
|
backgroundActive?: Color;
|
||||||
|
foreground?: Color;
|
||||||
|
foregroundSubtle?: Color;
|
||||||
|
foregroundSubtler?: Color;
|
||||||
|
colors?: Partial<RootColors>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const themes: Record<Theme, AppThemeColors> = {
|
interface YaakTheme extends ThemeComponent {
|
||||||
yaak: {
|
name: string;
|
||||||
gray: 'hsl(245, 23%, 45%)',
|
components?: {
|
||||||
red: 'hsl(342,100%, 63%)',
|
dialog?: ThemeComponent;
|
||||||
orange: 'hsl(32, 98%, 54%)',
|
sidebar?: ThemeComponent;
|
||||||
yellow: 'hsl(52, 79%, 58%)',
|
responsePane?: ThemeComponent;
|
||||||
green: 'hsl(136, 62%, 54%)',
|
appHeader?: ThemeComponent;
|
||||||
blue: 'hsl(206, 100%, 56%)',
|
button?: ThemeComponent;
|
||||||
pink: 'hsl(300, 100%, 71%)',
|
banner?: ThemeComponent;
|
||||||
violet: 'hsl(266, 100%, 73%)',
|
placeholder?: ThemeComponent;
|
||||||
},
|
};
|
||||||
catppuccin: {
|
}
|
||||||
gray: 'hsl(240, 23%, 47%)',
|
|
||||||
red: 'hsl(343, 91%, 74%)',
|
|
||||||
orange: 'hsl(23, 92%, 74%)',
|
|
||||||
yellow: 'hsl(41, 86%, 72%)',
|
|
||||||
green: 'hsl(115, 54%, 65%)',
|
|
||||||
blue: 'hsl(217, 92%, 65%)',
|
|
||||||
pink: 'hsl(316, 72%, 75%)',
|
|
||||||
violet: 'hsl(267, 84%, 70%)',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const darkTheme: AppTheme = {
|
interface RootColors {
|
||||||
name: 'Default Dark',
|
primary: Color;
|
||||||
appearance: 'dark',
|
secondary: Color;
|
||||||
layers: {
|
info: Color;
|
||||||
root: {
|
success: Color;
|
||||||
blackPoint: 0.2,
|
notice: Color;
|
||||||
colors: themes.yaak,
|
warning: Color;
|
||||||
|
danger: Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ColorName = keyof RootColors;
|
||||||
|
type ComponentName = keyof NonNullable<YaakTheme['components']>;
|
||||||
|
|
||||||
|
const yaakThemes: Record<string, YaakTheme> = {
|
||||||
|
yaakLight: {
|
||||||
|
name: 'Yaak (Light)',
|
||||||
|
background: new Color('#f2f4f7', 'light').lower(1),
|
||||||
|
foreground: new Color('hsl(219,23%,15%)', 'light'),
|
||||||
|
colors: {
|
||||||
|
primary: new Color('hsl(266,100%,70%)', 'light'),
|
||||||
|
secondary: new Color('hsl(220,24%,59%)', 'light'),
|
||||||
|
info: new Color('hsl(206,100%,48%)', 'light'),
|
||||||
|
success: new Color('hsl(155,95%,33%)', 'light'),
|
||||||
|
notice: new Color('hsl(45,100%,41%)', 'light'),
|
||||||
|
warning: new Color('hsl(30,100%,43%)', 'light'),
|
||||||
|
danger: new Color('hsl(335,75%,57%)', 'light'),
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
sidebar: {
|
||||||
|
background: new Color('#f2f4f7', 'light'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as YaakTheme,
|
||||||
|
|
||||||
|
yaakDark: {
|
||||||
|
name: 'Yaak Dark',
|
||||||
|
background: new Color('hsl(244,23%,12%)', 'dark'),
|
||||||
|
foreground: new Color('#bcbad4', 'dark'),
|
||||||
|
|
||||||
|
colors: {
|
||||||
|
primary: new Color('hsl(266,100%,79%)', 'dark'),
|
||||||
|
secondary: new Color('hsl(245,23%,60%)', 'dark'),
|
||||||
|
info: new Color('hsl(206,100%,63%)', 'dark'),
|
||||||
|
success: new Color('hsl(150,100%,37%)', 'dark'),
|
||||||
|
notice: new Color('hsl(48,80%,63%)', 'dark'),
|
||||||
|
warning: new Color('hsl(28,100%,61%)', 'dark'),
|
||||||
|
danger: new Color('hsl(342,90%,68%)', 'dark'),
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
sidebar: {
|
||||||
|
background: new Color('hsl(243,23%,15%)', 'dark'),
|
||||||
|
},
|
||||||
|
responsePane: {
|
||||||
|
background: new Color('hsl(243,23%,15%)', 'dark'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
catppuccin: {
|
||||||
|
name: 'Catppuccin',
|
||||||
const lightTheme: AppTheme = {
|
background: new Color('#181825', 'dark'),
|
||||||
name: 'Default Light',
|
foreground: new Color('#cdd6f4', 'dark'),
|
||||||
appearance: 'light',
|
foregroundSubtle: new Color('#cdd6f4', 'dark').lower(0.1).translucify(0.3),
|
||||||
layers: {
|
foregroundSubtler: new Color('#cdd6f4', 'dark').lower(0.1).translucify(0.55),
|
||||||
root: {
|
colors: {
|
||||||
colors: {
|
primary: new Color('#cba6f7', 'dark'),
|
||||||
gray: '#7f8fb0',
|
secondary: new Color('#bac2de', 'dark'),
|
||||||
red: '#ec3f87',
|
info: new Color('#89b4fa', 'dark'),
|
||||||
orange: '#ff8000',
|
success: new Color('#a6e3a1', 'dark'),
|
||||||
yellow: '#e7cf24',
|
notice: new Color('#f9e2af', 'dark'),
|
||||||
green: '#00d365',
|
warning: new Color('#fab387', 'dark'),
|
||||||
blue: '#0090ff',
|
danger: new Color('#f38ba8', 'dark'),
|
||||||
pink: '#ea6cea',
|
},
|
||||||
violet: '#ac6cff',
|
components: {
|
||||||
|
dialog: {
|
||||||
|
background: new Color('#181825', 'dark'),
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
background: new Color('#1e1e2e', 'dark'),
|
||||||
|
},
|
||||||
|
appHeader: {
|
||||||
|
background: new Color('#11111b', 'dark'),
|
||||||
|
},
|
||||||
|
responsePane: {
|
||||||
|
background: new Color('#1e1e2e', 'dark'),
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
colors: {
|
||||||
|
primary: new Color('#cba6f7', 'dark').lower(0.2),
|
||||||
|
secondary: new Color('#bac2de', 'dark').lower(0.2),
|
||||||
|
info: new Color('#89b4fa', 'dark').lower(0.2),
|
||||||
|
success: new Color('#a6e3a1', 'dark').lower(0.2),
|
||||||
|
notice: new Color('#f9e2af', 'dark').lower(0.2),
|
||||||
|
warning: new Color('#fab387', 'dark').lower(0.2),
|
||||||
|
danger: new Color('#f38ba8', 'dark').lower(0.2),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CSSVariables = Record<string, string | undefined>;
|
||||||
|
|
||||||
|
function themeVariables(theme?: ThemeComponent, base?: CSSVariables): CSSVariables | null {
|
||||||
|
const vars: CSSVariables = {
|
||||||
|
'--background': theme?.background?.css(),
|
||||||
|
'--background-highlight':
|
||||||
|
theme?.backgroundHighlight?.css() ?? theme?.background?.lift(0.11).css(),
|
||||||
|
'--background-highlight-secondary':
|
||||||
|
theme?.backgroundHighlightSecondary?.css() ?? theme?.background?.lift(0.06).css(),
|
||||||
|
'--background-active':
|
||||||
|
theme?.backgroundActive?.css() ?? theme?.colors?.primary?.lower(0.2).translucify(0.8).css(),
|
||||||
|
'--background-backdrop': theme?.background?.lower(0.2).translucify(0.2).css(),
|
||||||
|
'--background-selection': theme?.colors?.primary?.lower(0.1).translucify(0.7).css(),
|
||||||
|
'--fg': theme?.foreground?.css(),
|
||||||
|
'--fg-subtle': theme?.foregroundSubtle?.css() ?? theme?.foreground?.lower(0.2).css(),
|
||||||
|
'--fg-subtler': theme?.foregroundSubtler?.css() ?? theme?.foreground?.lower(0.3).css(),
|
||||||
|
'--border-focus': theme?.colors?.info?.css(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [color, value] of Object.entries(theme?.colors ?? {})) {
|
||||||
|
vars[`--fg-${color}`] = (value as Color).css();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend with base
|
||||||
|
for (const [k, v] of Object.entries(vars)) {
|
||||||
|
if (!v && base?.[k]) {
|
||||||
|
vars[k] = base[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
function placeholderColorVariables(color: Color): CSSVariables {
|
||||||
|
return {
|
||||||
|
'--fg': color.lift(0.6).css(),
|
||||||
|
'--fg-subtle': color.lift(0.4).css(),
|
||||||
|
'--fg-subtler': color.css(),
|
||||||
|
'--background': color.lower(0.2).translucify(0.8).css(),
|
||||||
|
'--background-highlight': color.lower(0.2).translucify(0.2).css(),
|
||||||
|
'--background-highlight-secondary': color.lower(0.1).translucify(0.7).css(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function bannerColorVariables(color: Color): CSSVariables {
|
||||||
|
return {
|
||||||
|
'--fg': color.lift(0.8).css(),
|
||||||
|
'--fg-subtle': color.translucify(0.3).css(),
|
||||||
|
'--fg-subtler': color.css(),
|
||||||
|
'--background': color.css(),
|
||||||
|
'--background-highlight': color.lift(0.3).translucify(0.4).css(),
|
||||||
|
'--background-highlight-secondary': color.translucify(0.9).css(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buttonSolidColorVariables(color: Color): CSSVariables {
|
||||||
|
return {
|
||||||
|
'--fg': new Color('white', 'dark').css(),
|
||||||
|
'--background': color.lower(0.15).css(),
|
||||||
|
'--background-highlight': color.css(),
|
||||||
|
'--background-highlight-secondary': color.lower(0.3).css(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buttonBorderColorVariables(color: Color): CSSVariables {
|
||||||
|
return {
|
||||||
|
'--fg': color.lift(0.6).css(),
|
||||||
|
'--fg-subtle': color.lift(0.4).css(),
|
||||||
|
'--fg-subtler': color.lift(0.4).translucify(0.6).css(),
|
||||||
|
'--background': Color.transparent().css(),
|
||||||
|
'--background-highlight': color.translucify(0.8).css(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function variablesToCSS(selector: string | null, vars: CSSVariables | null): string | null {
|
||||||
|
if (vars == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const css = Object.entries(vars ?? {})
|
||||||
|
.filter(([, value]) => value)
|
||||||
|
.map(([name, value]) => `${name}: ${value};`)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return selector == null ? css : `${selector} {\n${indent(css)}\n}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function componentCSS(
|
||||||
|
component: ComponentName,
|
||||||
|
components?: YaakTheme['components'],
|
||||||
|
): string | null {
|
||||||
|
if (components == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeVars = themeVariables(components[component]);
|
||||||
|
return variablesToCSS(`.x-theme-${component}`, themeVars);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buttonCSS(color: ColorName, colors?: Partial<RootColors>): string | null {
|
||||||
|
const cssColor = colors?.[color];
|
||||||
|
if (cssColor == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
variablesToCSS(`.x-theme-button--solid--${color}`, buttonSolidColorVariables(cssColor)),
|
||||||
|
variablesToCSS(`.x-theme-button--border--${color}`, buttonBorderColorVariables(cssColor)),
|
||||||
|
].join('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function bannerCSS(color: ColorName, colors?: Partial<RootColors>): string | null {
|
||||||
|
const cssColor = colors?.[color];
|
||||||
|
if (cssColor == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [variablesToCSS(`.x-theme-banner--${color}`, bannerColorVariables(cssColor))].join('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function placeholderCSS(color: ColorName, colors?: Partial<RootColors>): string | null {
|
||||||
|
const cssColor = colors?.[color];
|
||||||
|
if (cssColor == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
variablesToCSS(`.x-theme-placeholder-widget--${color}`, placeholderColorVariables(cssColor)),
|
||||||
|
].join('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isThemeDark(theme: YaakTheme): boolean {
|
||||||
|
if (theme.background && theme.foreground) {
|
||||||
|
return theme.foreground.lighterThan(theme.background);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setThemeOnDocument(yaakThemes.yaakLight!);
|
||||||
|
setThemeOnDocument(yaakThemes.yaakDark!);
|
||||||
|
|
||||||
|
export function getThemeCSS(theme: YaakTheme): string {
|
||||||
|
let themeCSS = '';
|
||||||
|
try {
|
||||||
|
const baseCss = variablesToCSS(null, themeVariables(theme));
|
||||||
|
const { components, colors } = theme;
|
||||||
|
themeCSS = [
|
||||||
|
baseCss,
|
||||||
|
...Object.keys(components ?? {}).map((key) =>
|
||||||
|
componentCSS(key as ComponentName, theme.components),
|
||||||
|
),
|
||||||
|
...Object.keys(colors ?? {}).map((key) =>
|
||||||
|
buttonCSS(key as ColorName, theme.components?.button?.colors ?? colors),
|
||||||
|
),
|
||||||
|
...Object.keys(colors ?? {}).map((key) =>
|
||||||
|
bannerCSS(key as ColorName, theme.components?.banner?.colors ?? colors),
|
||||||
|
),
|
||||||
|
...Object.keys(colors ?? {}).map((key) =>
|
||||||
|
placeholderCSS(key as ColorName, theme.components?.placeholder?.colors ?? colors),
|
||||||
|
),
|
||||||
|
].join('\n\n');
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
return themeCSS;
|
||||||
|
}
|
||||||
|
|
||||||
export function setAppearanceOnDocument(appearance: Appearance = DEFAULT_APPEARANCE) {
|
export function setAppearanceOnDocument(appearance: Appearance = DEFAULT_APPEARANCE) {
|
||||||
const resolvedAppearance = appearance === 'system' ? getPreferredAppearance() : appearance;
|
const resolvedAppearance = appearance === 'system' ? getPreferredAppearance() : appearance;
|
||||||
const theme = resolvedAppearance === 'dark' ? darkTheme : lightTheme;
|
|
||||||
|
|
||||||
document.documentElement.setAttribute('data-resolved-appearance', resolvedAppearance);
|
document.documentElement.setAttribute('data-resolved-appearance', resolvedAppearance);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setThemeOnDocument(theme: YaakTheme) {
|
||||||
document.documentElement.setAttribute('data-theme', theme.name);
|
document.documentElement.setAttribute('data-theme', theme.name);
|
||||||
|
|
||||||
let existingStyleEl = document.head.querySelector(`style[data-theme-definition]`);
|
const darkOrLight = isThemeDark(theme) ? 'dark' : 'light';
|
||||||
|
let existingStyleEl = document.head.querySelector(`style[data-theme-definition=${darkOrLight}]`);
|
||||||
if (!existingStyleEl) {
|
if (!existingStyleEl) {
|
||||||
const styleEl = document.createElement('style');
|
const styleEl = document.createElement('style');
|
||||||
document.head.appendChild(styleEl);
|
document.head.appendChild(styleEl);
|
||||||
@@ -78,16 +315,12 @@ export function setAppearanceOnDocument(appearance: Appearance = DEFAULT_APPEARA
|
|||||||
}
|
}
|
||||||
|
|
||||||
existingStyleEl.textContent = [
|
existingStyleEl.textContent = [
|
||||||
`/* ${darkTheme.name} */`,
|
`/* ${theme.name} */`,
|
||||||
`[data-resolved-appearance="dark"] {`,
|
`[data-resolved-appearance="${isThemeDark(theme) ? 'dark' : 'light'}"] {`,
|
||||||
...generateCSS(darkTheme).map(toTailwindVariable),
|
getThemeCSS(theme),
|
||||||
'}',
|
|
||||||
`/* ${lightTheme.name} */`,
|
|
||||||
`[data-resolved-appearance="light"] {`,
|
|
||||||
...generateCSS(lightTheme).map(toTailwindVariable),
|
|
||||||
'}',
|
'}',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
existingStyleEl.setAttribute('data-theme-definition', '');
|
existingStyleEl.setAttribute('data-theme-definition', darkOrLight);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPreferredAppearance(): Appearance {
|
export function getPreferredAppearance(): Appearance {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#root {
|
#root {
|
||||||
@apply w-full h-full overflow-hidden text-gray-900 bg-gray-50;
|
@apply w-full h-full overflow-hidden text-fg bg-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
&:hover {
|
&:hover {
|
||||||
&.scrollbar-thumb,
|
&.scrollbar-thumb,
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
@apply bg-gray-500/30 hover:bg-gray-500/50 rounded-full;
|
@apply bg-background-highlight-secondary hover:bg-fg-subtler rounded-full;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
iframe {
|
iframe {
|
||||||
&::-webkit-scrollbar-corner,
|
&::-webkit-scrollbar-corner,
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
@apply bg-gray-100;
|
@apply bg-background-highlight-secondary !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,58 +50,45 @@ module.exports = {
|
|||||||
'4xs': '0.6rem',
|
'4xs': '0.6rem',
|
||||||
'3xs': '0.675rem',
|
'3xs': '0.675rem',
|
||||||
'2xs': '0.75rem',
|
'2xs': '0.75rem',
|
||||||
xs: '0.8rem',
|
'xs': '0.8rem',
|
||||||
sm: '0.9rem',
|
'sm': '0.9rem',
|
||||||
base: '1rem',
|
'base': '1rem',
|
||||||
xl: '1.25rem',
|
'xl': '1.25rem',
|
||||||
'2xl': '1.5rem',
|
'2xl': '1.5rem',
|
||||||
'3xl': '2rem',
|
'3xl': '2rem',
|
||||||
'4xl': '2.5rem',
|
'4xl': '2.5rem',
|
||||||
'5xl': '3rem',
|
'5xl': '3rem',
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
selection: 'hsl(var(--color-violet-500) / 0.3)',
|
'transparent': 'transparent',
|
||||||
focus: 'hsl(var(--color-blue-500) / 0.7)',
|
'placeholder': 'var(--fg-subtler)',
|
||||||
invalid: 'hsl(var(--color-red-500))',
|
'selection': 'var(--background-selection)',
|
||||||
highlight: 'hsl(var(--color-gray-500) / 0.3)',
|
|
||||||
highlightSecondary: 'hsl(var(--color-gray-500) / 0.15)',
|
// New theme values
|
||||||
transparent: 'transparent',
|
|
||||||
white: 'hsl(0 100% 100% / <alpha-value>)',
|
'border-focus': 'var(--border-focus)',
|
||||||
black: 'hsl(0 100% 0% / <alpha-value>)',
|
'fg': 'var(--fg)',
|
||||||
placeholder: 'hsl(var(--color-gray-400) / <alpha-value>)',
|
'fg-danger': 'var(--fg-danger)',
|
||||||
red: color('red'),
|
'fg-subtle': 'var(--fg-subtle)',
|
||||||
orange: color('orange'),
|
'fg-subtler': 'var(--fg-subtler)',
|
||||||
yellow: color('yellow'),
|
'fg-primary': 'var(--fg-primary)',
|
||||||
blue: color('blue'),
|
'fg-secondary': 'var(--fg-secondary)',
|
||||||
green: color('green'),
|
'fg-success': 'var(--fg-success)',
|
||||||
pink: color('pink'),
|
'fg-info': 'var(--fg-info)',
|
||||||
violet: color('violet'),
|
'fg-notice': 'var(--fg-notice)',
|
||||||
gray: color('gray'),
|
'fg-warning': 'var(--fg-warning)',
|
||||||
|
'background': 'var(--background)',
|
||||||
|
'background-active': 'var(--background-active)',
|
||||||
|
'background-highlight': 'var(--background-highlight)',
|
||||||
|
'background-highlight-secondary': 'var(--background-highlight-secondary)',
|
||||||
|
'background-backdrop': 'var(--background-backdrop)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
require('@tailwindcss/container-queries'),
|
require('@tailwindcss/container-queries'),
|
||||||
plugin(function ({ addVariant }) {
|
plugin(function ({addVariant}) {
|
||||||
addVariant('hocus', ['&:hover', '&:focus-visible', '&.focus:focus']);
|
addVariant('hocus', ['&:hover', '&:focus-visible', '&.focus:focus']);
|
||||||
addVariant('focus-visible-or-class', ['&:focus-visible', '&.focus:focus']);
|
addVariant('focus-visible-or-class', ['&:focus-visible']);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
function color(name) {
|
|
||||||
return {
|
|
||||||
0: `hsl(var(--color-${name}-0) / <alpha-value>)`,
|
|
||||||
50: `hsl(var(--color-${name}-50) / <alpha-value>)`,
|
|
||||||
100: `hsl(var(--color-${name}-100) / <alpha-value>)`,
|
|
||||||
200: `hsl(var(--color-${name}-200) / <alpha-value>)`,
|
|
||||||
300: `hsl(var(--color-${name}-300) / <alpha-value>)`,
|
|
||||||
400: `hsl(var(--color-${name}-400) / <alpha-value>)`,
|
|
||||||
500: `hsl(var(--color-${name}-500) / <alpha-value>)`,
|
|
||||||
600: `hsl(var(--color-${name}-600) / <alpha-value>)`,
|
|
||||||
700: `hsl(var(--color-${name}-700) / <alpha-value>)`,
|
|
||||||
800: `hsl(var(--color-${name}-800) / <alpha-value>)`,
|
|
||||||
900: `hsl(var(--color-${name}-900) / <alpha-value>)`,
|
|
||||||
950: `hsl(var(--color-${name}-950) / <alpha-value>)`,
|
|
||||||
1000: `hsl(var(--color-${name}-1000) / <alpha-value>)`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user