Skip to content

Commit

Permalink
improve: Key Component scale transition
Browse files Browse the repository at this point in the history
- improve: smoother scale transition when hovering
- feat: toggle select+deselect, allows users to deselect a currently
  selected key
  • Loading branch information
pongstr committed Dec 12, 2024
1 parent e5ab3b5 commit 7d133d6
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 37 deletions.
15 changes: 14 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,22 @@ module.exports = {
parser: "@typescript-eslint/parser",
plugins: ["react-refresh"],
rules: {
"tailwindcss/no-custom-classname": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
args: "all",
argsIgnorePattern: "^_",
caughtErrors: "all",
caughtErrorsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
varsIgnorePattern: "^_",
ignoreRestSiblings: true,
},
],
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
},
};
}
5 changes: 4 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
{}
{
"tabWidth": 2,
"semi": true,
}
46 changes: 46 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
"@tauri-apps/api": "^2.0.0",
"@tauri-apps/plugin-cli": "^2.0.0",
"@zmkfirmware/zmk-studio-ts-client": "^0.0.18",
"clsx": "^2.1.1",
"emittery": "^1.0.3",
"immer": "^10.1.1",
"lucide-react": "^0.445.0",
"react": "^18.2.0",
"react-aria-components": "^1.4.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^2.5.5",
"tailwindcss-react-aria-components": "^1.1.3"
},
"devDependencies": {
Expand All @@ -48,6 +50,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"eslint-plugin-storybook": "^0.8.0",
"eslint-plugin-tailwindcss": "^3.17.5",
"postcss": "^8.4.38",
"prettier": "3.3.2",
"run-script-os": "^1.1.6",
Expand Down
66 changes: 41 additions & 25 deletions src/keyboard/Key.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// import './key.css';

import { PropsWithChildren, Children, CSSProperties } from "react";
import { PropsWithChildren, Children, CSSProperties, FC } from "react"
import { cn } from "../utils"

interface KeyProps {
/**
Expand Down Expand Up @@ -33,15 +34,29 @@ interface KeyDimension {

function makeSize(
{ width, height }: KeyDimension,
oneU: number
oneU: number,
): CSSProperties {
width *= oneU;
height *= oneU;
width *= oneU
height *= oneU

return {
"--zmk-key-center-width": "calc(" + width + "px - 2px)",
"--zmk-key-center-height": "calc(" + height + "px - 2px)",
};
"--zmk-key-center-width": `calc(${width}px - 2px)`,
"--zmk-key-center-height": `calc(${height}px - 2px)`,
}
}

const ChildItem: FC<PropsWithChildren<{ hoverZoom: boolean }>> = ({
children,
hoverZoom,
}) => {
return (
<div
data-zoomer={hoverZoom}
className="col-start-2 col-end-3 row-start-2 row-end-3 self-center justify-self-center font-keycap text-lg"
>
{children}
</div>
)
}

export const Key = ({
Expand All @@ -51,38 +66,39 @@ export const Key = ({
hoverZoom = true,
...props
}: PropsWithChildren<KeyProps>) => {
const size = makeSize(props, oneU);

const children = Children.map(props.children, (c) => (
<div
data-zoomer={hoverZoom}
className="justify-self-center self-center row-start-2 row-end-3 col-start-2 col-end-3 font-keycap text-lg data-[zoomer=true]:group-hover:text-2xl"
>
{c}
</div>
));
const size = makeSize(props, oneU)

return (
<div
className="group inline-flex b-0 justify-content-center items-center transition-all duration-100 data-[zoomer=true]:hover:translate-y-[calc(-1em-7px)] data-[zoomer=true]:hover:translate-x-[calc(-1em)]"
className={cn(
"group justify-content-center group items-center select-none",
"transition-transform origin-center data-[zoomer=true]:hover:scale-[2] data-[zoomer=true]:hover:z-20",
)}
data-zoomer={hoverZoom}
style={size}
{...props}
>
<button
aria-selected={selected}
data-zoomer={hoverZoom}
className={`rounded${
oneU > 20 ? "-md" : ""
} transition-all duration-100 m-auto p-0 b-0 box-border grid grid-rows-[0_var(--zmk-key-center-height)_0] grid-cols-[0_var(--zmk-key-center-width)_0] data-[zoomer=true]:hover:grid-rows-[1em_var(--zmk-key-center-height)_1em] data-[zoomer=true]:hover:grid-cols-[1em_var(--zmk-key-center-width)_1em] shadow-[0_0_0_1px_inset] shadow-base-content data-[zoomer=true]:shadow-base-200 data-[zoomer=true]:hover:shadow-base-content data-[zoomer=true]:hover:z-50 text-base-content bg-base-100 aria-selected:bg-primary aria-selected:text-primary-content grow @container`}
className={cn(
oneU > 20 ? "rounded-md" : "rounded",
"relative w-[var(--zmk-key-center-width)] h-[var(--zmk-key-center-height)] bg-base-100 border border-transparent",
"group-hover:border-[#a6adbb] aria-selected:bg-primary aria-selected:text-primary-content @container"
)}
>
{header && (
<span className="p-0 b-0 m-0 text-xs w-full h-full text-nowrap justify-self-start row-start-1 row-end-2 col-start-1 col-end-4 hidden group-hover:inline-block group-hover:truncate @md:underline">
<span className="opacity-80 truncate hidden group-hover:block text-[6px] text-center uppercase absolute top-1 left-[4px] right-[4px] max-w-[90%] h-4 leading-none">
{header}
</span>
)}
{children}

{props.children && Children.map(props.children, (child, index) => (
<ChildItem key={index} hoverZoom={hoverZoom}>
{child}
</ChildItem>
))}
</button>
</div>
);
};
)
}
4 changes: 2 additions & 2 deletions src/keyboard/Keymap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface KeymapProps {
scale: LayoutZoom;
selectedLayerIndex: number;
selectedKeyPosition: number | undefined;
onKeyPositionClicked: (keyPosition: number) => void;
onKeyPositionClicked: (keyPosition: number | undefined) => number | undefined;
}

export const Keymap = ({
Expand All @@ -35,7 +35,7 @@ export const Keymap = ({
return <></>;
}

let positions = layout.keys.map((k, i) => {
const positions = layout.keys.map((k, i) => {
if (i >= keymap.layers[selectedLayerIndex].bindings.length) {
return {
header: "Unknown",
Expand Down
1 change: 1 addition & 0 deletions src/keyboard/PhysicalLayout.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const meta = {
component: PhysicalLayout,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: "centered"
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ["autodocs"],
Expand Down
16 changes: 8 additions & 8 deletions src/keyboard/PhysicalLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface PhysicalLayoutProps {
oneU?: number;
hoverZoom?: boolean;
zoom?: LayoutZoom;
onPositionClicked?: (position: number) => void;
onPositionClicked?: (position: number | undefined) => number|undefined;
}

interface PhysicalLayoutPositionLocation {
Expand All @@ -48,14 +48,14 @@ function scalePosition(
{ x, y, r, rx, ry }: PhysicalLayoutPositionLocation,
oneU: number,
): CSSProperties {
let left = x * oneU;
let top = y * oneU;
const left = x * oneU;
const top = y * oneU;
let transformOrigin = undefined;
let transform = undefined;

if (r) {
let transformX = ((rx || x) - x) * oneU;
let transformY = ((ry || y) - y) * oneU;
const transformX = ((rx || x) - x) * oneU;
const transformY = ((ry || y) - y) * oneU;
transformOrigin = `${transformX}px ${transformY}px`;
transform = `rotate(${r}deg)`;
}
Expand Down Expand Up @@ -115,17 +115,17 @@ export const PhysicalLayout = ({
}, [props.zoom]);

// TODO: Add a bit of padding for rotation when supported
let rightMost = positions
const rightMost = positions
.map((k) => k.x + k.width)
.reduce((a, b) => Math.max(a, b), 0);
let bottomMost = positions
const bottomMost = positions
.map((k) => k.y + k.height)
.reduce((a, b) => Math.max(a, b), 0);

const positionItems = positions.map((p, idx) => (
<div
key={idx}
onClick={() => onPositionClicked?.(idx)}
onClick={() => onPositionClicked?.((prev: number | undefined) => (prev !== idx ? idx : undefined))}
className="absolute data-[zoomer=true]:hover:z-[1000] leading-[0]"
data-zoomer={hoverZoom}
style={scalePosition(p, oneU)}
Expand Down
6 changes: 6 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

0 comments on commit 7d133d6

Please sign in to comment.