diff --git a/AGENTS.md b/AGENTS.md index 6576fb9..ffdb8d3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,10 +16,21 @@ The codebase uses Docker to run third-party services locally (Meilisearch for se - Vanilla CSS modules - Zod for schemas +# General + +- If you need to add dependencies, add them with `bun install XYZ` and ensure that the bun.lock file is updated and part of the commit or PR. + # Typescript - Use `== null` (double equals null) instead of either `=== null` or `=== undefined` (triple equals null / undefined), and the same for `!=`. This is to make `null` and `undefined` mean the same thing everywhere in our codebase to avoid any potential serialisation/deserialisation confusion or issues. - Avoid use of `any` unless absolutely necessary. +- Prefer early-exit if statements rather than nested if statements. + +# CSS + +- Use design tokens present in the root layout as CSS variables +- Any metric should be based on a multiple of gridBaseline. Fractional multipliers are okay if necessary, e.g. calc(1.5 \* var(--gridBaseline)) +- Metrics that aren't dependent on "UI scale" should not be based on gridBaseline, e.g. font sizes, line heights, percentage border radii, 1px thin borders, etc. # Commands @@ -53,7 +64,7 @@ If you're receiving a request through an issue or PR comment, always: - Based on your max turn limit, you may need to break up your proposal into multiple steps. If so, report back to the requester with these multiple proposed stages and implement them one by one instead, after confirmation from the requester. - Most important - if the request is (verbatim and exactly) "test" with no other words other than the trigger phrase ping itself, respond with ONLY "meow", with no other words, context, or punctuation. - When creating a PR, always add the relevant CODEOWNER as a reviewer on the PR with the `-r` argument on `gh pr create`. -- When updating a PR to address review comments, always re-request a review from the person that reviewed it using `gh pr edit` using the `--add-reviewer` argument. +- When updating a PR to address review comments, always re-request a review from the person that reviewed it using `gh pr edit` using the `--add-reviewer` argument, and "Resolve" any review comments that were addressed via the Github pull request API. # English style diff --git a/bun.lock b/bun.lock index afedee7..f8bbe9e 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "paradb", diff --git a/src/app/page.tsx b/src/app/page.tsx index f04a7d9..7101cf1 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -20,6 +20,7 @@ import styles from './page.module.css'; import { Search } from './search'; import useInfiniteScroll from 'hooks/useInfiniteScroll'; import { useSkeletonRef } from 'app/skeleton_provider'; +import { Tooltip } from 'ui/base/tooltip/tooltip'; export default function Page() { return ( diff --git a/src/ui/base/tooltip/tooltip.module.css b/src/ui/base/tooltip/tooltip.module.css new file mode 100644 index 0000000..40fa53d --- /dev/null +++ b/src/ui/base/tooltip/tooltip.module.css @@ -0,0 +1,72 @@ +.tooltip { + background: var(--colorBackground); + color: var(--colorForeground); + padding: var(--gridBaseline) calc(var(--gridBaseline) * 1.5); + border-radius: calc(var(--gridBaseline) * 0.5); + border: 1px solid var(--colorPurple); + box-sizing: border-box; + font-size: 14px; + max-width: calc(var(--gridBaseline) * 32); + transition: opacity 100ms ease-in-out; +} + +.tooltip[data-placement='top'] { + margin-bottom: var(--gridBaseline); +} + +.tooltip[data-placement='bottom'] { + margin-top: var(--gridBaseline); +} + +.tooltip[data-placement='left'] { + margin-right: var(--gridBaseline); +} + +.tooltip[data-placement='right'] { + margin-left: var(--gridBaseline); +} + +.arrow { + position: absolute; +} + +.arrow svg { + fill: var(--colorBackground); + display: block; +} + +.tooltip[data-placement='top'] .arrow { + bottom: calc(var(--gridBaseline) * -0.5); +} + +.tooltip[data-placement='bottom'] .arrow { + top: calc(var(--gridBaseline) * -1); +} + +.tooltip[data-placement='left'] .arrow { + right: calc(var(--gridBaseline) * -0.5); +} + +.tooltip[data-placement='right'] .arrow { + left: calc(var(--gridBaseline) * -1); +} + +.tooltip[data-placement='bottom'] .arrow svg { + transform: rotate(180deg); +} + +.tooltip[data-placement='left'] .arrow svg { + transform: rotate(-90deg); +} + +.tooltip[data-placement='right'] .arrow svg { + transform: rotate(90deg); +} + +.tooltip[data-entering] { + opacity: 0; +} + +.tooltip[data-exiting] { + opacity: 0; +} diff --git a/src/ui/base/tooltip/tooltip.tsx b/src/ui/base/tooltip/tooltip.tsx new file mode 100644 index 0000000..44f2c3a --- /dev/null +++ b/src/ui/base/tooltip/tooltip.tsx @@ -0,0 +1,40 @@ +'use client'; + +import React from 'react'; +import { + Tooltip as AriaTooltip, + TooltipTrigger, + OverlayArrow, + type TooltipProps as AriaTooltipProps, +} from 'react-aria-components'; +import styles from './tooltip.module.css'; +import { colors } from '../design_system/design_tokens'; + +export type TooltipProps = { + /** Content to display in the tooltip */ + content: React.ReactNode; + /** Position of the tooltip relative to the trigger */ + placement?: AriaTooltipProps['placement']; + /** Delay in ms before showing the tooltip */ + delay?: number; + /** The trigger element */ + children: React.ReactElement; +}; + +export const Tooltip = (props: TooltipProps) => { + const { content, placement = 'top', delay = 300, children } = props; + + return ( + + {children} + + + + + + + {content} + + + ); +};