From 546052bd5b1be6b917116084861d91bbd961c502 Mon Sep 17 00:00:00 2001 From: mitsuyapega Date: Wed, 4 Dec 2024 13:35:21 +0900 Subject: [PATCH 1/5] feat: japanese input --- .../Pega_Extensions_JapaneseInput/Docs.mdx | 14 + .../PConnProps.d.ts | 46 +++ .../StatusWork.tsx | 26 ++ .../Pega_Extensions_JapaneseInput/config.json | 129 ++++++++ .../create-nonce.ts | 6 + .../demo.stories.tsx | 76 +++++ .../demo.test.tsx | 10 + .../event-utils.ts | 18 ++ .../Pega_Extensions_JapaneseInput/index.tsx | 300 ++++++++++++++++++ .../Pega_Extensions_JapaneseInput/mock.ts | 32 ++ .../Pega_Extensions_JapaneseInput/styles.ts | 8 + .../suggestions-handler.ts | 9 + 12 files changed, 674 insertions(+) create mode 100644 src/components/Pega_Extensions_JapaneseInput/Docs.mdx create mode 100644 src/components/Pega_Extensions_JapaneseInput/PConnProps.d.ts create mode 100644 src/components/Pega_Extensions_JapaneseInput/StatusWork.tsx create mode 100644 src/components/Pega_Extensions_JapaneseInput/config.json create mode 100644 src/components/Pega_Extensions_JapaneseInput/create-nonce.ts create mode 100644 src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx create mode 100644 src/components/Pega_Extensions_JapaneseInput/demo.test.tsx create mode 100644 src/components/Pega_Extensions_JapaneseInput/event-utils.ts create mode 100644 src/components/Pega_Extensions_JapaneseInput/index.tsx create mode 100644 src/components/Pega_Extensions_JapaneseInput/mock.ts create mode 100644 src/components/Pega_Extensions_JapaneseInput/styles.ts create mode 100644 src/components/Pega_Extensions_JapaneseInput/suggestions-handler.ts diff --git a/src/components/Pega_Extensions_JapaneseInput/Docs.mdx b/src/components/Pega_Extensions_JapaneseInput/Docs.mdx new file mode 100644 index 0000000..bdf9072 --- /dev/null +++ b/src/components/Pega_Extensions_JapaneseInput/Docs.mdx @@ -0,0 +1,14 @@ +import { Meta, Primary, Controls, Story } from '@storybook/blocks'; +import * as DemoStories from './demo.stories'; + + + +# Overview + +This component that converts Japanese input, such as kana characters and full-width characters. + + + +## Props + + diff --git a/src/components/Pega_Extensions_JapaneseInput/PConnProps.d.ts b/src/components/Pega_Extensions_JapaneseInput/PConnProps.d.ts new file mode 100644 index 0000000..58abec9 --- /dev/null +++ b/src/components/Pega_Extensions_JapaneseInput/PConnProps.d.ts @@ -0,0 +1,46 @@ +import {PConnect } from '@pega/pcore-pconnect-typedefs'; + +// PConnProps.d.ts +// This gives us a place to have each component (which is most DX Components) that is +// expected to have a getPConnect extend its props (from BaseProps) +// such that every component will be expected to have a getPConnect() function +// that returns a PConnect object. (new/better way of doing .propTypes). +// This PConnProps can be extended to include other props that we know are in every component +export interface PConnProps { + // getPConnect should exist for every C11n component. (add @ts-ignore in special cases where it isn't) + getPConnect: () => typeof PConnect; + + // Allow any/all other key/value pairs in the BaseProps for now + // TODO: refine which other props are always expected for various component + // types and consider further interface "subclassing". For example, we may + // want to create a "BasePropsForm" that gives guidance on required, readonly, etc. + // and any other props that every Form component expects. + // For example, see the PConnFieldProps below. + // NOTE: if you uncomment the line below, the PConnProps type will allow + // otherwise undefined types to appear. This can be helpful for debugging + // or when adding new components whose types aren't yet known. + // [key: string]: any; +} + + +// PConnFieldProps extends PConnProps to bring in the common properties that are +// associated with most field components (ex: Dropdown, TextInput, etc.) in the +// components/field directory +export interface PConnFieldProps extends PConnProps { + label: string, + required: boolean, + disabled: boolean, + value: any, + validatemessage: string, + status?: string, + onChange?: any, + onBlur?: any, + readOnly: boolean, + testId: string, + helperText: string, + displayMode?: string, + hideLabel: boolean, + placeholder?: string, + fieldMetadata?: any, + additionalProps?: any +} diff --git a/src/components/Pega_Extensions_JapaneseInput/StatusWork.tsx b/src/components/Pega_Extensions_JapaneseInput/StatusWork.tsx new file mode 100644 index 0000000..5f81379 --- /dev/null +++ b/src/components/Pega_Extensions_JapaneseInput/StatusWork.tsx @@ -0,0 +1,26 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import React from 'react'; +import { Status } from '@pega/cosmos-react-core'; + +export default function StatusWorkRenderer({ value }: any) { + // need to cast variant + let variant: 'success' | 'urgent' | 'warn' | 'pending' | 'info'; + variant = 'info'; + + const warnStrings = ['fail', 'cancel', 'reject', 'revoke', 'stopped', 'warn']; + const infoStrings = ['open', 'hold', 'info', 'new']; + const successStrings = ['resolved', 'completed', 'success']; + const pendingStrings = ['pending']; + + if (new RegExp(warnStrings.join('|'), 'i').test(value)) { + variant = 'warn'; + } else if (new RegExp(infoStrings.join('|'), 'i').test(value)) { + variant = 'info'; + } else if (new RegExp(successStrings.join('|'), 'i').test(value)) { + variant = 'success'; + } else if (new RegExp(pendingStrings.join('|'), 'i').test(value)) { + variant = 'pending'; + } + + return {value}; +} diff --git a/src/components/Pega_Extensions_JapaneseInput/config.json b/src/components/Pega_Extensions_JapaneseInput/config.json new file mode 100644 index 0000000..fec23c2 --- /dev/null +++ b/src/components/Pega_Extensions_JapaneseInput/config.json @@ -0,0 +1,129 @@ +{ + "name": "Pega_Extensions_JapaneseInput", + "label": "Japanese Input", + "description": "", + "organization": "Pega", + "version": "1.0.0", + "library": "Extensions", + "allowedApplications": [], + "componentKey": "Pega_Extensions_JapaneseInput", + "type": "Field", + "subtype": "Text", + "icon": "images/pz-text-input-active.svg", + "properties": [ + { + "name": "label", + "label": "Field label", + "format": "TEXT", + "required": true + }, + { + "name": "readOnly", + "label": "Edit mode", + "format": "READONLY" + }, + { + "label": "Column settings", + "format": "GROUP", + "visibility": "@VIEWTYPE == 'MultiRecordDisplayAsTable'", + "properties": [ + { + "name": "columnWidth", + "label": "Column width", + "format": "SELECT", + "source": [ + { + "key": "auto", + "value": "Auto" + }, + { + "key": "custom", + "value": "Custom" + } + ] + }, + { + "name": "width", + "label": "Width (px)", + "format": "NUMBER", + "visibility": "$this.columnWidth == 'custom'" + } + ] + }, + { + "label": "Input settings", + "format": "GROUP", + "visibility": "(!readOnly = true)", + "properties": [ + { + "name": "placeholder", + "label": "Placeholder", + "format": "TEXT" + }, + { + "name": "helperText", + "label": "Helper text", + "format": "TEXT" + }, + { + "name": "hiraganaToKatakana", + "label": "Hiragana to Katakana", + "format": "BOOLEAN" + }, + { + "name": "fullToHalf", + "label": "Full-Width to Half-Width", + "format": "BOOLEAN" + }, + { + "name": "lowerToUpper", + "label": "Lowercase to Uppercase", + "format": "BOOLEAN" + } + ] + }, + { + "label": "Conditions", + "format": "GROUP", + "properties": [ + { + "name": "required", + "label": "Required", + "format": "REQUIRED", + "visibility": "(!readOnly = true)" + }, + { + "name": "disabled", + "label": "Disabled", + "format": "DISABLED", + "visibility": "(!readOnly = true)" + }, + { + "name": "visibility", + "label": "Visibility", + "format": "VISIBILITY" + } + ] + }, + { + "label": "Advanced", + "format": "GROUP", + "collapsible": true, + "properties": [ + { + "name": "testId", + "label": "Test ID", + "format": "TEXT", + "ignorePattern": "[^-_\\p{N}\\p{L}]", + "includeAnnotations": false + } + ] + } + ], + "defaultConfig": { + "label": "@L $this.label", + "detailFVLItem": true, + "isFormWidth" : false, + "isContainerWidth" : false + } +} diff --git a/src/components/Pega_Extensions_JapaneseInput/create-nonce.ts b/src/components/Pega_Extensions_JapaneseInput/create-nonce.ts new file mode 100644 index 0000000..b58e2be --- /dev/null +++ b/src/components/Pega_Extensions_JapaneseInput/create-nonce.ts @@ -0,0 +1,6 @@ +/* eslint-disable camelcase */ +// @ts-ignore +if (window?.__webpack_nonce__) { + // @ts-ignore + __webpack_nonce__ = window.__webpack_nonce__; +} diff --git a/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx b/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx new file mode 100644 index 0000000..4e9aa2b --- /dev/null +++ b/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx @@ -0,0 +1,76 @@ +/* eslint-disable react/jsx-no-useless-fragment */ +import type { Meta, StoryObj } from '@storybook/react'; + +import PegaExtensionsJapaneseInput from './index'; + +import { stateProps, fieldMetadata, configProps } from './mock'; + +const meta: Meta = { + title: 'Fields/JapaneseInput', + component: PegaExtensionsJapaneseInput, + excludeStories: /.*Data$/ +}; + +export default meta; +type Story = StoryObj; + +export const BasePegaExtensionsJapaneseInput: Story = (args: any) => { + const props = { + value: configProps.value, + hasSuggestions: configProps.hasSuggestions, + fieldMetadata, + getPConnect: () => { + return { + getStateProps: () => { + return stateProps; + }, + getActionsApi: () => { + return { + updateFieldValue: () => { + /* nothing */ + }, + triggerFieldChange: () => { + /* nothing */ + } + }; + }, + ignoreSuggestion: () => { + /* nothing */ + }, + acceptSuggestion: () => { + /* nothing */ + }, + setInheritedProps: () => { + /* nothing */ + }, + resolveConfigProps: () => { + /* nothing */ + } + }; + } + }; + + return ( + <> + + + ); +}; + +BasePegaExtensionsJapaneseInput.args = { + label: configProps.label, + helperText: configProps.helperText, + placeholder: configProps.placeholder, + testId: configProps.testId, + readOnly: configProps.readOnly, + disabled: configProps.disabled, + required: configProps.required, + status: configProps.status, + hideLabel: configProps.hideLabel, + displayMode: configProps.displayMode, + variant: configProps.variant, + validatemessage: configProps.validatemessage, + hiraganaToKatakana: configProps.hiraganaToKatakana, + fullToHalf: configProps.fullToHalf, + lowerToUpper: configProps.lowerToUpper +}; diff --git a/src/components/Pega_Extensions_JapaneseInput/demo.test.tsx b/src/components/Pega_Extensions_JapaneseInput/demo.test.tsx new file mode 100644 index 0000000..30b577c --- /dev/null +++ b/src/components/Pega_Extensions_JapaneseInput/demo.test.tsx @@ -0,0 +1,10 @@ +import { render, screen } from '@testing-library/react'; +import { composeStories } from '@storybook/react'; +import * as DemoStories from './demo.stories'; + +const { BasePegaExtensionsJapaneseInput } = composeStories(DemoStories); + +test('renders Password Input component with default args', async () => { + render(); + expect(await screen.findByText('TextInput Sample')).toBeVisible(); +}); diff --git a/src/components/Pega_Extensions_JapaneseInput/event-utils.ts b/src/components/Pega_Extensions_JapaneseInput/event-utils.ts new file mode 100644 index 0000000..5360375 --- /dev/null +++ b/src/components/Pega_Extensions_JapaneseInput/event-utils.ts @@ -0,0 +1,18 @@ +const handleEvent = (actions: any, eventType: string, propName: string, value: string) => { + switch (eventType) { + case 'change': + actions.updateFieldValue(propName, value); + break; + case 'blur': + actions.triggerFieldChange(propName, value); + break; + case 'changeNblur': + actions.updateFieldValue(propName, value); + actions.triggerFieldChange(propName, value); + break; + default: + break; + } +}; + +export default handleEvent; \ No newline at end of file diff --git a/src/components/Pega_Extensions_JapaneseInput/index.tsx b/src/components/Pega_Extensions_JapaneseInput/index.tsx new file mode 100644 index 0000000..ee6620a --- /dev/null +++ b/src/components/Pega_Extensions_JapaneseInput/index.tsx @@ -0,0 +1,300 @@ +import { useEffect, useState, useRef } from 'react'; +import { + Input, + FieldValueList, + Text, + EmailDisplay, + PhoneDisplay, + URLDisplay, + withConfiguration +} from '@pega/cosmos-react-core'; +import type { PConnFieldProps } from './PConnProps'; +import './create-nonce'; + +// include in bundle +import handleEvent from './event-utils'; +import StatusWorkRenderer from './StatusWork'; +import { suggestionsHandler } from './suggestions-handler'; + +import StyledPegaExtensionsJapaneseInputWrapper from './styles'; + +// interface for props +interface PegaExtensionsJapaneseInputProps extends PConnFieldProps { + // If any, enter additional props that only exist on TextInput here + displayAsStatus?: boolean; + isTableFormatter?: boolean; + hasSuggestions?: boolean; + variant?: any; + formatter: string; + hiraganaToKatakana: boolean; + fullToHalf: boolean; + lowerToUpper: boolean; +} + +// interface for StateProps object +interface StateProps { + value: string; + hasSuggestions: boolean; +} + +export const formatExists = (formatterVal: string) => { + const formatterValues = [ + 'TextInput', + 'WorkStatus', + 'RichText', + 'Email', + 'Phone', + 'URL', + 'Operator' + ]; + let isformatter = false; + if (formatterValues.includes(formatterVal)) { + isformatter = true; + } + return isformatter; +}; + +export const textFormatter = (formatter: string, value: string) => { + let displayComponent: any = null; + switch (formatter) { + case 'TextInput': { + displayComponent = value; + break; + } + case 'Email': { + displayComponent = ; + break; + } + case 'Phone': { + displayComponent = ; + break; + } + case 'URL': { + displayComponent = ( + + ); + break; + } + // no default + } + return displayComponent; +}; + +// Duplicated runtime code from Constellation Design System Component + +// props passed in combination of props from property panel (config.json) and run time props from Constellation +// any default values in config.pros should be set in defaultProps at bottom of this file +function PegaExtensionsJapaneseInput(props: PegaExtensionsJapaneseInputProps) { + const { + getPConnect, + placeholder, + validatemessage, + label, + hideLabel = false, + helperText, + testId, + fieldMetadata = {}, + additionalProps = {}, + displayMode, + displayAsStatus, + variant = 'inline', + hasSuggestions = false, + isTableFormatter = false, + hiraganaToKatakana = false, + fullToHalf = false, + lowerToUpper = false + } = props; + const { formatter } = props; + const pConn = getPConnect(); + const actions = pConn.getActionsApi(); + const stateProps = pConn.getStateProps() as StateProps; + const propName: string = stateProps.value; + const maxLength = fieldMetadata?.maxLength; + const hasValueChange = useRef(false); + + let { value, readOnly = false, required = false, disabled = false } = props; + [readOnly, required, disabled] = [readOnly, required, disabled].map( + prop => prop === true || (typeof prop === 'string' && prop === 'true') + ); + + const [inputValue, setInputValue] = useState(value); + const [status, setStatus] = useState(hasSuggestions ? 'pending' : undefined); + + // cast status + let myStatus: 'success' | 'warning' | 'error' | 'pending'; + // eslint-disable-next-line prefer-const + myStatus = status as 'success' | 'warning' | 'error' | 'pending'; + + useEffect(() => setInputValue(value), [value]); + + useEffect(() => { + if (validatemessage !== '') { + setStatus('error'); + } + if (hasSuggestions) { + setStatus('pending'); + } else if (!hasSuggestions && myStatus !== 'success') { + setStatus(validatemessage !== '' ? 'error' : undefined); + } + }, [validatemessage, hasSuggestions, myStatus]); + + const onResolveSuggestionHandler = (accepted: boolean) => { + suggestionsHandler(accepted, pConn, setStatus); + }; + // Override the value to render as status work when prop passed to display as status + if (displayAsStatus) { + value = StatusWorkRenderer({ value }); + + // Fall into this scenario for case summary, default to stacked status + if (!displayMode) { + return ( + + ); + } + } + + if (displayMode === 'LABELS_LEFT' || displayMode === 'DISPLAY_ONLY') { + let displayComp = value || ; + if (isTableFormatter && formatExists(formatter)) { + displayComp = textFormatter(formatter, value); + } + return displayMode === 'DISPLAY_ONLY' ? ( + + {' '} + {displayComp}{' '} + + ) : ( + + + + ); + } + + if (displayMode === 'STACKED_LARGE_VAL') { + const isValDefined = typeof value !== 'undefined' && value !== ''; + const val = isValDefined ? ( + + {value} + + ) : ( + '' + ); + return ( + + + + ); + } + + const handleChange = (event: any) => { + if (hasSuggestions) { + setStatus(undefined); + } + setInputValue(event.target.value); + if (value !== event.target.value) { + handleEvent(actions, 'change', propName, event.target.value); + hasValueChange.current = true; + } + }; + + const handleBlur = (event: any) => { + if ((!value || hasValueChange.current) && !readOnly) { + handleEvent(actions, 'blur', propName, event.target.value); + if (hasSuggestions) { + pConn.ignoreSuggestion(''); + } + hasValueChange.current = false; + } + + const convertHiraganaToKatakana = (str: string) => { + return str.replace(/[\u3041-\u3096]/g, match => { + return String.fromCharCode(match.charCodeAt(0) + 0x60); + }); + }; + const fullWidthToHalfWidth = (str: string): string => { + str = str.replace(/[A-Za-z0-9]/g, match => { + return String.fromCharCode(match.charCodeAt(0) - 0xfee0); + }); + // prettier-ignore + const kanaMap: Record = { + ガ: 'ガ', ギ: 'ギ', グ: 'グ', ゲ: 'ゲ', ゴ: 'ゴ', + ザ: 'ザ', ジ: 'ジ', ズ: 'ズ', ゼ: 'ゼ', ゾ: 'ゾ', + ダ: 'ダ', ヂ: 'ヂ', ヅ: 'ヅ', デ: 'デ', ド: 'ド', + バ: 'バ', ビ: 'ビ', ブ: 'ブ', ベ: 'ベ', ボ: 'ボ', + パ: 'パ', ピ: 'ピ', プ: 'プ', ペ: 'ペ', ポ: 'ポ', + ヴ: 'ヴ', ヷ: 'ヷ', ヺ: 'ヺ', + ア: 'ア', イ: 'イ', ウ: 'ウ', エ: 'エ', オ: 'オ', + カ: 'カ', キ: 'キ', ク: 'ク', ケ: 'ケ', コ: 'コ', + サ: 'サ', シ: 'シ', ス: 'ス', セ: 'セ', ソ: 'ソ', + タ: 'タ', チ: 'チ', ツ: 'ツ', テ: 'テ', ト: 'ト', + ナ: 'ナ', ニ: 'ニ', ヌ: 'ヌ', ネ: 'ネ', ノ: 'ノ', + ハ: 'ハ', ヒ: 'ヒ', フ: 'フ', ヘ: 'ヘ', ホ: 'ホ', + マ: 'マ', ミ: 'ミ', ム: 'ム', メ: 'メ', モ: 'モ', + ヤ: 'ヤ', ユ: 'ユ', ヨ: 'ヨ', + ラ: 'ラ', リ: 'リ', ル: 'ル', レ: 'レ', ロ: 'ロ', + ワ: 'ワ', ヲ: 'ヲ', ン: 'ン', + ァ: 'ァ', ィ: 'ィ', ゥ: 'ゥ', ェ: 'ェ', ォ: 'ォ', + ッ: 'ッ', ャ: 'ャ', ュ: 'ュ', ョ: 'ョ', + '。': '。', '、': '、', ー: 'ー', '「': '「', '」': '」', '・': '・' + }; + const reg = new RegExp(`(${Object.keys(kanaMap).join('|')})`, 'g'); + return str + .replace(reg, match => { + return kanaMap[match]; + }) + .replace(/゛/g, '゙') + .replace(/゜/g, '゚'); + }; + let newValue = event.target.value; + if (hiraganaToKatakana) { + newValue = convertHiraganaToKatakana(newValue); + } + if (fullToHalf) { + newValue = fullWidthToHalfWidth(newValue); + } + if (lowerToUpper) { + newValue = newValue.toUpperCase(); + } + if (event.target.value !== newValue) { + setInputValue(newValue); + actions.updateFieldValue(propName, newValue); + } + }; + + return ( + + + + ); +} + +export default withConfiguration(PegaExtensionsJapaneseInput); diff --git a/src/components/Pega_Extensions_JapaneseInput/mock.ts b/src/components/Pega_Extensions_JapaneseInput/mock.ts new file mode 100644 index 0000000..be397da --- /dev/null +++ b/src/components/Pega_Extensions_JapaneseInput/mock.ts @@ -0,0 +1,32 @@ +export const configProps = { + value: '', + label: 'TextInput Sample', + placeholder: 'TextInput Placeholder', + helperText: 'TextInput Helper Text', + testId: 'TextInput-12345678', + hasSuggestions: false, + displayMode: '', + variant: '', + hideLabel: false, + readOnly: false, + required: false, + disabled: false, + status: '', + validatemessage: '', + hiraganaToKatakana: false, + fullToHalf: false, + lowerToUpper: false +}; + +export const stateProps = { + value: '.TextInputSample', + hasSuggestions: false +}; + +export const fieldMetadata = { + classID: 'DIXL-MediaCo-Work-NewService', + type: 'Text', + maxLength: 256, + displayAs: 'pxTextInput', + label: 'TextInput Sample' +}; diff --git a/src/components/Pega_Extensions_JapaneseInput/styles.ts b/src/components/Pega_Extensions_JapaneseInput/styles.ts new file mode 100644 index 0000000..737e0c9 --- /dev/null +++ b/src/components/Pega_Extensions_JapaneseInput/styles.ts @@ -0,0 +1,8 @@ +// individual style, comment out above, and uncomment here and add styles +import styled, { css } from 'styled-components'; + +export default styled.div(() => { + return css` + margin: 0px 0; + `; +}); diff --git a/src/components/Pega_Extensions_JapaneseInput/suggestions-handler.ts b/src/components/Pega_Extensions_JapaneseInput/suggestions-handler.ts new file mode 100644 index 0000000..68f6e9f --- /dev/null +++ b/src/components/Pega_Extensions_JapaneseInput/suggestions-handler.ts @@ -0,0 +1,9 @@ +export const suggestionsHandler = (accepted: boolean, pConn: any, setStatus: any) => { + if (accepted) { + pConn.acceptSuggestion(); + setStatus('success'); + } else { + pConn.ignoreSuggestion(); + setStatus(undefined); + } +}; From cc11469b3500c9066305e4fe2e12338bf3181022 Mon Sep 17 00:00:00 2001 From: mitsuyapega Date: Tue, 10 Dec 2024 11:15:05 +0900 Subject: [PATCH 2/5] fixup! feat: japanese input --- .../PConnProps.d.ts | 46 ---- .../StatusWork.tsx | 26 --- .../create-nonce.ts | 6 - .../demo.stories.tsx | 123 ++++++----- .../demo.test.tsx | 6 +- .../event-utils.ts | 18 -- .../Pega_Extensions_JapaneseInput/index.tsx | 196 +++++------------- .../Pega_Extensions_JapaneseInput/mock.ts | 32 --- .../Pega_Extensions_JapaneseInput/styles.ts | 8 - .../suggestions-handler.ts | 9 - 10 files changed, 121 insertions(+), 349 deletions(-) delete mode 100644 src/components/Pega_Extensions_JapaneseInput/PConnProps.d.ts delete mode 100644 src/components/Pega_Extensions_JapaneseInput/StatusWork.tsx delete mode 100644 src/components/Pega_Extensions_JapaneseInput/create-nonce.ts delete mode 100644 src/components/Pega_Extensions_JapaneseInput/event-utils.ts delete mode 100644 src/components/Pega_Extensions_JapaneseInput/mock.ts delete mode 100644 src/components/Pega_Extensions_JapaneseInput/styles.ts delete mode 100644 src/components/Pega_Extensions_JapaneseInput/suggestions-handler.ts diff --git a/src/components/Pega_Extensions_JapaneseInput/PConnProps.d.ts b/src/components/Pega_Extensions_JapaneseInput/PConnProps.d.ts deleted file mode 100644 index 58abec9..0000000 --- a/src/components/Pega_Extensions_JapaneseInput/PConnProps.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -import {PConnect } from '@pega/pcore-pconnect-typedefs'; - -// PConnProps.d.ts -// This gives us a place to have each component (which is most DX Components) that is -// expected to have a getPConnect extend its props (from BaseProps) -// such that every component will be expected to have a getPConnect() function -// that returns a PConnect object. (new/better way of doing .propTypes). -// This PConnProps can be extended to include other props that we know are in every component -export interface PConnProps { - // getPConnect should exist for every C11n component. (add @ts-ignore in special cases where it isn't) - getPConnect: () => typeof PConnect; - - // Allow any/all other key/value pairs in the BaseProps for now - // TODO: refine which other props are always expected for various component - // types and consider further interface "subclassing". For example, we may - // want to create a "BasePropsForm" that gives guidance on required, readonly, etc. - // and any other props that every Form component expects. - // For example, see the PConnFieldProps below. - // NOTE: if you uncomment the line below, the PConnProps type will allow - // otherwise undefined types to appear. This can be helpful for debugging - // or when adding new components whose types aren't yet known. - // [key: string]: any; -} - - -// PConnFieldProps extends PConnProps to bring in the common properties that are -// associated with most field components (ex: Dropdown, TextInput, etc.) in the -// components/field directory -export interface PConnFieldProps extends PConnProps { - label: string, - required: boolean, - disabled: boolean, - value: any, - validatemessage: string, - status?: string, - onChange?: any, - onBlur?: any, - readOnly: boolean, - testId: string, - helperText: string, - displayMode?: string, - hideLabel: boolean, - placeholder?: string, - fieldMetadata?: any, - additionalProps?: any -} diff --git a/src/components/Pega_Extensions_JapaneseInput/StatusWork.tsx b/src/components/Pega_Extensions_JapaneseInput/StatusWork.tsx deleted file mode 100644 index 5f81379..0000000 --- a/src/components/Pega_Extensions_JapaneseInput/StatusWork.tsx +++ /dev/null @@ -1,26 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import React from 'react'; -import { Status } from '@pega/cosmos-react-core'; - -export default function StatusWorkRenderer({ value }: any) { - // need to cast variant - let variant: 'success' | 'urgent' | 'warn' | 'pending' | 'info'; - variant = 'info'; - - const warnStrings = ['fail', 'cancel', 'reject', 'revoke', 'stopped', 'warn']; - const infoStrings = ['open', 'hold', 'info', 'new']; - const successStrings = ['resolved', 'completed', 'success']; - const pendingStrings = ['pending']; - - if (new RegExp(warnStrings.join('|'), 'i').test(value)) { - variant = 'warn'; - } else if (new RegExp(infoStrings.join('|'), 'i').test(value)) { - variant = 'info'; - } else if (new RegExp(successStrings.join('|'), 'i').test(value)) { - variant = 'success'; - } else if (new RegExp(pendingStrings.join('|'), 'i').test(value)) { - variant = 'pending'; - } - - return {value}; -} diff --git a/src/components/Pega_Extensions_JapaneseInput/create-nonce.ts b/src/components/Pega_Extensions_JapaneseInput/create-nonce.ts deleted file mode 100644 index b58e2be..0000000 --- a/src/components/Pega_Extensions_JapaneseInput/create-nonce.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint-disable camelcase */ -// @ts-ignore -if (window?.__webpack_nonce__) { - // @ts-ignore - __webpack_nonce__ = window.__webpack_nonce__; -} diff --git a/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx b/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx index 4e9aa2b..b9975e4 100644 --- a/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx +++ b/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx @@ -1,12 +1,10 @@ /* eslint-disable react/jsx-no-useless-fragment */ import type { Meta, StoryObj } from '@storybook/react'; -import PegaExtensionsJapaneseInput from './index'; - -import { stateProps, fieldMetadata, configProps } from './mock'; +import { PegaExtensionsJapaneseInput } from './index'; const meta: Meta = { - title: 'Fields/JapaneseInput', + title: 'Fields/Japanese Input', component: PegaExtensionsJapaneseInput, excludeStories: /.*Data$/ }; @@ -14,63 +12,74 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const BasePegaExtensionsJapaneseInput: Story = (args: any) => { - const props = { - value: configProps.value, - hasSuggestions: configProps.hasSuggestions, - fieldMetadata, - getPConnect: () => { +const setPCore = () => { + (window as any).PCore = { + getComponentsRegistry: () => { + return { + getLazyComponent: (f: string) => f + }; + }, + getEnvironmentInfo: () => { return { - getStateProps: () => { - return stateProps; - }, - getActionsApi: () => { - return { - updateFieldValue: () => { - /* nothing */ - }, - triggerFieldChange: () => { - /* nothing */ - } - }; - }, - ignoreSuggestion: () => { - /* nothing */ - }, - acceptSuggestion: () => { - /* nothing */ - }, - setInheritedProps: () => { - /* nothing */ - }, - resolveConfigProps: () => { - /* nothing */ - } + getTimeZone: () => 'local' }; } }; - - return ( - <> - - - ); }; -BasePegaExtensionsJapaneseInput.args = { - label: configProps.label, - helperText: configProps.helperText, - placeholder: configProps.placeholder, - testId: configProps.testId, - readOnly: configProps.readOnly, - disabled: configProps.disabled, - required: configProps.required, - status: configProps.status, - hideLabel: configProps.hideLabel, - displayMode: configProps.displayMode, - variant: configProps.variant, - validatemessage: configProps.validatemessage, - hiraganaToKatakana: configProps.hiraganaToKatakana, - fullToHalf: configProps.fullToHalf, - lowerToUpper: configProps.lowerToUpper +export const Default: Story = { + render: args => { + setPCore(); + const props = { + ...args, + getPConnect: () => { + return { + getStateProps: () => { + return { + value: '.TextInputSample' + }; + }, + getActionsApi: () => { + return { + updateFieldValue: () => { + /* nothing */ + }, + triggerFieldChange: () => { + /* nothing */ + } + }; + }, + ignoreSuggestion: () => { + /* nothing */ + }, + acceptSuggestion: () => { + /* nothing */ + }, + setInheritedProps: () => { + /* nothing */ + }, + resolveConfigProps: () => { + /* nothing */ + } + }; + } + }; + return ; + }, + args: { + label: 'TextInput Sample', + helperText: 'TextInput Helper Text', + placeholder: 'TextInput Placeholder', + testId: 'TextInput-12345678', + readOnly: false, + disabled: false, + required: false, + hideLabel: false, + displayMode: '', + variant: '', + validatemessage: '', + hiraganaToKatakana: false, + fullToHalf: false, + lowerToUpper: false + } }; diff --git a/src/components/Pega_Extensions_JapaneseInput/demo.test.tsx b/src/components/Pega_Extensions_JapaneseInput/demo.test.tsx index 30b577c..258161f 100644 --- a/src/components/Pega_Extensions_JapaneseInput/demo.test.tsx +++ b/src/components/Pega_Extensions_JapaneseInput/demo.test.tsx @@ -2,9 +2,9 @@ import { render, screen } from '@testing-library/react'; import { composeStories } from '@storybook/react'; import * as DemoStories from './demo.stories'; -const { BasePegaExtensionsJapaneseInput } = composeStories(DemoStories); +const { Default } = composeStories(DemoStories); -test('renders Password Input component with default args', async () => { - render(); +test('renders Japanese Input component with default args', async () => { + render(); expect(await screen.findByText('TextInput Sample')).toBeVisible(); }); diff --git a/src/components/Pega_Extensions_JapaneseInput/event-utils.ts b/src/components/Pega_Extensions_JapaneseInput/event-utils.ts deleted file mode 100644 index 5360375..0000000 --- a/src/components/Pega_Extensions_JapaneseInput/event-utils.ts +++ /dev/null @@ -1,18 +0,0 @@ -const handleEvent = (actions: any, eventType: string, propName: string, value: string) => { - switch (eventType) { - case 'change': - actions.updateFieldValue(propName, value); - break; - case 'blur': - actions.triggerFieldChange(propName, value); - break; - case 'changeNblur': - actions.updateFieldValue(propName, value); - actions.triggerFieldChange(propName, value); - break; - default: - break; - } -}; - -export default handleEvent; \ No newline at end of file diff --git a/src/components/Pega_Extensions_JapaneseInput/index.tsx b/src/components/Pega_Extensions_JapaneseInput/index.tsx index ee6620a..160b789 100644 --- a/src/components/Pega_Extensions_JapaneseInput/index.tsx +++ b/src/components/Pega_Extensions_JapaneseInput/index.tsx @@ -1,34 +1,29 @@ import { useEffect, useState, useRef } from 'react'; -import { - Input, - FieldValueList, - Text, - EmailDisplay, - PhoneDisplay, - URLDisplay, - withConfiguration -} from '@pega/cosmos-react-core'; -import type { PConnFieldProps } from './PConnProps'; -import './create-nonce'; - -// include in bundle -import handleEvent from './event-utils'; -import StatusWorkRenderer from './StatusWork'; -import { suggestionsHandler } from './suggestions-handler'; - -import StyledPegaExtensionsJapaneseInputWrapper from './styles'; +import { Input, FieldValueList, Text, withConfiguration } from '@pega/cosmos-react-core'; +import '../create-nonce'; // interface for props -interface PegaExtensionsJapaneseInputProps extends PConnFieldProps { +interface PegaExtensionsJapaneseInputProps { // If any, enter additional props that only exist on TextInput here - displayAsStatus?: boolean; - isTableFormatter?: boolean; hasSuggestions?: boolean; variant?: any; - formatter: string; hiraganaToKatakana: boolean; fullToHalf: boolean; lowerToUpper: boolean; + label: string; + required: boolean; + disabled: boolean; + value: any; + readOnly: boolean; + getPConnect: any; + placeholder?: string; + validatemessage: string; + hideLabel: boolean; + helperText: string; + testId: string; + fieldMetadata?: any; + additionalProps?: any; + displayMode?: string; } // interface for StateProps object @@ -37,54 +32,11 @@ interface StateProps { hasSuggestions: boolean; } -export const formatExists = (formatterVal: string) => { - const formatterValues = [ - 'TextInput', - 'WorkStatus', - 'RichText', - 'Email', - 'Phone', - 'URL', - 'Operator' - ]; - let isformatter = false; - if (formatterValues.includes(formatterVal)) { - isformatter = true; - } - return isformatter; -}; - -export const textFormatter = (formatter: string, value: string) => { - let displayComponent: any = null; - switch (formatter) { - case 'TextInput': { - displayComponent = value; - break; - } - case 'Email': { - displayComponent = ; - break; - } - case 'Phone': { - displayComponent = ; - break; - } - case 'URL': { - displayComponent = ( - - ); - break; - } - // no default - } - return displayComponent; -}; - // Duplicated runtime code from Constellation Design System Component // props passed in combination of props from property panel (config.json) and run time props from Constellation // any default values in config.pros should be set in defaultProps at bottom of this file -function PegaExtensionsJapaneseInput(props: PegaExtensionsJapaneseInputProps) { +export const PegaExtensionsJapaneseInput = (props: PegaExtensionsJapaneseInputProps) => { const { getPConnect, placeholder, @@ -96,15 +48,13 @@ function PegaExtensionsJapaneseInput(props: PegaExtensionsJapaneseInputProps) { fieldMetadata = {}, additionalProps = {}, displayMode, - displayAsStatus, + value, variant = 'inline', hasSuggestions = false, - isTableFormatter = false, hiraganaToKatakana = false, fullToHalf = false, lowerToUpper = false } = props; - const { formatter } = props; const pConn = getPConnect(); const actions = pConn.getActionsApi(); const stateProps = pConn.getStateProps() as StateProps; @@ -112,7 +62,7 @@ function PegaExtensionsJapaneseInput(props: PegaExtensionsJapaneseInputProps) { const maxLength = fieldMetadata?.maxLength; const hasValueChange = useRef(false); - let { value, readOnly = false, required = false, disabled = false } = props; + let { readOnly = false, required = false, disabled = false } = props; [readOnly, required, disabled] = [readOnly, required, disabled].map( prop => prop === true || (typeof prop === 'string' && prop === 'true') ); @@ -138,63 +88,24 @@ function PegaExtensionsJapaneseInput(props: PegaExtensionsJapaneseInputProps) { } }, [validatemessage, hasSuggestions, myStatus]); - const onResolveSuggestionHandler = (accepted: boolean) => { - suggestionsHandler(accepted, pConn, setStatus); - }; - // Override the value to render as status work when prop passed to display as status - if (displayAsStatus) { - value = StatusWorkRenderer({ value }); - - // Fall into this scenario for case summary, default to stacked status - if (!displayMode) { - return ( - - ); - } + const displayComp = value || ''; + if (displayMode === 'DISPLAY_ONLY') { + return {displayComp}; } - - if (displayMode === 'LABELS_LEFT' || displayMode === 'DISPLAY_ONLY') { - let displayComp = value || ; - if (isTableFormatter && formatExists(formatter)) { - displayComp = textFormatter(formatter, value); - } - return displayMode === 'DISPLAY_ONLY' ? ( - - {' '} - {displayComp}{' '} - - ) : ( - - - + if (displayMode === 'LABELS_LEFT') { + return ( + ); } - if (displayMode === 'STACKED_LARGE_VAL') { - const isValDefined = typeof value !== 'undefined' && value !== ''; - const val = isValDefined ? ( + return ( - {value} + {displayComp} - ) : ( - '' - ); - return ( - - - ); } @@ -204,14 +115,14 @@ function PegaExtensionsJapaneseInput(props: PegaExtensionsJapaneseInputProps) { } setInputValue(event.target.value); if (value !== event.target.value) { - handleEvent(actions, 'change', propName, event.target.value); + actions.updateFieldValue(propName, event.target.value); hasValueChange.current = true; } }; const handleBlur = (event: any) => { if ((!value || hasValueChange.current) && !readOnly) { - handleEvent(actions, 'blur', propName, event.target.value); + actions.triggerFieldChange(propName, event.target.value); if (hasSuggestions) { pConn.ignoreSuggestion(''); } @@ -274,27 +185,24 @@ function PegaExtensionsJapaneseInput(props: PegaExtensionsJapaneseInputProps) { }; return ( - - - + ); -} +}; export default withConfiguration(PegaExtensionsJapaneseInput); diff --git a/src/components/Pega_Extensions_JapaneseInput/mock.ts b/src/components/Pega_Extensions_JapaneseInput/mock.ts deleted file mode 100644 index be397da..0000000 --- a/src/components/Pega_Extensions_JapaneseInput/mock.ts +++ /dev/null @@ -1,32 +0,0 @@ -export const configProps = { - value: '', - label: 'TextInput Sample', - placeholder: 'TextInput Placeholder', - helperText: 'TextInput Helper Text', - testId: 'TextInput-12345678', - hasSuggestions: false, - displayMode: '', - variant: '', - hideLabel: false, - readOnly: false, - required: false, - disabled: false, - status: '', - validatemessage: '', - hiraganaToKatakana: false, - fullToHalf: false, - lowerToUpper: false -}; - -export const stateProps = { - value: '.TextInputSample', - hasSuggestions: false -}; - -export const fieldMetadata = { - classID: 'DIXL-MediaCo-Work-NewService', - type: 'Text', - maxLength: 256, - displayAs: 'pxTextInput', - label: 'TextInput Sample' -}; diff --git a/src/components/Pega_Extensions_JapaneseInput/styles.ts b/src/components/Pega_Extensions_JapaneseInput/styles.ts deleted file mode 100644 index 737e0c9..0000000 --- a/src/components/Pega_Extensions_JapaneseInput/styles.ts +++ /dev/null @@ -1,8 +0,0 @@ -// individual style, comment out above, and uncomment here and add styles -import styled, { css } from 'styled-components'; - -export default styled.div(() => { - return css` - margin: 0px 0; - `; -}); diff --git a/src/components/Pega_Extensions_JapaneseInput/suggestions-handler.ts b/src/components/Pega_Extensions_JapaneseInput/suggestions-handler.ts deleted file mode 100644 index 68f6e9f..0000000 --- a/src/components/Pega_Extensions_JapaneseInput/suggestions-handler.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const suggestionsHandler = (accepted: boolean, pConn: any, setStatus: any) => { - if (accepted) { - pConn.acceptSuggestion(); - setStatus('success'); - } else { - pConn.ignoreSuggestion(); - setStatus(undefined); - } -}; From f669173b93ae64866f28431881dd64863256a84d Mon Sep 17 00:00:00 2001 From: mitsuyapega Date: Wed, 11 Dec 2024 17:43:34 +0900 Subject: [PATCH 3/5] fixup! feat: japanese input --- .../demo.stories.tsx | 2 +- .../Pega_Extensions_JapaneseInput/index.tsx | 111 ++++++++++-------- 2 files changed, 61 insertions(+), 52 deletions(-) diff --git a/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx b/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx index b9975e4..fa55132 100644 --- a/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx +++ b/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx @@ -74,7 +74,7 @@ export const Default: Story = { readOnly: false, disabled: false, required: false, - hideLabel: false, + labelHidden: false, displayMode: '', variant: '', validatemessage: '', diff --git a/src/components/Pega_Extensions_JapaneseInput/index.tsx b/src/components/Pega_Extensions_JapaneseInput/index.tsx index 160b789..779a5f3 100644 --- a/src/components/Pega_Extensions_JapaneseInput/index.tsx +++ b/src/components/Pega_Extensions_JapaneseInput/index.tsx @@ -1,9 +1,21 @@ +import { type FC } from 'react'; import { useEffect, useState, useRef } from 'react'; -import { Input, FieldValueList, Text, withConfiguration } from '@pega/cosmos-react-core'; +import { + Input, + FieldValueList, + Text, + withConfiguration, + type InputProps, + type FormControlProps, + type TestIdProp, + useTestIds, + createTestIds, + withTestIds +} from '@pega/cosmos-react-core'; import '../create-nonce'; // interface for props -interface PegaExtensionsJapaneseInputProps { +export interface PegaExtensionsJapaneseInputProps extends InputProps, TestIdProp { // If any, enter additional props that only exist on TextInput here hasSuggestions?: boolean; variant?: any; @@ -11,14 +23,8 @@ interface PegaExtensionsJapaneseInputProps { fullToHalf: boolean; lowerToUpper: boolean; label: string; - required: boolean; - disabled: boolean; - value: any; - readOnly: boolean; getPConnect: any; - placeholder?: string; validatemessage: string; - hideLabel: boolean; helperText: string; testId: string; fieldMetadata?: any; @@ -27,53 +33,58 @@ interface PegaExtensionsJapaneseInputProps { } // interface for StateProps object -interface StateProps { +export interface StateProps { value: string; hasSuggestions: boolean; } +// Test-id configuration +export const getJapaneseInputTestIds = createTestIds('japanese-input', [] as const); + // Duplicated runtime code from Constellation Design System Component // props passed in combination of props from property panel (config.json) and run time props from Constellation // any default values in config.pros should be set in defaultProps at bottom of this file -export const PegaExtensionsJapaneseInput = (props: PegaExtensionsJapaneseInputProps) => { - const { - getPConnect, - placeholder, - validatemessage, - label, - hideLabel = false, - helperText, - testId, - fieldMetadata = {}, - additionalProps = {}, - displayMode, - value, - variant = 'inline', - hasSuggestions = false, - hiraganaToKatakana = false, - fullToHalf = false, - lowerToUpper = false - } = props; +export const PegaExtensionsJapaneseInput: FC = ({ + getPConnect, + placeholder, + validatemessage, + helperText, + testId, + fieldMetadata = {}, + displayMode, + value, + label, + labelHidden, + variant = 'inline', + hasSuggestions = false, + hiraganaToKatakana = false, + fullToHalf = false, + lowerToUpper = false, + ...restProps +}: PegaExtensionsJapaneseInputProps) => { const pConn = getPConnect(); const actions = pConn.getActionsApi(); const stateProps = pConn.getStateProps() as StateProps; const propName: string = stateProps.value; const maxLength = fieldMetadata?.maxLength; const hasValueChange = useRef(false); + const testIds = useTestIds(testId, getJapaneseInputTestIds); - let { readOnly = false, required = false, disabled = false } = props; - [readOnly, required, disabled] = [readOnly, required, disabled].map( - prop => prop === true || (typeof prop === 'string' && prop === 'true') - ); + // let { readOnly = false, required = false, disabled = false } = props; + // [readOnly, required, disabled] = [readOnly, required, disabled].map( + // prop => prop === true || (typeof prop === 'string' && prop === 'true') + // ); const [inputValue, setInputValue] = useState(value); - const [status, setStatus] = useState(hasSuggestions ? 'pending' : undefined); + const [status, setStatus] = useState( + hasSuggestions ? 'pending' : undefined + ); // cast status - let myStatus: 'success' | 'warning' | 'error' | 'pending'; - // eslint-disable-next-line prefer-const - myStatus = status as 'success' | 'warning' | 'error' | 'pending'; + // let myStatus: 'success' | 'warning' | 'error' | 'pending'; + + // myStatus = status as 'success' | 'warning' | 'error' | 'pending'; useEffect(() => setInputValue(value), [value]); @@ -83,10 +94,10 @@ export const PegaExtensionsJapaneseInput = (props: PegaExtensionsJapaneseInputPr } if (hasSuggestions) { setStatus('pending'); - } else if (!hasSuggestions && myStatus !== 'success') { + } else if (!hasSuggestions && status !== 'success') { setStatus(validatemessage !== '' ? 'error' : undefined); } - }, [validatemessage, hasSuggestions, myStatus]); + }, [validatemessage, hasSuggestions, status]); const displayComp = value || ''; if (displayMode === 'DISPLAY_ONLY') { @@ -95,9 +106,9 @@ export const PegaExtensionsJapaneseInput = (props: PegaExtensionsJapaneseInputPr if (displayMode === 'LABELS_LEFT') { return ( ); } @@ -121,7 +132,7 @@ export const PegaExtensionsJapaneseInput = (props: PegaExtensionsJapaneseInputPr }; const handleBlur = (event: any) => { - if ((!value || hasValueChange.current) && !readOnly) { + if (!value || hasValueChange.current) { actions.triggerFieldChange(propName, event.target.value); if (hasSuggestions) { pConn.ignoreSuggestion(''); @@ -134,6 +145,7 @@ export const PegaExtensionsJapaneseInput = (props: PegaExtensionsJapaneseInputPr return String.fromCharCode(match.charCodeAt(0) + 0x60); }); }; + const fullWidthToHalfWidth = (str: string): string => { str = str.replace(/[A-Za-z0-9]/g, match => { return String.fromCharCode(match.charCodeAt(0) - 0xfee0); @@ -186,23 +198,20 @@ export const PegaExtensionsJapaneseInput = (props: PegaExtensionsJapaneseInputPr return ( ); }; -export default withConfiguration(PegaExtensionsJapaneseInput); +export default withTestIds(withConfiguration(PegaExtensionsJapaneseInput), getJapaneseInputTestIds); From 7a7babbf8b59ab32b5af28d61d19618d2ef4cec8 Mon Sep 17 00:00:00 2001 From: mitsuyapega Date: Thu, 12 Dec 2024 19:15:50 +0900 Subject: [PATCH 4/5] fixup! feat: japanese input --- .../Pega_Extensions_JapaneseInput/config.json | 4 +- .../demo.stories.tsx | 8 +-- .../Pega_Extensions_JapaneseInput/index.tsx | 58 ++++++++----------- 3 files changed, 30 insertions(+), 40 deletions(-) diff --git a/src/components/Pega_Extensions_JapaneseInput/config.json b/src/components/Pega_Extensions_JapaneseInput/config.json index fec23c2..21c3ad0 100644 --- a/src/components/Pega_Extensions_JapaneseInput/config.json +++ b/src/components/Pega_Extensions_JapaneseInput/config.json @@ -123,7 +123,7 @@ "defaultConfig": { "label": "@L $this.label", "detailFVLItem": true, - "isFormWidth" : false, - "isContainerWidth" : false + "isFormWidth": false, + "isContainerWidth": false } } diff --git a/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx b/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx index fa55132..e05e791 100644 --- a/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx +++ b/src/components/Pega_Extensions_JapaneseInput/demo.stories.tsx @@ -68,16 +68,16 @@ export const Default: Story = { }, args: { label: 'TextInput Sample', - helperText: 'TextInput Helper Text', + info: 'TextInput Helper Text', placeholder: 'TextInput Placeholder', testId: 'TextInput-12345678', readOnly: false, disabled: false, required: false, labelHidden: false, - displayMode: '', - variant: '', - validatemessage: '', + displayMode: undefined, + variant: undefined, + errorMessage: '', hiraganaToKatakana: false, fullToHalf: false, lowerToUpper: false diff --git a/src/components/Pega_Extensions_JapaneseInput/index.tsx b/src/components/Pega_Extensions_JapaneseInput/index.tsx index 779a5f3..f57561e 100644 --- a/src/components/Pega_Extensions_JapaneseInput/index.tsx +++ b/src/components/Pega_Extensions_JapaneseInput/index.tsx @@ -13,23 +13,26 @@ import { withTestIds } from '@pega/cosmos-react-core'; import '../create-nonce'; +import type { FieldValueVariant } from '@pega/cosmos-react-core/lib/components/FieldValueList/FieldValueList'; + +enum DisplayMode { + DisplayOnly = 'DISPLAY_ONLY', + LabelsLeft = 'LABELS_LEFT', + StackedLargeVal = 'STACKED_LARGE_VAL' +} // interface for props export interface PegaExtensionsJapaneseInputProps extends InputProps, TestIdProp { // If any, enter additional props that only exist on TextInput here hasSuggestions?: boolean; - variant?: any; + variant?: FieldValueVariant; hiraganaToKatakana: boolean; fullToHalf: boolean; lowerToUpper: boolean; label: string; getPConnect: any; - validatemessage: string; - helperText: string; - testId: string; - fieldMetadata?: any; - additionalProps?: any; - displayMode?: string; + errorMessage: string; + displayMode?: DisplayMode; } // interface for StateProps object @@ -46,17 +49,15 @@ export const getJapaneseInputTestIds = createTestIds('japanese-input', [] as con // props passed in combination of props from property panel (config.json) and run time props from Constellation // any default values in config.pros should be set in defaultProps at bottom of this file export const PegaExtensionsJapaneseInput: FC = ({ - getPConnect, - placeholder, - validatemessage, - helperText, testId, - fieldMetadata = {}, + getPConnect, + errorMessage, displayMode, value, label, labelHidden, - variant = 'inline', + info, + variant, hasSuggestions = false, hiraganaToKatakana = false, fullToHalf = false, @@ -67,43 +68,33 @@ export const PegaExtensionsJapaneseInput: FC = const actions = pConn.getActionsApi(); const stateProps = pConn.getStateProps() as StateProps; const propName: string = stateProps.value; - const maxLength = fieldMetadata?.maxLength; const hasValueChange = useRef(false); const testIds = useTestIds(testId, getJapaneseInputTestIds); - // let { readOnly = false, required = false, disabled = false } = props; - // [readOnly, required, disabled] = [readOnly, required, disabled].map( - // prop => prop === true || (typeof prop === 'string' && prop === 'true') - // ); - const [inputValue, setInputValue] = useState(value); const [status, setStatus] = useState( hasSuggestions ? 'pending' : undefined ); - // cast status - // let myStatus: 'success' | 'warning' | 'error' | 'pending'; - - // myStatus = status as 'success' | 'warning' | 'error' | 'pending'; - useEffect(() => setInputValue(value), [value]); useEffect(() => { - if (validatemessage !== '') { + if (errorMessage !== '') { setStatus('error'); } if (hasSuggestions) { setStatus('pending'); } else if (!hasSuggestions && status !== 'success') { - setStatus(validatemessage !== '' ? 'error' : undefined); + setStatus(errorMessage !== '' ? 'error' : undefined); } - }, [validatemessage, hasSuggestions, status]); + }, [errorMessage, hasSuggestions, status]); - const displayComp = value || ''; - if (displayMode === 'DISPLAY_ONLY') { + const displayComp = inputValue || ''; + if (displayMode === DisplayMode.DisplayOnly) { return {displayComp}; } - if (displayMode === 'LABELS_LEFT') { + + if (displayMode === DisplayMode.LabelsLeft) { return ( = /> ); } - if (displayMode === 'STACKED_LARGE_VAL') { + + if (displayMode === DisplayMode.StackedLargeVal) { return ( {displayComp} @@ -203,11 +195,9 @@ export const PegaExtensionsJapaneseInput: FC = type='text' label={label} labelHidden={labelHidden} - info={validatemessage || helperText} + info={errorMessage || info} value={inputValue} status={status} - placeholder={placeholder} - maxLength={maxLength} onChange={handleChange} onBlur={handleBlur} /> From ff8645c8e212018dd9ab3f5e64741ad5e95b0fe0 Mon Sep 17 00:00:00 2001 From: mitsuyapega Date: Fri, 13 Dec 2024 16:12:29 +0900 Subject: [PATCH 5/5] fixup! feat: japanese input --- .../Pega_Extensions_JapaneseInput/Docs.mdx | 9 +++- .../Pega_Extensions_JapaneseInput/index.tsx | 41 +------------------ .../Pega_Extensions_JapaneseInput/utils.ts | 40 ++++++++++++++++++ 3 files changed, 49 insertions(+), 41 deletions(-) create mode 100644 src/components/Pega_Extensions_JapaneseInput/utils.ts diff --git a/src/components/Pega_Extensions_JapaneseInput/Docs.mdx b/src/components/Pega_Extensions_JapaneseInput/Docs.mdx index bdf9072..a21f808 100644 --- a/src/components/Pega_Extensions_JapaneseInput/Docs.mdx +++ b/src/components/Pega_Extensions_JapaneseInput/Docs.mdx @@ -5,7 +5,14 @@ import * as DemoStories from './demo.stories'; # Overview -This component that converts Japanese input, such as kana characters and full-width characters. +Japanese input component has the following features. + +- Lowercase to Uppercase + Converts Lowercase to Uppercase, for when the client inputs lowercase and the internal character handles it as uppercase. +- Hiragana to Katakana + Japanese has two types of characters, Hiragana and Katakana, which correspond one-to-one. Hiragana input is converted to Katakana. +- Full-width to Half-width + In Japanese, characters are generally handled as multi-byte full-width characters rather than single-byte half-width characters, and for this reason Japanese keyboards have a function to input characters in full-width. Some full-width characters have the same meaning in half-width, and these are converted from full-width to half-width. diff --git a/src/components/Pega_Extensions_JapaneseInput/index.tsx b/src/components/Pega_Extensions_JapaneseInput/index.tsx index f57561e..01933d9 100644 --- a/src/components/Pega_Extensions_JapaneseInput/index.tsx +++ b/src/components/Pega_Extensions_JapaneseInput/index.tsx @@ -14,6 +14,7 @@ import { } from '@pega/cosmos-react-core'; import '../create-nonce'; import type { FieldValueVariant } from '@pega/cosmos-react-core/lib/components/FieldValueList/FieldValueList'; +import { convertHiraganaToKatakana, fullWidthToHalfWidth } from './utils'; enum DisplayMode { DisplayOnly = 'DISPLAY_ONLY', @@ -132,46 +133,6 @@ export const PegaExtensionsJapaneseInput: FC = hasValueChange.current = false; } - const convertHiraganaToKatakana = (str: string) => { - return str.replace(/[\u3041-\u3096]/g, match => { - return String.fromCharCode(match.charCodeAt(0) + 0x60); - }); - }; - - const fullWidthToHalfWidth = (str: string): string => { - str = str.replace(/[A-Za-z0-9]/g, match => { - return String.fromCharCode(match.charCodeAt(0) - 0xfee0); - }); - // prettier-ignore - const kanaMap: Record = { - ガ: 'ガ', ギ: 'ギ', グ: 'グ', ゲ: 'ゲ', ゴ: 'ゴ', - ザ: 'ザ', ジ: 'ジ', ズ: 'ズ', ゼ: 'ゼ', ゾ: 'ゾ', - ダ: 'ダ', ヂ: 'ヂ', ヅ: 'ヅ', デ: 'デ', ド: 'ド', - バ: 'バ', ビ: 'ビ', ブ: 'ブ', ベ: 'ベ', ボ: 'ボ', - パ: 'パ', ピ: 'ピ', プ: 'プ', ペ: 'ペ', ポ: 'ポ', - ヴ: 'ヴ', ヷ: 'ヷ', ヺ: 'ヺ', - ア: 'ア', イ: 'イ', ウ: 'ウ', エ: 'エ', オ: 'オ', - カ: 'カ', キ: 'キ', ク: 'ク', ケ: 'ケ', コ: 'コ', - サ: 'サ', シ: 'シ', ス: 'ス', セ: 'セ', ソ: 'ソ', - タ: 'タ', チ: 'チ', ツ: 'ツ', テ: 'テ', ト: 'ト', - ナ: 'ナ', ニ: 'ニ', ヌ: 'ヌ', ネ: 'ネ', ノ: 'ノ', - ハ: 'ハ', ヒ: 'ヒ', フ: 'フ', ヘ: 'ヘ', ホ: 'ホ', - マ: 'マ', ミ: 'ミ', ム: 'ム', メ: 'メ', モ: 'モ', - ヤ: 'ヤ', ユ: 'ユ', ヨ: 'ヨ', - ラ: 'ラ', リ: 'リ', ル: 'ル', レ: 'レ', ロ: 'ロ', - ワ: 'ワ', ヲ: 'ヲ', ン: 'ン', - ァ: 'ァ', ィ: 'ィ', ゥ: 'ゥ', ェ: 'ェ', ォ: 'ォ', - ッ: 'ッ', ャ: 'ャ', ュ: 'ュ', ョ: 'ョ', - '。': '。', '、': '、', ー: 'ー', '「': '「', '」': '」', '・': '・' - }; - const reg = new RegExp(`(${Object.keys(kanaMap).join('|')})`, 'g'); - return str - .replace(reg, match => { - return kanaMap[match]; - }) - .replace(/゛/g, '゙') - .replace(/゜/g, '゚'); - }; let newValue = event.target.value; if (hiraganaToKatakana) { newValue = convertHiraganaToKatakana(newValue); diff --git a/src/components/Pega_Extensions_JapaneseInput/utils.ts b/src/components/Pega_Extensions_JapaneseInput/utils.ts new file mode 100644 index 0000000..6985d1b --- /dev/null +++ b/src/components/Pega_Extensions_JapaneseInput/utils.ts @@ -0,0 +1,40 @@ +export const convertHiraganaToKatakana = (str: string) => { + return str.replace(/[\u3041-\u3096]/g, match => { + return String.fromCharCode(match.charCodeAt(0) + 0x60); + }); +}; + +export const fullWidthToHalfWidth = (str: string): string => { + str = str.replace(/[A-Za-z0-9]/g, match => { + return String.fromCharCode(match.charCodeAt(0) - 0xfee0); + }); + // prettier-ignore + const kanaMap: Record = { + ガ: 'ガ', ギ: 'ギ', グ: 'グ', ゲ: 'ゲ', ゴ: 'ゴ', + ザ: 'ザ', ジ: 'ジ', ズ: 'ズ', ゼ: 'ゼ', ゾ: 'ゾ', + ダ: 'ダ', ヂ: 'ヂ', ヅ: 'ヅ', デ: 'デ', ド: 'ド', + バ: 'バ', ビ: 'ビ', ブ: 'ブ', ベ: 'ベ', ボ: 'ボ', + パ: 'パ', ピ: 'ピ', プ: 'プ', ペ: 'ペ', ポ: 'ポ', + ヴ: 'ヴ', ヷ: 'ヷ', ヺ: 'ヺ', + ア: 'ア', イ: 'イ', ウ: 'ウ', エ: 'エ', オ: 'オ', + カ: 'カ', キ: 'キ', ク: 'ク', ケ: 'ケ', コ: 'コ', + サ: 'サ', シ: 'シ', ス: 'ス', セ: 'セ', ソ: 'ソ', + タ: 'タ', チ: 'チ', ツ: 'ツ', テ: 'テ', ト: 'ト', + ナ: 'ナ', ニ: 'ニ', ヌ: 'ヌ', ネ: 'ネ', ノ: 'ノ', + ハ: 'ハ', ヒ: 'ヒ', フ: 'フ', ヘ: 'ヘ', ホ: 'ホ', + マ: 'マ', ミ: 'ミ', ム: 'ム', メ: 'メ', モ: 'モ', + ヤ: 'ヤ', ユ: 'ユ', ヨ: 'ヨ', + ラ: 'ラ', リ: 'リ', ル: 'ル', レ: 'レ', ロ: 'ロ', + ワ: 'ワ', ヲ: 'ヲ', ン: 'ン', + ァ: 'ァ', ィ: 'ィ', ゥ: 'ゥ', ェ: 'ェ', ォ: 'ォ', + ッ: 'ッ', ャ: 'ャ', ュ: 'ュ', ョ: 'ョ', + '。': '。', '、': '、', ー: 'ー', '「': '「', '」': '」', '・': '・' + }; + const reg = new RegExp(`(${Object.keys(kanaMap).join('|')})`, 'g'); + return str + .replace(reg, match => { + return kanaMap[match]; + }) + .replace(/゛/g, '゙') + .replace(/゜/g, '゚'); +};