From 386719540e67bc582e57baa79f77fd35fbf09279 Mon Sep 17 00:00:00 2001 From: Greg Date: Thu, 27 Nov 2025 14:32:02 +0100 Subject: [PATCH 1/3] Add radio group component --- packages/base/package.json | 1 + .../base/src/shared/components/RadioGroup.tsx | 40 ++++++++++++++ packages/base/style/base.css | 1 + packages/base/style/shared/radioGroup.css | 55 +++++++++++++++++++ yarn.lock | 29 ++++++++++ 5 files changed, 126 insertions(+) create mode 100644 packages/base/src/shared/components/RadioGroup.tsx create mode 100644 packages/base/style/shared/radioGroup.css diff --git a/packages/base/package.json b/packages/base/package.json index 9ced9974f..d15b86e01 100644 --- a/packages/base/package.json +++ b/packages/base/package.json @@ -67,6 +67,7 @@ "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-radio-group": "^1.3.8", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.12", diff --git a/packages/base/src/shared/components/RadioGroup.tsx b/packages/base/src/shared/components/RadioGroup.tsx new file mode 100644 index 000000000..8f78ad32a --- /dev/null +++ b/packages/base/src/shared/components/RadioGroup.tsx @@ -0,0 +1,40 @@ +import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'; +import { CircleIcon } from 'lucide-react'; +import * as React from 'react'; + +import { cn } from './utils'; + +function RadioGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function RadioGroupItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ); +} + +export { RadioGroup, RadioGroupItem }; diff --git a/packages/base/style/base.css b/packages/base/style/base.css index 9b665e9d6..38e9dc70c 100644 --- a/packages/base/style/base.css +++ b/packages/base/style/base.css @@ -21,6 +21,7 @@ @import url('./shared/dropdownMenu.css'); @import url('./shared/badge.css'); @import url('./shared/checkbox.css'); +@import url('./shared/radioGroup.css'); @import url('./shared/switch.css'); .errors { diff --git a/packages/base/style/shared/radioGroup.css b/packages/base/style/shared/radioGroup.css new file mode 100644 index 000000000..8593a84a1 --- /dev/null +++ b/packages/base/style/shared/radioGroup.css @@ -0,0 +1,55 @@ +.jgis-radio-group { + display: grid; + gap: 0.75rem; +} + +.jgis-radio-group-item { + aspect-ratio: 1 / 1; + width: 1rem; + height: 1rem; + flex-shrink: 0; + border-radius: 9999px !important; + border: 1px solid var(--jp-border-color0); + background-color: var(--jp-layout-color2); + color: var(--jp-ui-font-color0); + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + transition: + color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + outline: none; + cursor: pointer; +} + +.jgis-radio-group-item:focus-visible { + border-color: var(--jp-ui-font-color0); + box-shadow: 0 0 0 3px + color-mix(in srgb, var(--jp-ui-font-color0), transparent 50%); +} + +.jgis-radio-group-item[aria-invalid='true'] { + border-color: var(--jp-error-color0, #dc2626); + box-shadow: 0 0 0 3px + color-mix(in srgb, var(--jp-error-color0, #dc2626), transparent 80%); +} + +.jgis-radio-group-item:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +.jgis-radio-group-indicator { + position: relative; + display: flex; + align-items: center; + justify-content: center; +} + +.jgis-radio-group-indicator-icon { + position: absolute; + top: 50%; + left: 50%; + width: 0.5rem; + height: 0.5rem; + transform: translate(-50%, -50%); + fill: currentColor; +} diff --git a/yarn.lock b/yarn.lock index af96bc49b..707aaf3e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -975,6 +975,7 @@ __metadata: "@radix-ui/react-checkbox": ^1.3.2 "@radix-ui/react-dropdown-menu": ^2.1.15 "@radix-ui/react-popover": ^1.1.14 + "@radix-ui/react-radio-group": ^1.3.8 "@radix-ui/react-slot": ^1.2.3 "@radix-ui/react-switch": ^1.2.6 "@radix-ui/react-tabs": ^1.1.12 @@ -3228,6 +3229,34 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-radio-group@npm:^1.3.8": + version: 1.3.8 + resolution: "@radix-ui/react-radio-group@npm:1.3.8" + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2 + "@radix-ui/react-context": 1.1.2 + "@radix-ui/react-direction": 1.1.1 + "@radix-ui/react-presence": 1.1.5 + "@radix-ui/react-primitive": 2.1.3 + "@radix-ui/react-roving-focus": 1.1.11 + "@radix-ui/react-use-controllable-state": 1.2.2 + "@radix-ui/react-use-previous": 1.1.1 + "@radix-ui/react-use-size": 1.1.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 5e35970ec8965f398f15ff9b7d52c74125119d4af303fe7cd0c91f95d00692e4630d3db01e068eba833669dfb813a14b06cd80587acde028bd4cd747cb2aff41 + languageName: node + linkType: hard + "@radix-ui/react-roving-focus@npm:1.1.11": version: 1.1.11 resolution: "@radix-ui/react-roving-focus@npm:1.1.11" From 5a7f42c5ef3fb25b9ea7010ee4ad998e098487cf Mon Sep 17 00:00:00 2001 From: Greg Date: Thu, 27 Nov 2025 14:50:44 +0100 Subject: [PATCH 2/3] Add Command and Dialog --- packages/base/package.json | 2 + .../base/src/shared/components/Command.tsx | 182 ++++++++++++++++++ .../base/src/shared/components/Dialog.tsx | 132 +++++++++++++ packages/base/style/base.css | 1 + packages/base/style/shared/dialog.css | 177 +++++++++++++++++ yarn.lock | 74 ++++++- 6 files changed, 565 insertions(+), 3 deletions(-) create mode 100644 packages/base/src/shared/components/Command.tsx create mode 100644 packages/base/src/shared/components/Dialog.tsx create mode 100644 packages/base/style/shared/dialog.css diff --git a/packages/base/package.json b/packages/base/package.json index d15b86e01..28627550a 100644 --- a/packages/base/package.json +++ b/packages/base/package.json @@ -65,6 +65,7 @@ "@mapbox/vector-tile": "^2.0.3", "@naisutech/react-tree": "^3.0.1", "@radix-ui/react-checkbox": "^1.3.2", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-radio-group": "^1.3.8", @@ -77,6 +78,7 @@ "ajv": "^8.14.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "colormap": "^2.3.2", "d3-color": "^3.1.0", "date-fns": "^4.1.0", diff --git a/packages/base/src/shared/components/Command.tsx b/packages/base/src/shared/components/Command.tsx new file mode 100644 index 000000000..d4056c62e --- /dev/null +++ b/packages/base/src/shared/components/Command.tsx @@ -0,0 +1,182 @@ +import { Command as CommandPrimitive } from 'cmdk'; +import { SearchIcon } from 'lucide-react'; +import * as React from 'react'; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/src/shared/components/Dialog'; +import { cn } from './utils'; + +function Command({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function CommandDialog({ + title = 'Command Palette', + description = 'Search for a command to run...', + children, + className, + showCloseButton = true, + ...props +}: React.ComponentProps & { + title?: string; + description?: string; + className?: string; + showCloseButton?: boolean; +}) { + return ( + + + {title} + {description} + + + + {children} + + + + ); +} + +function CommandInput({ + className, + ...props +}: React.ComponentProps) { + return ( +
+ + +
+ ); +} + +function CommandList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function CommandEmpty({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function CommandGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function CommandSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function CommandItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function CommandShortcut({ + className, + ...props +}: React.ComponentProps<'span'>) { + return ( + + ); +} + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +}; diff --git a/packages/base/src/shared/components/Dialog.tsx b/packages/base/src/shared/components/Dialog.tsx new file mode 100644 index 000000000..02fec4c2c --- /dev/null +++ b/packages/base/src/shared/components/Dialog.tsx @@ -0,0 +1,132 @@ +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { XIcon } from 'lucide-react'; +import * as React from 'react'; + +import { cn } from './utils'; + +function Dialog({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean; +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ); +} + +function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}; diff --git a/packages/base/style/base.css b/packages/base/style/base.css index 38e9dc70c..1a6bf1c58 100644 --- a/packages/base/style/base.css +++ b/packages/base/style/base.css @@ -22,6 +22,7 @@ @import url('./shared/badge.css'); @import url('./shared/checkbox.css'); @import url('./shared/radioGroup.css'); +@import url('./shared/dialog.css'); @import url('./shared/switch.css'); .errors { diff --git a/packages/base/style/shared/dialog.css b/packages/base/style/shared/dialog.css new file mode 100644 index 000000000..965022bd3 --- /dev/null +++ b/packages/base/style/shared/dialog.css @@ -0,0 +1,177 @@ +@keyframes dialog-fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes dialog-fade-out { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +@keyframes dialog-zoom-in { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes dialog-zoom-out { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.95); + } +} + +.jgis-dialog-overlay { + position: fixed; + inset: 0; + z-index: 50; + background-color: color-mix(in srgb, black, transparent 50%); +} + +.jgis-dialog-overlay[data-state='open'] { + animation: dialog-fade-in 0.2s ease-in-out; +} + +.jgis-dialog-overlay[data-state='closed'] { + animation: dialog-fade-out 0.2s ease-in-out; +} + +.jgis-dialog-content { + position: fixed; + top: 50%; + left: 50%; + z-index: 50; + display: grid; + width: 100%; + max-width: calc(100% - 2rem); + transform: translate(-50%, -50%); + gap: 1rem; + border-radius: 0.5rem; + border: 1px solid var(--jp-border-color0); + background-color: var(--jp-layout-color0); + padding: 1.5rem; + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + transition-duration: 200ms; +} + +@media (min-width: 640px) { + .jgis-dialog-content { + max-width: 32rem; + } +} + +.jgis-dialog-content[data-state='open'] { + animation: + dialog-fade-in 0.2s ease-in-out, + dialog-zoom-in 0.2s ease-in-out; +} + +.jgis-dialog-content[data-state='closed'] { + animation: + dialog-fade-out 0.2s ease-in-out, + dialog-zoom-out 0.2s ease-in-out; +} + +.jgis-dialog-close { + position: absolute; + top: 1rem; + right: 1rem; + border-radius: 0.125rem; + opacity: 0.7; + transition: opacity 0.15s ease-in-out; + outline: none; +} + +.jgis-dialog-close:hover { + opacity: 1; +} + +.jgis-dialog-close:focus { + opacity: 1; + box-shadow: 0 0 0 2px var(--jp-ui-font-color0); + outline: none; +} + +.jgis-dialog-close:disabled { + pointer-events: none; +} + +.jgis-dialog-close[data-state='open'] { + background-color: var(--jp-layout-color2); + color: var(--jp-ui-font-color2); +} + +.jgis-dialog-close svg { + pointer-events: none; + flex-shrink: 0; + width: 1rem; + height: 1rem; +} + +.jgis-dialog-header { + display: flex; + flex-direction: column; + gap: 0.5rem; + text-align: center; +} + +@media (min-width: 640px) { + .jgis-dialog-header { + text-align: left; + } +} + +.jgis-dialog-footer { + display: flex; + flex-direction: column-reverse; + gap: 0.5rem; +} + +@media (min-width: 640px) { + .jgis-dialog-footer { + flex-direction: row; + justify-content: flex-end; + } +} + +.jgis-dialog-title { + font-size: 1.125rem; + line-height: 1; + font-weight: 600; +} + +.jgis-dialog-description { + font-size: 0.875rem; + color: var(--jp-ui-font-color2); +} + +.jgis-sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} diff --git a/yarn.lock b/yarn.lock index 707aaf3e5..7218c1b5e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -973,6 +973,7 @@ __metadata: "@mapbox/vector-tile": ^2.0.3 "@naisutech/react-tree": ^3.0.1 "@radix-ui/react-checkbox": ^1.3.2 + "@radix-ui/react-dialog": ^1.1.15 "@radix-ui/react-dropdown-menu": ^2.1.15 "@radix-ui/react-popover": ^1.1.14 "@radix-ui/react-radio-group": ^1.3.8 @@ -991,6 +992,7 @@ __metadata: ajv: ^8.14.0 class-variance-authority: ^0.7.1 clsx: ^2.1.1 + cmdk: ^1.1.1 colormap: ^2.3.2 d3-color: ^3.1.0 date-fns: ^4.1.0 @@ -2937,7 +2939,7 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-compose-refs@npm:1.1.2": +"@radix-ui/react-compose-refs@npm:1.1.2, @radix-ui/react-compose-refs@npm:^1.1.1": version: 1.1.2 resolution: "@radix-ui/react-compose-refs@npm:1.1.2" peerDependencies: @@ -2963,6 +2965,38 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-dialog@npm:^1.1.15, @radix-ui/react-dialog@npm:^1.1.6": + version: 1.1.15 + resolution: "@radix-ui/react-dialog@npm:1.1.15" + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2 + "@radix-ui/react-context": 1.1.2 + "@radix-ui/react-dismissable-layer": 1.1.11 + "@radix-ui/react-focus-guards": 1.1.3 + "@radix-ui/react-focus-scope": 1.1.7 + "@radix-ui/react-id": 1.1.1 + "@radix-ui/react-portal": 1.1.9 + "@radix-ui/react-presence": 1.1.5 + "@radix-ui/react-primitive": 2.1.3 + "@radix-ui/react-slot": 1.2.3 + "@radix-ui/react-use-controllable-state": 1.2.2 + aria-hidden: ^1.2.4 + react-remove-scroll: ^2.6.3 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: a0834338ec66866ce301ef46e0dad9d99accf496f03b5021eceec7e2b79d7286b4f2c5e35f2387891e2bf33ef9a11d381dde2c8fe936a2f30cd50ca4e9bf4cb5 + languageName: node + linkType: hard + "@radix-ui/react-direction@npm:1.1.1": version: 1.1.1 resolution: "@radix-ui/react-direction@npm:1.1.1" @@ -3058,7 +3092,7 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-id@npm:1.1.1": +"@radix-ui/react-id@npm:1.1.1, @radix-ui/react-id@npm:^1.1.0": version: 1.1.1 resolution: "@radix-ui/react-id@npm:1.1.1" dependencies: @@ -3229,6 +3263,25 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-primitive@npm:^2.0.2": + version: 2.1.4 + resolution: "@radix-ui/react-primitive@npm:2.1.4" + dependencies: + "@radix-ui/react-slot": 1.2.4 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 8b4cf865b4d4d135c9c6768856fdad0f62cbe43a2819471eb682061d2076222c3368ae0b771516426b264afcaf0a0032943408b3a789cbb77273152dd4a06d05 + languageName: node + linkType: hard + "@radix-ui/react-radio-group@npm:^1.3.8": version: 1.3.8 resolution: "@radix-ui/react-radio-group@npm:1.3.8" @@ -3299,7 +3352,7 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-slot@npm:^1.2.3": +"@radix-ui/react-slot@npm:1.2.4": version: 1.2.4 resolution: "@radix-ui/react-slot@npm:1.2.4" dependencies: @@ -5651,6 +5704,21 @@ __metadata: languageName: node linkType: hard +"cmdk@npm:^1.1.1": + version: 1.1.1 + resolution: "cmdk@npm:1.1.1" + dependencies: + "@radix-ui/react-compose-refs": ^1.1.1 + "@radix-ui/react-dialog": ^1.1.6 + "@radix-ui/react-id": ^1.1.0 + "@radix-ui/react-primitive": ^2.0.2 + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + checksum: 063c3c66eba917c1968c278673cce17a9925cf4ea2d2da72718a7e09e2a103a4f1cb08eac8a7257b6613c8c8e0d273173f22097045b3dfaeaca0e05d3a2e81a7 + languageName: node + linkType: hard + "color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" From ba5aa806783f488024fc7f040db00c11c8359db6 Mon Sep 17 00:00:00 2001 From: Greg Date: Thu, 11 Dec 2025 10:14:04 +0100 Subject: [PATCH 3/3] Update lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 7218c1b5e..280da2c28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3352,7 +3352,7 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-slot@npm:1.2.4": +"@radix-ui/react-slot@npm:1.2.4, @radix-ui/react-slot@npm:^1.2.3": version: 1.2.4 resolution: "@radix-ui/react-slot@npm:1.2.4" dependencies: