Skip to content

Commit

Permalink
Introduce styled tooltips. Refactor to be cross-platform (#151)
Browse files Browse the repository at this point in the history
* Introduce styled tooltips. Refactor to be cross-platform

* Fix lint issues
  • Loading branch information
nichochar authored Jul 23, 2024
1 parent 0929f2f commit 78d4f0b
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 33 deletions.
1 change: 1 addition & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.1.2",
"@srcbook/shared": "workspace:^",
"@uiw/codemirror-themes": "^4.22.2",
"@uiw/react-codemirror": "^4.22.0",
Expand Down
44 changes: 31 additions & 13 deletions packages/web/src/components/cells/code.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import Shortcut from '@/components/keyboard-shortcut';
import { useNavigate } from 'react-router-dom';
import { useHotkeys } from 'react-hotkeys-hook';
import CodeMirror, { keymap, Prec } from '@uiw/react-codemirror';
Expand Down Expand Up @@ -201,15 +203,22 @@ export default function CodeCell(props: {
cell.status === 'running' ? 'opacity-100' : '',
)}
>
<Button
variant="icon"
size="icon"
disabled={promptMode !== 'off'}
onClick={() => setPromptMode('idle')}
tabIndex={1}
>
<Sparkles size={16} />
</Button>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Button
variant="icon"
size="icon"
disabled={promptMode !== 'off'}
onClick={() => setPromptMode('idle')}
tabIndex={1}
>
<Sparkles size={16} />
</Button>
</TooltipTrigger>
<TooltipContent>Edit cell using AI</TooltipContent>
</Tooltip>
</TooltipProvider>
{promptMode === 'idle' && (
<Button
variant="default"
Expand All @@ -233,10 +242,19 @@ export default function CodeCell(props: {
</Button>
)}
{cell.status === 'idle' && (
<Button size="default-with-icon" onClick={runCell} tabIndex={1}>
<Play size={16} />
Run
</Button>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Button size="default-with-icon" onClick={runCell} tabIndex={1}>
<Play size={16} />
Run
</Button>
</TooltipTrigger>
<TooltipContent>
<Shortcut keys={['mod', 'enter']} /> to run cell
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</>
)}
Expand Down
65 changes: 65 additions & 0 deletions packages/web/src/components/keyboard-shortcut.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react';

type Platform = 'mac' | 'windows' | 'linux' | 'other';
type KeyType = 'mod' | 'alt';

const getPlatform = () => {
const platform = navigator.platform.toLowerCase();
const userAgent = navigator.userAgent.toLowerCase();

if (platform.includes('mac') || userAgent.includes('mac')) {
return 'mac';
} else if (platform.includes('win') || userAgent.includes('win')) {
return 'windows';
} else if (platform.includes('linux') || userAgent.includes('linux')) {
return 'linux';
} else {
return 'other';
}
};

const keyMappings: Record<KeyType, Record<Platform, string>> = {
mod: {
mac: '⌘',
windows: 'Ctrl',
linux: 'Ctrl',
other: 'Ctrl',
},
alt: {
mac: '⌥',
windows: 'Alt',
linux: 'Alt',
other: 'Alt',
},
};

const getPlatformSpecificKey = (keyType: KeyType): string => {
const platform = getPlatform();
return keyMappings[keyType][platform];
};

export default function Shortcut({ keys }: { keys: string[] }) {
// Replace keys that are in the keyMappings with the platform specific key
keys = keys.map((key) => {
if (key === 'mod') {
return getPlatformSpecificKey('mod');
} else if (key === 'alt') {
return getPlatformSpecificKey('alt');
} else {
return key;
}
});
return (
<>
{keys.map((key) => {
return (
<React.Fragment key={key}>
<span className="font-mono bg-background text-foreground border py-[1px] px-1.5 rounded-sm drop-shadow-key mx-0.5">
{key}
</span>
</React.Fragment>
);
})}
</>
);
}
31 changes: 11 additions & 20 deletions packages/web/src/components/keyboard-shortcuts-dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import Shortcut from '@/components/keyboard-shortcut';
import {
Dialog,
DialogContent,
Expand All @@ -7,20 +7,11 @@ import {
DialogTitle,
} from '@/components/ui/dialog';

function ShortCut({ keys, description }: { keys: string[]; description: string }) {
function ShortcutRow({ keys, description }: { keys: string[]; description: string }) {
return (
<div className="grid grid-cols-4 w-full py-2">
<p className="col-span-1">
{keys.map((key, i) => {
return (
<React.Fragment key={key}>
<span className="font-mono bg-primary text-primary-foreground py-[1px] px-1.5 rounded shadow-md">
{key}
</span>
{i < keys.length - 1 && <span className="mx-1.5">+</span>}
</React.Fragment>
);
})}
<Shortcut keys={keys} />
</p>
<p className="col-span-3">{description}</p>
</div>
Expand All @@ -42,16 +33,16 @@ export default function KeyboardShortcutsDialog({
<DialogDescription asChild>
<div>
<h5 className="font-semibold pt-4 pb-2">Global</h5>
<ShortCut keys={['?']} description="show this dialog" />
<ShortCut keys={['', 'i']} description="open NPM package install modal" />
<ShortcutRow keys={['?']} description="show this dialog" />
<ShortcutRow keys={['mod', 'i']} description="open NPM package install modal" />
<h5 className="font-semibold pt-6 pb-2">Markdown edit</h5>
<ShortCut keys={['esc']} description="switch back to preview mode" />
<ShortCut keys={['', '↵']} description="switch back to preview mode" />
<ShortcutRow keys={['esc']} description="switch back to preview mode" />
<ShortcutRow keys={['mod', '↵']} description="switch back to preview mode" />
<h5 className="font-semibold pt-6 pb-2">Code cell edit</h5>
<ShortCut keys={['', '↵']} description="run cell" />
<ShortCut keys={['', '/']} description="toggle lines comment" />
<ShortCut keys={['', '↑']} description="move lines up" />
<ShortCut keys={['', '↓']} description="move lines down" />
<ShortcutRow keys={['mod', '↵']} description="run cell" />
<ShortcutRow keys={['mod', '/']} description="toggle lines comment" />
<ShortcutRow keys={['alt', '↑']} description="move lines up" />
<ShortcutRow keys={['alt', '↓']} description="move lines down" />
</div>
</DialogDescription>
</DialogHeader>
Expand Down
29 changes: 29 additions & 0 deletions packages/web/src/components/ui/tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';

import { cn } from '@/lib/utils';

const TooltipProvider = TooltipPrimitive.Provider;

const Tooltip = TooltipPrimitive.Root;

const TooltipTrigger = TooltipPrimitive.Trigger;

const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
side="bottom"
className={cn(
'z-50 overflow-hidden rounded-sm bg-muted px-1.5 py-1.5 text-xs font-medium border animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;

export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
3 changes: 3 additions & 0 deletions packages/web/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ module.exports = {
'blue-80': 'hsl(var(--sb-blue-80))',
},
},
dropShadow: {
key: '0 1px 0 hsl(var(--border))', // for keyboard shortcuts. border-border 1px straight down
},
borderRadius: {
sm: '0.1875rem', // 3px
mid: '0.375rem', // 6px
Expand Down
Loading

0 comments on commit 78d4f0b

Please sign in to comment.