From a0dd9fd6844868cae5ca8e9e35e89a8ab6424bce Mon Sep 17 00:00:00 2001 From: softmarshmallow Date: Tue, 29 Mar 2022 20:39:16 +0900 Subject: [PATCH] add checkbox flag & mapping --- externals/reflect-core | 2 +- .../html-input/html-input-checkbox.ts | 132 ++++++++++++++++++ .../widgets-native/html-input/index.ts | 1 + packages/designto-token/main.ts | 2 + .../designto-token/support-flags/index.ts | 5 + .../support-flags/token-checkbox/index.ts | 86 ++++++++++++ .../compose-unwrapped-checkbox.ts | 10 ++ .../tokens-to-web-widget/index.ts | 11 ++ packages/support-flags/--as-checkbox/index.ts | 11 ++ packages/support-flags/keys.ts | 7 +- packages/support-flags/parse.ts | 16 +++ packages/support-flags/types.ts | 3 + 12 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 packages/builder-web-core/widgets-native/html-input/html-input-checkbox.ts create mode 100644 packages/designto-token/support-flags/token-checkbox/index.ts create mode 100644 packages/designto-web/tokens-to-web-widget/compose-unwrapped-checkbox.ts create mode 100644 packages/support-flags/--as-checkbox/index.ts diff --git a/externals/reflect-core b/externals/reflect-core index e3c0523b..ab0498c4 160000 --- a/externals/reflect-core +++ b/externals/reflect-core @@ -1 +1 @@ -Subproject commit e3c0523bdee90aaad8beb2f92807bbd1f8ab40db +Subproject commit ab0498c474aee5a375e7ac7eb71173c8817bfedc diff --git a/packages/builder-web-core/widgets-native/html-input/html-input-checkbox.ts b/packages/builder-web-core/widgets-native/html-input/html-input-checkbox.ts new file mode 100644 index 00000000..12cb3f1b --- /dev/null +++ b/packages/builder-web-core/widgets-native/html-input/html-input-checkbox.ts @@ -0,0 +1,132 @@ +import type { ElementCssStyleData } from "@coli.codes/css"; +import type { + BorderSide, + Color, + ICheckboxManifest, + IWHStyleWidget, + MouseCursor, +} from "@reflect-ui/core"; +import { WidgetKey } from "../../widget-key"; +import type { StylableJSXElementConfig } from "../../widget-core"; +import { Container } from "../container"; +import * as css from "@web-builder/styles"; +import { JSX, JSXAttribute, StringLiteral } from "coli"; + +/** + * A jsx attibute to indicate input type as checkbox + */ +const attr_type_range = new JSXAttribute("type", new StringLiteral("checkbox")); + +/** + * checkbox + * + * + * @see + * - [html#progress](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress) + */ +export class HtmlInputCheckbox extends Container implements ICheckboxManifest { + _type = "input/checkbox"; + + value?: boolean; + + /** + * @deprecated - not supported natively + */ + tristate?: boolean; + mouseCursor?: MouseCursor; + activeColor?: Color; + // fillColor?: ReflectStateProperty; + checkColor?: Color; + focusColor?: Color; + + /** + * @deprecated - not supported natively + */ + hoverColor?: Color; + // overlayColor?: ReflectStateProperty; + + /** + * @deprecated - not supported natively + */ + splashRadius?: number; + // visualDensity?: VisualDensity, + autofocus?: boolean; + // shape?: OutlinedBorder; + + /** + * @deprecated - not supported natively + */ + side?: BorderSide; + + constructor({ + key, + + value, + tristate, + mouseCursor, + activeColor, + checkColor, + focusColor, + hoverColor, + splashRadius, + autofocus, + side, + + ...rest + }: { + key: WidgetKey; + } & IWHStyleWidget & + ICheckboxManifest) { + super({ key, ...rest }); + + this.value = value; + this.tristate = tristate; + this.mouseCursor = mouseCursor; + this.activeColor = activeColor; + this.checkColor = checkColor; + this.focusColor = focusColor; + this.hoverColor = hoverColor; + this.splashRadius = splashRadius; + this.autofocus = autofocus; + this.side = side; + } + + get indeterminate() { + return this.value === undefined; + } + + styleData(): ElementCssStyleData { + const containerstyle = super.styleData(); + + return { + // general layouts, continer --------------------- + ...containerstyle, + // ------------------------------------------------- + + "border-radius": undefined, // clear to use default appearance + "background-color": undefined, // clear to use default appearance + padding: undefined, + + // general slider styles + "accent-color": css.color(this.activeColor), + }; + } + + jsxConfig(): StylableJSXElementConfig { + const attrs = [ + attr_type_range, + this.indeterminate + ? new JSXAttribute("indeterminate") + : new JSXAttribute( + "value", + new StringLiteral(this.value ? "checked" : "unchecked") + ), + ].filter(Boolean); + + return { + type: "tag-and-attr", + tag: JSX.identifier("input"), + attributes: attrs, + }; + } +} diff --git a/packages/builder-web-core/widgets-native/html-input/index.ts b/packages/builder-web-core/widgets-native/html-input/index.ts index 2413a60f..47358064 100644 --- a/packages/builder-web-core/widgets-native/html-input/index.ts +++ b/packages/builder-web-core/widgets-native/html-input/index.ts @@ -1,2 +1,3 @@ export { HtmlTextField as TextInput } from "./html-input-text"; export { HtmlSlider as Slider } from "./html-input-range"; +export { HtmlInputCheckbox as Checkbox } from "./html-input-checkbox"; diff --git a/packages/designto-token/main.ts b/packages/designto-token/main.ts index 770d45c7..efb42e68 100644 --- a/packages/designto-token/main.ts +++ b/packages/designto-token/main.ts @@ -183,6 +183,8 @@ function handleNode( if (config.disable_detection) { // skip detection } else { + // TODO: only execute detection if all the nested children is not flagged as other component. + // - icon - const _detect_if_icon = detectIf.icon(node); if (_detect_if_icon.result) { diff --git a/packages/designto-token/support-flags/index.ts b/packages/designto-token/support-flags/index.ts index 6ed2bef6..3948a5db 100644 --- a/packages/designto-token/support-flags/index.ts +++ b/packages/designto-token/support-flags/index.ts @@ -21,6 +21,7 @@ import { tokenize_flagged_progress } from "./token-progress"; import { tokenize_flagged_google_maps_view } from "./token-x-google-maps-view"; import { tokenize_flagged_youtube_view } from "./token-x-youtube-view"; import { tokenize_flagged_camera_view } from "./token-x-camera-display"; +import { tokenize_flagged_checkbox } from "./token-checkbox"; export default function handleWithFlags(node: ReflectSceneNode) { const flags = parse(node.name); @@ -54,6 +55,10 @@ function _handle_with_flags(node, flags: FlagsParseResult) { return tokenize_flagged_button(node, flags[keys.flag_key__as_button]); } + if (flags.__meta?.contains_checkbox_flag) { + return tokenize_flagged_checkbox(node, flags[keys.flag_key__as_checkbox]); + } + if (flags.__meta?.contains_input_flag) { return tokenize_flagged_textfield(node, flags[keys.flag_key__as_input]); } diff --git a/packages/designto-token/support-flags/token-checkbox/index.ts b/packages/designto-token/support-flags/token-checkbox/index.ts new file mode 100644 index 00000000..16d1fb6a --- /dev/null +++ b/packages/designto-token/support-flags/token-checkbox/index.ts @@ -0,0 +1,86 @@ +import { Checkbox, Container, ICheckboxManifest } from "@reflect-ui/core"; +import type { AsCheckboxFlag } from "@code-features/flags"; +import type { + ReflectFrameNode, + ReflectSceneNode, +} from "@design-sdk/figma-node"; +import { WrappingContainer } from "../../tokens"; +import assert from "assert"; +import { unwrappedChild } from "../../wrappings"; +import { tokenizeLayout } from "../../token-layout"; +import { keyFromNode } from "../../key"; + +export function tokenize_flagged_checkbox( + node: ReflectSceneNode, + flag: AsCheckboxFlag +): Checkbox | WrappingContainer { + if (flag.value === false) return; + + const validated = validate_checkbox(node); + if (validated.error === false) { + switch (validated.__type) { + case "frame-as-checkbox": { + const { checkbox_base, checkbox_value } = validated; + + const _key = keyFromNode(node); + + const container = unwrappedChild( + tokenizeLayout.fromFrame( + checkbox_base, + checkbox_base.children, + { is_root: false }, + {} + ) + ) as Container; + + const checked = checkbox_value && checkbox_value.visible; + + return new WrappingContainer({ + ...container, + key: keyFromNode(node), + child: new Checkbox({ + key: _key, + fillColor: { default: checkbox_base?.primaryColor }, + checkColor: checkbox_value?.primaryColor, + value: checked, + }), + }); + } + default: + throw new Error("unreachable"); + } + } else { + throw new Error(validated.error); + } +} + +/** + * validate if layer casted as checkbox can be actually tokenized to checkbox. + * @param node + */ +function validate_checkbox(node: ReflectSceneNode): + | { + __type: "frame-as-checkbox"; + error: false; + checkbox_base: ReflectFrameNode; + checkbox_value?: ReflectSceneNode; + } + | { error: string } { + assert(!!node, "target must not be null or undefined"); + switch (node.type) { + case "FRAME": { + const firstvaluenode = node.children.find( + (child) => child.type === "VECTOR" + ); + + return { + __type: "frame-as-checkbox", + checkbox_base: node, + checkbox_value: firstvaluenode, + error: false, + }; + } + default: + return { error: "checkbox target is not a valid frame node" }; + } +} diff --git a/packages/designto-web/tokens-to-web-widget/compose-unwrapped-checkbox.ts b/packages/designto-web/tokens-to-web-widget/compose-unwrapped-checkbox.ts new file mode 100644 index 00000000..0a5734b2 --- /dev/null +++ b/packages/designto-web/tokens-to-web-widget/compose-unwrapped-checkbox.ts @@ -0,0 +1,10 @@ +import * as web from "@web-builder/core"; +import * as core from "@reflect-ui/core"; + +export function compose_unwrapped_checkbox( + key, + widget: core.Checkbox, + container?: core.Container +): web.Checkbox { + return new web.Checkbox({ ...(container ?? {}), ...widget, key }); +} diff --git a/packages/designto-web/tokens-to-web-widget/index.ts b/packages/designto-web/tokens-to-web-widget/index.ts index b75bb55c..9b675bb0 100644 --- a/packages/designto-web/tokens-to-web-widget/index.ts +++ b/packages/designto-web/tokens-to-web-widget/index.ts @@ -17,6 +17,7 @@ import { compose_wrapped_with_overflow_box } from "./compose-wrapped-with-overfl import { compose_wrapped_with_expanded } from "./compose-wrapped-with-expanded"; import { compose_unwrapped_text_input } from "./compose-unwrapped-text-field"; import { compose_unwrapped_button } from "./compose-unwrapped-button"; +import { compose_unwrapped_checkbox } from "./compose-unwrapped-checkbox"; import { compose_unwrapped_slider } from "./compose-unwrapped-slider"; import { compose_unwrapped_progress } from "./compose-unwrapped-progress"; import { compose_instanciation } from "./compose-instanciation"; @@ -242,6 +243,10 @@ function compose( // TODO: widget.icon - not supported thisWebWidget = compose_unwrapped_button(_key, widget); } + // checkbox + else if (widget instanceof core.Checkbox) { + thisWebWidget = compose_unwrapped_checkbox(_key, widget); + } // textfield else if (widget instanceof core.TextField) { thisWebWidget = compose_unwrapped_text_input(_key, widget); @@ -263,6 +268,8 @@ function compose( thisWebWidget = compose_unwrapped_text_input(_key, widget.child, widget); } else if (widget.child instanceof core.ButtonStyleButton) { thisWebWidget = compose_unwrapped_button(_key, widget.child, widget); + } else if (widget.child instanceof core.Checkbox) { + thisWebWidget = compose_unwrapped_checkbox(_key, widget.child, widget); } else if (widget.child instanceof core.Slider) { thisWebWidget = compose_unwrapped_slider(_key, widget.child, widget); } else if (widget.child instanceof core.ProgressIndicator) { @@ -283,6 +290,10 @@ function compose( // --- --- --- --- --- // #endregion else { + console.error( + `Unsupported web widget type: ${widget.child.constructor.name}`, + widget.child + ); throw new Error( `Unsupported web widget type: ${widget.child.constructor.name}` ); diff --git a/packages/support-flags/--as-checkbox/index.ts b/packages/support-flags/--as-checkbox/index.ts new file mode 100644 index 00000000..5da351b9 --- /dev/null +++ b/packages/support-flags/--as-checkbox/index.ts @@ -0,0 +1,11 @@ +// primary +export const flag_key__as_checkbox = "as-checkbox"; + +export const flag_key_alias__as_checkbox = [flag_key__as_checkbox]; + +export interface AsCheckboxFlag { + flag: typeof flag_key__as_checkbox; + + value: boolean; + _raw?: string; +} diff --git a/packages/support-flags/keys.ts b/packages/support-flags/keys.ts index ddddf66c..0844e64f 100644 --- a/packages/support-flags/keys.ts +++ b/packages/support-flags/keys.ts @@ -10,6 +10,8 @@ import { flag_key_alias__as_p, flag_key__as_p } from "./--as-p"; import { flag_key_alias__as_span, flag_key__as_span } from "./--as-span"; import { flag_key__as_button, flag_key_alias__as_button } from "./--as-button"; +// prettier-ignore +import { flag_key__as_checkbox, flag_key_alias__as_checkbox } from "./--as-checkbox"; import { flag_key__as_input, flag_key_alias_as_input } from "./--as-input"; import { flag_key__as_slider, flag_key_alias__as_slider } from "./--as-slider"; // prettier-ignore @@ -61,11 +63,9 @@ export { flag_key__as_p }; export { flag_key__as_span }; export { flag_key__as_button }; - +export { flag_key__as_checkbox }; export { flag_key__as_input }; - export { flag_key__as_slider }; - export { flag_key__as_progress }; export { @@ -101,6 +101,7 @@ export const alias = { as_p: flag_key_alias__as_p, as_span: flag_key_alias__as_span, as_button: flag_key_alias__as_button, + as_checkbox: flag_key_alias__as_checkbox, as_input: flag_key_alias_as_input, as_slider: flag_key_alias__as_slider, as_progress: flag_key_alias__as_progress, diff --git a/packages/support-flags/parse.ts b/packages/support-flags/parse.ts index 365f63b0..96b6e83e 100644 --- a/packages/support-flags/parse.ts +++ b/packages/support-flags/parse.ts @@ -14,6 +14,7 @@ import type { AsParagraphFlag, AsTextSpanFlag, AsButtonFlag, + AsCheckboxFlag, AsInputFlag, AsProgressFlag, SimpleBooleanValueFlag, @@ -37,6 +38,7 @@ export type FlagsParseResult = Results & { contains_paragraph_flag: boolean; contains_span_flag: boolean; contains_button_flag: boolean; + contains_checkbox_flag: boolean; contains_input_flag: boolean; contains_slider_flag: boolean; contains_progress_flag: boolean; @@ -80,6 +82,9 @@ export function parse(name: string): FlagsParseResult { // button __button_alias_pref, + // checkbox + __checkbox_alias_pref, + // input __input_alias_pref, @@ -139,6 +144,11 @@ export function parse(name: string): FlagsParseResult { keys.alias.as_button ); + const as_checkbox_flag = handle_single_boolean_flag_alias( + _raw_parsed, + keys.alias.as_checkbox + ); + const as_input_flag = handle_single_boolean_flag_alias( _raw_parsed, keys.alias.as_input @@ -211,6 +221,7 @@ export function parse(name: string): FlagsParseResult { ...(as_paragraph_flag ?? {}), ...(as_span_flag ?? {}), ...(as_button_flag ?? {}), + ...(as_checkbox_flag ?? {}), ...(as_input_flag ?? {}), ...(as_progress_flag ?? {}), ...(wh_declaration_flag ?? {}), @@ -229,6 +240,7 @@ export function parse(name: string): FlagsParseResult { contains_paragraph_flag: notempty(as_paragraph_flag), contains_span_flag: notempty(as_span_flag), contains_button_flag: notempty(as_button_flag), + contains_checkbox_flag: notempty(as_checkbox_flag), contains_input_flag: notempty(as_input_flag), contains_slider_flag: notempty(as_slider_flag), contains_progress_flag: notempty(as_progress_flag), @@ -382,6 +394,10 @@ const __button_alias_pref = _simple_boolean_value_flag_prefernce_mapper( keys.alias.as_button ); +const __checkbox_alias_pref = _simple_boolean_value_flag_prefernce_mapper( + keys.alias.as_checkbox +); + const __input_alias_pref = _simple_boolean_value_flag_prefernce_mapper( keys.alias.as_input ); diff --git a/packages/support-flags/types.ts b/packages/support-flags/types.ts index c5488427..6d325a39 100644 --- a/packages/support-flags/types.ts +++ b/packages/support-flags/types.ts @@ -9,6 +9,7 @@ import type { AsHeading6Flag } from "./--as-h6"; import type { AsButtonFlag } from "./--as-button"; import type { AsInputFlag } from "./--as-input"; import type { AsSliderFlag } from "./--as-slider"; +import type { AsCheckboxFlag } from "./--as-checkbox"; import type { AsProgressFlag } from "./--as-progress"; import type { AsParagraphFlag } from "./--as-p"; import type { AsTextSpanFlag } from "./--as-span"; @@ -105,6 +106,7 @@ export type ValueFeedFlag = QFlag | LatLngFlag; */ export type ComponentCastingFlag = | AsButtonFlag + | AsCheckboxFlag | AsInputFlag | AsSliderFlag | AsProgressFlag; @@ -150,6 +152,7 @@ export type { AsTextSpanFlag }; // --------------------------------------------------------------------------- export type { AsButtonFlag }; +export type { AsCheckboxFlag }; export type { AsInputFlag }; export type { AsSliderFlag }; export type { AsProgressFlag };