feat: implemented a basic store and emulatorjs
This commit is contained in:
parent
2f32cbc730
commit
7286541822
121 changed files with 5900 additions and 1092 deletions
|
|
@ -7,12 +7,26 @@ import
|
|||
import classNames from "classnames";
|
||||
import { GamePadButtonCode, Shortcut, useShortcuts } from "@/mainview/scripts/shortcuts";
|
||||
|
||||
export type ButtonStyle = 'base' | 'accent' | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error';
|
||||
|
||||
const styles = {
|
||||
base: 'bg-base-200 text-base-content active:bg-base-300! active:text-base-content! active:ring-offset-base-content',
|
||||
accent: "bg-accent text-accent-content active:bg-base-content! active:text-base-content active:ring-offset-accent",
|
||||
primary: "bg-primary text-primary-content active:bg-base-content! active:text-base-content! active:ring-offset-primary",
|
||||
secondary: "bg-secondary text-secondary-content active:bg-base-content! active:text-base-content! active:ring-offset-secondary",
|
||||
info: "bg-info text-info-content active:bg-base-content! active:text-base-content! active:ring-offset-info",
|
||||
success: "bg-success text-success-content active:bg-base-content! active:text-base-content! active:ring-offset-success",
|
||||
warning: "bg-warning text-warning-content active:bg-base-content! active:text-base-content! active:ring-offset-warning",
|
||||
error: "bg-error text-error-content active:bg-base-content! active:text-base-content! active:ring-offset-error",
|
||||
};
|
||||
|
||||
export function Button (data: {
|
||||
id: string,
|
||||
children?: any,
|
||||
className?: string,
|
||||
disabled?: boolean,
|
||||
type?: "reset" | "button" | "submit";
|
||||
style?: ButtonStyle,
|
||||
shortcutLabel?: string;
|
||||
focusClassName?: string;
|
||||
} & InteractParams & FocusParams)
|
||||
|
|
@ -20,7 +34,7 @@ export function Button (data: {
|
|||
const { ref, focused, focusKey } = useFocusable({
|
||||
focusKey: data.id,
|
||||
onEnterPress: data.onAction,
|
||||
onFocus: data.onFocus,
|
||||
onFocus: (_l, _p, details) => data.onFocus?.(focusKey, ref.current, details),
|
||||
focusable: !data.disabled
|
||||
});
|
||||
|
||||
|
|
@ -31,9 +45,10 @@ export function Button (data: {
|
|||
|
||||
return <button
|
||||
ref={ref}
|
||||
onClick={data.onAction}
|
||||
onClick={e => data.onAction?.(e.nativeEvent)}
|
||||
disabled={data.disabled}
|
||||
className={twMerge("btn rounded-full focus:bg-base-content focus:text-base-300 md:text-lg",
|
||||
className={twMerge("flex items-center justify-center px-4 py-2 disabled:bg-base-200/40 disabled:text-base-content/40 cursor-pointer rounded-3xl md:text-lg not-control-mouse:focused:drop-shadow-lg border border-base-content/5 not-control-mouse:focused:bg-base-content not-control-mouse:focused:text-base-100 control-mouse:hover:bg-base-content control-mouse:hover:text-base-100 active:transition-none active:ring-offset-4",
|
||||
styles[data.style ?? 'base'],
|
||||
focused ? data.focusClassName : undefined,
|
||||
classNames({
|
||||
"btn-accent": focused,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { HTMLInputTypeAttribute, JSX } from "react";
|
||||
import { LocalSettingsSchema, LocalSettingsType } from "../../../shared/constants";
|
||||
import { LocalSettingsSchema, LocalSettingsType } from "@shared/constants";
|
||||
import { OptionSpace } from "./OptionSpace";
|
||||
import { OptionInput } from "./OptionInput";
|
||||
import { useLocalStorage } from "usehooks-ts";
|
||||
|
|
@ -18,7 +18,7 @@ export function LocalOption (data: {
|
|||
const [localValue, setLocalValue] = useLocalStorage<any>(data.id, LocalSettingsSchema.shape[data.id].parse(undefined), { deserializer: (v) => LocalSettingsSchema.shape[data.id].parse(JSON.parse(v)) });
|
||||
|
||||
return (
|
||||
<OptionSpace label={data.label}>
|
||||
<OptionSpace id={`${data.id}-space`} label={data.label}>
|
||||
{data.type === 'dropdown' && data.values && <OptionDropdown values={data.values} icon={data.icon}
|
||||
name={data.id ?? ""}
|
||||
type={data.type}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import classNames from "classnames";
|
||||
import { ChangeEventHandler, FocusEventHandler, HTMLInputAutoCompleteAttribute, HTMLInputTypeAttribute, JSX, useRef, useState } from "react";
|
||||
import { FocusEventHandler, HTMLInputAutoCompleteAttribute, HTMLInputTypeAttribute, JSX, useRef, useState } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useOptionContext } from "./OptionSpace";
|
||||
import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
|
||||
import { systemApi } from "../../scripts/clientApi";
|
||||
import { useFocusable } from "@noriginmedia/norigin-spatial-navigation";
|
||||
import { ContextDialog, ContextList, DialogEntry } from "../ContextDialog";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
|
|
@ -39,16 +37,13 @@ export function OptionDropdown (data: {
|
|||
|
||||
return (
|
||||
<>
|
||||
<label ref={ref} className={twMerge("flex items-center gap-3 rounded-full sm:flex-2 md:flex-1 divide-accent",
|
||||
classNames({ "[&_button]:not-focus:ring-7 [&_button]:not-focus:ring-accent": focused }))}>
|
||||
{!!data.icon && <span className={twMerge("text-base-content/80", classNames({
|
||||
"text-primary-content": option.focused
|
||||
}))}>{data.icon}</span>}
|
||||
<label ref={ref} className={twMerge("flex group-focusable items-center gap-3 rounded-full sm:flex-2 md:flex-1 divide-accent")}>
|
||||
{!!data.icon && <span className={"text-base-content/80 is-focused:text-primary-content"}>{data.icon}</span>}
|
||||
<button onClick={() =>
|
||||
{
|
||||
console.log("Open");
|
||||
setOpen(true);
|
||||
}} className={classNames('btn input rounded-full cursor-pointer grow', { "bg-base-200": !focused })}>{data.value}<ChevronDown /></button>
|
||||
}} className={'flex items-center justify-center border h-10 border-base-content/30 px-4 py-2 rounded-full cursor-pointer grow not-in-focused:bg-base-200 focusable focusable-accent hover:border-base-content hover:bg-base-content hover:text-base-300'}>{data.value}<ChevronDown /></button>
|
||||
</label>
|
||||
{open && <ContextDialog id={`${data.name}-context`} open={true} close={handleClose}>
|
||||
<ContextList options={data.values.map((v, i) => ({
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import classNames from "classnames";
|
||||
import { ChangeEventHandler, FocusEventHandler, HTMLInputAutoCompleteAttribute, HTMLInputTypeAttribute, JSX, useRef } from "react";
|
||||
import { FocusEventHandler, HTMLInputAutoCompleteAttribute, HTMLInputTypeAttribute, JSX, useRef } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useOptionContext } from "./OptionSpace";
|
||||
import { useFocusable } from "@noriginmedia/norigin-spatial-navigation";
|
||||
import { systemApi } from "../../scripts/clientApi";
|
||||
import { Check, CheckIcon, X } from "lucide-react";
|
||||
import { CheckIcon, X } from "lucide-react";
|
||||
|
||||
export function OptionInput (data: {
|
||||
name: string;
|
||||
|
|
@ -52,11 +51,8 @@ export function OptionInput (data: {
|
|||
};
|
||||
|
||||
return (
|
||||
<label ref={ref} className={twMerge("flex items-center gap-3 rounded-full sm:flex-2 md:flex-1 divide-accent",
|
||||
classNames({ "[&_.focus-target]:not-focus:ring-7 [&_.focus-target]:not-focus:ring-accent": focused, "pl-1": data.type === 'checkbox' }))}>
|
||||
{!!data.icon && <span className={twMerge("text-base-content/80", classNames({
|
||||
"text-primary-content": option.focused
|
||||
}))}>{data.icon}</span>}
|
||||
<label ref={ref} className={`flex items-center gap-3 rounded-full sm:flex-2 md:flex-1 divide-accent group-focusable`}>
|
||||
{!!data.icon && <span className="text-base-content/80">{data.icon}</span>}
|
||||
{data.type !== 'checkbox' && <input
|
||||
ref={inputRef}
|
||||
id={data.name}
|
||||
|
|
@ -72,17 +68,11 @@ export function OptionInput (data: {
|
|||
onBlur={data.onBlur}
|
||||
defaultChecked={typeof data.defaultValue === 'boolean' ? data.defaultValue : undefined}
|
||||
className={twMerge(
|
||||
"focus-target text-base-content",
|
||||
"input grow rounded-full ring-primary-content focus:ring-7", classNames({
|
||||
"bg-base-200": !focused
|
||||
}),
|
||||
"flex text-base-content px-4 py-2 items-center justify-center border border-base-content/20 grow rounded-full focus:ring-base-content in-focused:bg-base-200 focusable focusable-accent focus:not-focused:ring-7 control-mouse:ring-0! hover:border-base-content",
|
||||
data.className
|
||||
)}
|
||||
/>}
|
||||
{data.type === 'checkbox' && <div className={classNames("toggle focus-target toggle-primary toggle-xl border-base-content/30 rounded-full before:rounded-full text-base-content", {
|
||||
"bg-base-200": !focused,
|
||||
"border-0": focused,
|
||||
})}>
|
||||
{data.type === 'checkbox' && <div className="toggle toggle-xl before:size-6 h-8 border-base-content/30 rounded-full before:rounded-full text-base-content not-in-focus:bg-base-200 focused-child:border-0 ml-1 ring-7 hover:border-base-content focusable focusable-accent">
|
||||
<input
|
||||
ref={inputRef}
|
||||
id={data.name}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,9 @@
|
|||
import { FocusContext, FocusDetails, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
|
||||
import { OptionContext } from "@/mainview/scripts/contexts";
|
||||
import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
|
||||
import classNames from "classnames";
|
||||
import { createContext, JSX, useContext, useEffect, useMemo } from "react";
|
||||
import { JSX, useContext, useEffect, useMemo } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export const OptionContext = createContext(
|
||||
{} as {
|
||||
focused: boolean;
|
||||
focus: (focusDetails?: FocusDetails | undefined) => void;
|
||||
eventTarget: EventTarget;
|
||||
},
|
||||
);
|
||||
|
||||
export function useOptionContext (params?: { onOptionEnterPress?: () => void; })
|
||||
{
|
||||
const context = useContext(OptionContext);
|
||||
|
|
@ -81,11 +74,7 @@ export function OptionSpace (data: {
|
|||
<OptionContext value={{ focused, focus: focusSelf, eventTarget }}>
|
||||
<li
|
||||
ref={ref}
|
||||
className={twMerge("flex portrait:flex-col portrait:gap-2 portrait:p-4 md:flex-row sm:p-2 md:p-4 md:pl-8! rounded-3xl border-b border-base-content/5",
|
||||
classNames(
|
||||
{
|
||||
"bg-base-300": focused || hasFocusedChild,
|
||||
}),
|
||||
className={twMerge("flex portrait:flex-col portrait:gap-2 portrait:p-4 md:flex-row sm:p-2 md:p-4 md:pl-8! rounded-3xl border-b border-base-content/5 focused:bg-base-300 focused-child:bg-base-300",
|
||||
data.className,
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ export function PathSettingsOptionBase (data: PathSettingsOptionParams & {
|
|||
};
|
||||
|
||||
return (
|
||||
<OptionSpace id={data.id} className="gap-2" label={<>{data.label}{changed && <Pen />}</>}>
|
||||
<OptionSpace id={`${data.id}-space`} className="gap-2" label={<>{data.label}{changed && <Pen />}</>}>
|
||||
<OptionInput
|
||||
icon={data.icon}
|
||||
name={`${data.id}-input`}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const { useAppForm: useSettingsForm, useTypedAppFormContext: useSettingsF
|
|||
function FormOption (data: { type: HTMLInputTypeAttribute, icon?: JSX.Element; label?: string | JSX.Element; placeholder?: string; })
|
||||
{
|
||||
const field = useFieldContext<string>();
|
||||
return <OptionSpace label={<div className="flex flex-1 gap-2">
|
||||
return <OptionSpace id={`${field.name}-space`} label={<div className="flex flex-1 gap-2">
|
||||
{data.label}
|
||||
{field.getMeta().errors.length > 0 && <div className="badge badge-error">
|
||||
{field.state.meta.errors.map(e => e.message).join(',')}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export function SettingsOption (data: {
|
|||
}, [dirty, setDirty, localValue]);
|
||||
|
||||
return (
|
||||
<OptionSpace label={data.label}>
|
||||
<OptionSpace id={`${data.id}-space`} label={data.label}>
|
||||
<OptionInput
|
||||
icon={data.icon}
|
||||
name={data.id ?? ""}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue