-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat week with time selector component #1439
Conversation
🦋 Changeset detectedLatest commit: 7184d5b The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
✅ Deploy Preview for ingred-ui ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
372d6dc
to
e246e81
Compare
プロダクト側では全選択・全解除みたいな機能作ったけど、そこまではやらない(onChange あるしプロダクト側で頑張って!) |
src/components/WeekTime/WeekTime.tsx
Outdated
import { convertTargetSettingToHex, getTargetSetting } from "./utils"; | ||
|
||
export type WeekTimeProps = { | ||
weekTime: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
引数、42桁の16進数が入ってくることを型レベルで制御したいけど、重すぎて動かなくなるので一旦 string で進める
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ちゃんと動くかわからないけど、こんな感じ...??
type HexDigit =
| "0"
| "1"
| "2"
| "3"
| "4"
| "5"
| "6"
| "7"
| "8"
| "9"
| "a"
| "b"
| "c"
| "d"
| "e"
| "f";
type Join<T extends string[], S extends string> = T extends []
? ""
: T extends [infer F, ...infer R]
? F extends string
? `${F & string}${Join<Extract<R, string[]>, S>}`
: never
: never;
type Repeat<
S extends string,
N extends number,
T extends string[] = [],
> = T["length"] extends N ? Join<T, S> : Repeat<S, N, [S, ...T]>;
type WeekTime = Repeat<HexDigit, 42>;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ChatGPTに聞いたら同じような感じに
type HexDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f';
type Join<T extends string[], S extends string = ''> = T extends [] ? S : T extends [infer F, ...infer R] ? Join<R, `${S}${F & string}`> : never;
type Repeat<T extends string, N extends number, R extends string[] = []> =
R['length'] extends N ? Join<R> : Repeat<T, N, [T, ...R]>;
type Hex42 = Repeat<HexDigit, 42>;
もしくはシンプルにテンプレートリテラルを使うとかですかね?
type HexDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f';
type Hex42 = `${HexDigit}${HexDigit}${Hex...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
羅列はしたくないです(´・ω・`)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changeset fix test and update snapshot fix: variables name test rename ...
c917a8a
to
daa9ccd
Compare
1点、国際化対応どうしようかというのが気になりました!(遅くにすみません) |
確かに!!! |
|
||
export const Example: StoryObj<WeekTimeSelectorProps> = { | ||
args: { | ||
weekTime: "ffffffffffffffffffffffffffffffffffffffffff", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
あーこれね(Slackで議論していたのを見逃していた:pray:)
fluctのDBの構造に仕様が引っ張られすぎてる感がどうしても否めないな...:thinking:
プロダクトによってはhexを扱わないけど時間帯を指定したい時に、hexをどうにかせざるを得なくなる。
- あきらめてhexを扱ってもらう
- weektimeは
string[][]
で扱う、hexへの変換ロジックは各プロダクトで書く - weektimeは
string[][]
で扱う、hexへの変換ロジックはingred-uiに書いておく
3が妥協案で、2が自然だなという認識だけど、どう思う..?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
それは結構思ってて、本当に hex 使わないといけないんだっけみたいなのは気になってました。(まさに Slack でも書いた通り、ヘルパーを含めるかどうか、hex を露出するかどうか)
3が妥協案で、2が自然だなという認識だけど、どう思う..?
ここに関しては同意です!
ただ、今回そのまま hex を使って実装したのは
- 現状このコンポーネントを使いたいプロダクトは同じ DB を参照しに行くプロダクト
- hex への変換も同じロジックを使う
- ユースケースが限定的
あたりを考えると一旦ここは hex を扱うのでもいいのかなと思います。拡張は後からいくらでもできると思うので。(特に3に倒すのはかなり容易なはず)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
memo
- hex については一旦このまま扱う形にする
- 現状のユースケースが hex を扱うものしかないので
- 今後、社内の別の場所で時間帯ターゲティングをする機会があり、なおかつ hex 以外の値を使用したいケースがあったときにその処理を切り出すことを検討する
デザインの変更入った、仕様も固まってなかったし(hex 使うのか否かのあたり)ちょっとレビューリクエスト投げるには早かったかもしれない。ごめんなさい。(´・ω・`) |
引数は hex に倒すことにした #1439 (comment) |
errorText, | ||
onChange, | ||
}) => { | ||
const [weekTimeList, setWeekTimeList] = useState(getTargetSetting(weekTime)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
weekTime
propsを変換したものがweekTimeList
なので、状態として扱わないほうが実装として読みやすくなるかなーと思うのだけどどうだろう...??:thinking:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
状態として扱わないほうが
変数、または useMemo とかを使うという意味ですか...??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なるほど?
weekTimeList
は状態で扱うべきなのと、weekTime
props を変換したものに関しては初期値なのでいいかなと思ってるのですがどうですかね???
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
weekTimeList は状態で扱うべきなの
そうではないなと思った理由が
weekTimepropsを変換したものがweekTimeListなので、状態として扱わないほうが実装として読みやすくなるかなーと思うのだけどどうだろう...??
なのだけど、状態で扱うべき理由はどこかな?:thinking:
説明がむずそうなら口頭でも
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 初期値は hex から weekTimeList を求める
- 更新は選択した index から weekTimeList を更新する
ので状態で表現するのが適切かなと思ってます。
setState してるのはここらへんで、これは getNewWeekTimeList
を使って求めているので初期値とは別扱いかなと。
https://github.com/voyagegroup/ingred-ui/pull/1439/files#diff-184eaa807c8a4513b51aa920881b647e71a985ee69e00c099ab96007b71dab88R43-R52
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
初期値は hex から weekTimeList を求める
更新は選択した index から weekTimeList を更新する
この辺は今の実装について教えてくれているけど、それが状態として扱うべき理由になるのがちょっとわからなかった...
古い引用だけど
https://ja.legacy.reactjs.org/docs/react-component.html
props を state にコピーしないでください。これはよくある間違いです。
この辺について言いたかった感じかな。
https://ja.legacy.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-controlled-component
で、(Hexがややこしくしているけど)対応としては↑で良いんじゃないかな、という話。
Reactではよくある話だと思う。
別の手法も挙げてるけど、今回のケースだと1番目に挙げられた案で良いと思うぐらい、ユースケースはシンプルだと思う。
If you’re using derived state to memoize some computation based only on the current props, you don’t need derived state. See What about memoization? below.
なるほど、今回の例は「派生ステート」と呼ばれるのね。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ちょっと見てみます
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
難しかった、完全に理解した
const [hoverWeekTimeList, setHoverWeekTimeList] = | ||
useState(defaultHoverWeekTime); | ||
|
||
const [startIndex, setStartIndex] = useState<{ | ||
weekIndex: number; | ||
timeIndex: number; | ||
}>({ weekIndex: 0, timeIndex: 0 }); | ||
const [endIndex, setEndIndex] = useState<{ | ||
weekIndex: number; | ||
timeIndex: number; | ||
}>({ weekIndex: 0, timeIndex: 0 }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hoverWeekTimeList
はstartIndex
とendIndex
から求めてる感じかな..?
実装ぱっと見hoverWeekTimeList
はなくても実現できそうな雰囲気を感じてるのだけどどうだろう...?:thinking:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
いや、hover は onMouseOver をしている button のインデックスをハンドラーで取得して管理しているので endIndex は見てないですね。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
今の実装はそうだけど、hoverをしている(スタイルを変えたいセル)ってstartIndexとendIndex、要するに2つの座標情報さえあれば実現できそうな気がしたなという感じ。
だけど妙案出すには時間かかりそうだからこれでいこうか:pray:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
結局それは hover の状態を計算しないといけないのでどっちにしても値は必要になる気がしました。
だけど妙案出すには時間かかりそうだからこれでいこうか🙏
🙇
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@takurinton
AIとともに書いてみたけどどうだろうか、だいぶスッキリしたなーという印象がある。
import ErrorText from "../ErrorText";
import * as Styled from "./styled";
import React, { Fragment, useEffect, useMemo, useState } from "react";
import { timeList, weekList } from "./constants";
import {
convertTargetSettingToHex,
getNewWeekTimeList,
getTargetSetting,
} from "./utils";
export type WeekTimeSelectorProps = {
weekTime: string;
errorText?: string;
onChange?: (weekTime: string) => void;
};
const WeekTimeSelector: React.FC<WeekTimeSelectorProps> = ({
weekTime,
errorText,
onChange,
}) => {
const weekTimeList = useMemo(() => getTargetSetting(weekTime), [weekTime]);
const [startIndex, setStartIndex] = useState<{
weekIndex: number;
timeIndex: number;
}>({ weekIndex: 0, timeIndex: 0 });
const [endIndex, setEndIndex] = useState<{
weekIndex: number;
timeIndex: number;
}>({ weekIndex: 0, timeIndex: 0 });
const [selectState, setSelectState] = useState<"none" | "choosing">("none");
const [toValue, setToValue] = useState<string>("1");
const handleChangeWeekTime = (
weekIndex: number,
timeIndex: number,
newValue: string,
) => {
if (selectState === "choosing") {
const newWeekTimeList = getNewWeekTimeList(
startIndex.weekIndex,
startIndex.timeIndex,
weekIndex,
timeIndex,
newValue,
[...weekTimeList],
);
setSelectState("none");
onChange?.(convertTargetSettingToHex(newWeekTimeList));
} else if (selectState === "none") {
setStartIndex({ weekIndex, timeIndex });
setEndIndex({ weekIndex, timeIndex });
setSelectState("choosing");
}
};
// 上手く切り出してテスタブルにしたい
const isWithinHoverRange = (weekIndex: number, timeIndex: number) => {
if (selectState === "choosing") {
const minWeekIndex = Math.min(startIndex.weekIndex, endIndex.weekIndex);
const maxWeekIndex = Math.max(startIndex.weekIndex, endIndex.weekIndex);
const minTimeIndex = Math.min(startIndex.timeIndex, endIndex.timeIndex);
const maxTimeIndex = Math.max(startIndex.timeIndex, endIndex.timeIndex);
return weekIndex >= minWeekIndex && weekIndex <= maxWeekIndex && timeIndex >= minTimeIndex && timeIndex <= maxTimeIndex;
}
return false;
};
const handleMouseOver = (weekIndex: number, timeIndex: number) => () => {
if (selectState === "none") {
return;
}
setEndIndex({ weekIndex, timeIndex });
};
const handleMouseDown =
(weekIndex: number, timeIndex: number, time: string) => () => {
setStartIndex({ weekIndex, timeIndex });
setEndIndex({ weekIndex, timeIndex });
const newValue = time === "1" ? "0" : "1";
setToValue(newValue);
handleChangeWeekTime(weekIndex, timeIndex, newValue);
};
// MEMO: button 以外の場所で mouseup したときの挙動を制御する
const onMouseUp = () => {
// mousedown の位置が button のときにのみこの条件に到達する想定
if (selectState === "choosing") {
handleChangeWeekTime(endIndex.weekIndex, endIndex.timeIndex, toValue);
}
};
useEffect(() => {
addEventListener("mouseup", onMouseUp);
return () => {
removeEventListener("mouseup", onMouseUp);
};
});
return (
<Styled.Container>
<Styled.WeekTimeContainer>
<Styled.EmptyContainer />
{timeList.map((time) => (
<Styled.TimeContainer key={time}>{time}</Styled.TimeContainer>
))}
{weekTimeList.map((time, weekIndex) => (
// eslint-disable-next-line react/no-array-index-key
<Fragment key={weekIndex}>
<Styled.WeekContainer>{weekList[weekIndex]}</Styled.WeekContainer>
{time.map((t, timeIndex) => (
<Styled.WeekTimeItem
// eslint-disable-next-line react/no-array-index-key
key={`${weekIndex}-${timeIndex}`}
active={t === "1"}
hover={isWithinHoverRange(weekIndex, timeIndex)}
onMouseOver={handleMouseOver(weekIndex, timeIndex)}
onMouseDown={handleMouseDown(weekIndex, timeIndex, t)}
/>
))}
</Fragment>
))}
</Styled.WeekTimeContainer>
{errorText && <ErrorText>{errorText}</ErrorText>}
</Styled.Container>
);
};
export default WeekTimeSelector;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
すごい
hover の状態だけ少し修正したら使えそうですね
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fm
この方向でやってみてもらえると:pray:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
単一日を選択したいときに、onMouseDown で背景が水色になってしまうけど、まあそれは許容にしますか...。(許容というか、mousedown で日付を選択中(選択済みではなく)の場合は色が変わるという挙動を正しいものとする)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hover の状態とはこれを指していたので、問題なさそう
#1439 (comment)
// 選択範囲内のセルをnewValueに更新する | ||
const getNewWeekTimeList = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(仕様としての)テストがあると読む人に優しいかなーと思った!
fix: style
test baseline update test
e2ed29b
to
841f313
Compare
fix: state
9c5d928
to
fd0fcb5
Compare
No description provided.