Skip to content

Commit

Permalink
feat: TextField headless (#414)
Browse files Browse the repository at this point in the history
* feat: text-field headless

* feat: add textfield example in stackflow-spa

* chore: delete .gitignore

* chore: textfield test

* feat: more test, handle onChange logic when maxLength

* Create vitest.workspace.ts

* chore: yarn

* chore: change names

* chore: test

* chore: change test naem
  • Loading branch information
junghyeonsu committed Jun 27, 2024
1 parent 36de8f5 commit 0849de7
Show file tree
Hide file tree
Showing 17 changed files with 682 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ jobs:
run: yarn workspace @seed-design/dom-utils build

- name: Run tests
run: yarn test:react
run: yarn test:react:once
Binary file not shown.
1 change: 1 addition & 0 deletions examples/stackflow-spa/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"@seed-design/icon": "^0.6.1",
"@seed-design/react-radio-group": "0.0.0",
"@seed-design/react-switch": "0.0.0",
"@seed-design/react-text-field": "0.0.0",
"@seed-design/recipe": "0.0.0",
"@seed-design/stylesheet": "1.0.4",
"@stackflow/core": "^1.0.8",
Expand Down
1 change: 1 addition & 0 deletions examples/stackflow-spa/src/activities/ActivityHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const ActivityHome: ActivityComponentType = () => {
<BoxButton onClick={() => push("ActivityCallout", {})}>Callout</BoxButton>
<BoxButton onClick={() => push("ActivityCallout", {})}>Callout</BoxButton>
<BoxButton onClick={() => push("ActivitySwitch", {})}>Switch</BoxButton>
<BoxButton onClick={() => push("ActivityTextField", {})}>TextField</BoxButton>
</div>
</AppScreen>
);
Expand Down
14 changes: 14 additions & 0 deletions examples/stackflow-spa/src/activities/ActivityTextField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { ActivityComponentType } from "@stackflow/react";

import { AppScreen } from "@stackflow/plugin-basic-ui";
import { TextField } from "../design-system/components";

const ActivityTextField: ActivityComponentType = () => {
return (
<AppScreen appBar={{ title: "TextField" }}>
<TextField variant="outlined" />
</AppScreen>
);
};

export default ActivityTextField;
120 changes: 120 additions & 0 deletions examples/stackflow-spa/src/design-system/components/TextField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import clsx from "clsx";
import * as React from "react";
import { useTextField, type UseTextFieldProps } from "@seed-design/react-text-field";

import type { Assign } from "../util/types";

// TODO: Change
// import "@seed-design/stylesheet/textfield.css";

export interface TextFieldProps
extends Assign<
Omit<React.InputHTMLAttributes<HTMLInputElement>, "size" | "prefix" | "suffix">,
UseTextFieldProps
> {
/**
* @default "medium"
*/
size?: "small" | "medium" | "large";

/**
* @default "outlined"
*/
variant: "outlined" | "underlined";

requiredIndicator?: string;

optionalIndicator?: string;

prefix?: React.ReactNode;

suffix?: React.ReactNode;

label?: string;

description?: string;

errorMessage?: string;

hideCharacterCount?: boolean;
}

export const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>((props, ref) => {
const {
className,
size = "medium",
variant = "outlined",
requiredIndicator,
optionalIndicator,
prefix,
suffix,
label,
description,
errorMessage,
hideCharacterCount,
maxLength,
} = props;
const {
rootProps,
inputProps,
labelProps,
descriptionProps,
errorMessageProps,
stateProps,
restProps,
isInvalid,
isRequired,
graphemes,
} = useTextField({ ...props, elementType: "input" });

const showErrorMessage = isInvalid && !!errorMessage;
const indicator = isRequired ? requiredIndicator : optionalIndicator;
const showHint = !!description || (errorMessage && isInvalid);
const renderCharacterCount = !hideCharacterCount && maxLength;
const renderFoot = showHint || renderCharacterCount;
const renderHead = label || indicator;

return (
<div className={clsx(className)} {...stateProps} {...rootProps}>
{renderHead && (
<div data-part="head">
{label && <label {...labelProps}>{label}</label>}
{indicator && <span data-part="indicator">{indicator}</span>}
</div>
)}
<div data-part="field">
{prefix && (
<div data-part="prefix">
{typeof prefix === "string" ? <span>{prefix}</span> : prefix}
</div>
)}
<input ref={ref} {...inputProps} {...restProps} />
{suffix && (
<div data-part="suffix">
{typeof suffix === "string" ? <span>{suffix}</span> : suffix}
</div>
)}
</div>
{renderFoot && (
<div data-part="foot">
{showErrorMessage ? (
<span {...stateProps} {...errorMessageProps}>
{errorMessage && errorMessage}
</span>
) : (
<span {...stateProps} {...descriptionProps}>
{description}
</span>
)}
{renderCharacterCount && (
<div {...stateProps} data-part="count-container">
<span data-part="character-count">{graphemes.length}</span>
<span data-part="max-count">/{maxLength}</span>
</div>
)}
</div>
)}
</div>
);
});
TextField.displayName = "TextField";
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from "./Flex";
export * from "./RadioGroup";
export * from "./Text";
export * from "./Switch";
export * from "./TextField";
2 changes: 2 additions & 0 deletions examples/stackflow-spa/src/stackflow/Stack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const { Stack, useFlow, useStepFlow } = stackflow({
ActivityRadioGroup: React.lazy(() => import("../activities/ActivityRadioGroup")),
ActivityCheckbox: React.lazy(() => import("../activities/ActivityCheckbox")),
ActivityAlertDialog: React.lazy(() => import("../activities/ActivityAlertDialog")),
ActivityTextField: React.lazy(() => import("../activities/ActivityTextField")),
ActivityChip: React.lazy(() => import("../activities/ActivityChip")),
ActivityCallout: React.lazy(() => import("../activities/ActivityCallout")),
ActivitySwitch: React.lazy(() => import("../activities/ActivitySwitch")),
Expand Down Expand Up @@ -57,6 +58,7 @@ const { Stack, useFlow, useStepFlow } = stackflow({
ActivityCallout: "/callout",
ActivitySwitch: "/switch",
ActivityNotFound: "/404",
ActivityTextField: "/text-field",
},
}),
],
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@
"version": "changeset version && yarn build-only-package && yarn install --no-immutable",
"lint:all": "yarn biome lint --fix",
"format:all": "yarn biome format --fix",
"test:react": "vitest run",
"test:react:watch": "vitest dev"
"test:all": "yarn vitest run",
"test:spec:once": "yarn vitest run --project component-spec",
"test:spec:watch": "vitest dev --project component-spec",
"test:react:once": "yarn vitest run --project react-headless",
"test:react:watch": "vitest dev --project react-headless"
},
"devDependencies": {
"@biomejs/biome": "^1.8.0",
Expand Down
8 changes: 0 additions & 8 deletions packages/component-spec/core/vitest.config.mts

This file was deleted.

47 changes: 47 additions & 0 deletions packages/react-headless/text-field/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "@seed-design/react-text-field",
"version": "0.0.0",
"repository": {
"type": "git",
"url": "git+https://github.com/daangn/seed-design.git",
"directory": "packages/react-headless/text-field"
},
"sideEffects": false,
"exports": {
".": {
"types": "./lib/index.d.ts",
"require": "./lib/index.js",
"import": "./lib/index.mjs"
}
},
"main": "./lib/index.js",
"files": [
"lib",
"src"
],
"scripts": {
"prepack": "rm -rf lib && yarn build",
"build": "nanobundle build"
},
"dependencies": {
"@radix-ui/react-use-controllable-state": "1.0.1",
"@seed-design/dom-utils": "0.0.0",
"unicode-segmenter": "^0.7.0"
},
"devDependencies": {
"nanobundle": "^1.6.0"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
},
"publishConfig": {
"access": "public"
},
"ultra": {
"concurrent": [
"dev",
"build"
]
}
}
Loading

0 comments on commit 0849de7

Please sign in to comment.