diff --git a/components/__snapshots__/article.spec.tsx.snap b/components/__snapshots__/article.spec.tsx.snap new file mode 100644 index 00000000..6b9020d5 --- /dev/null +++ b/components/__snapshots__/article.spec.tsx.snap @@ -0,0 +1,613 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`
renders content by the given props 1`] = ` +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua +

+ +
+

+ :::callout{variant="info"}\\n2021年3月に最初に書きました。同じURLで随時このページを更新していく予定です。\\n\\n何か質問などがあればTwitterか、 + + こちらのフォームから送って + + もらえれば順次書き足します。\\n:::\\n\\n::rich-heading-2[働いている会社 / 背景]{#641db2a7}\\n\\nParsable Inc. という会社で、ざっくり言うと工業系のフロントライナーのお客さんたちの手順書やチェックリストをデジタル化するSaaSを作っています。ユーザーが入力した項目の内容や時間からボトルネックを探して手順を改善したりするための機能もあって、単なる電子化というよりかは全体最適化を目指すプラットフォームという感じです。\\n\\n技術的には、GUIでポチポチしながら柔軟に手順書やチェックリストのテンプレートを作れる機能とパフォーマンス計測ができる機能を持ったダッシュボードがあるという感じで、かなり歯応えのある複雑なフロントエンドです。\\n\\n本社はアメリカのサンフランシスコのダウンタウンのど真ん中にありますが、僕は2020年3月にカナダのバンクーバーオフィスでシニアフロントエンドエンジニアとして採用されました。\\n\\n採用されてからすぐにCOVID-19の影響でオフィスがシャットダウンされ、それ以来ずっとリモートで働いています。同僚とイベントで何度か顔を合わせてますが、主にオンラインでのやりとりがメインです。\\n\\n::image-figure{src="https://media.graphcms.com/output=format + /VE7swp8LSZCbftKg1TJ9" caption="同僚とBBQに行った時に遊んだミニスポーツ。Spikeballというらしい" width="1920" height="1200"}\\n\\n::rich-heading-2[英語]{#590b5b4b}\\n\\n「USの会社で働く」ということでまず頭に浮かぶのは英語だと思います。結論から言うと、僕の英語力は徐々に伸びてはいるもののまだ充分に流暢ではないという感じです。シニアポジションの特性上、コミュニケーションの仕事の比重がめちゃくちゃ増えます。ミーティングの中で会話をリードする機会や気の利いたコメント(というより説明)を求められる場面が多くなります。\\n\\n最初の頃は「こういうことを聞かれるだろうな」というのをミーティングの中で想定しておいて、テキストエディタに英語で書き出しておいて話すというのを試していました。元々英語でのライティングが得意だったので使っていたハックという感じです。最近ではそういう下準備なしに複雑な会話もできるようになってきました。\\n\\n::rich-heading-3[英語で喋ると頭が悪くなる]{#51373d1d}\\n\\n第二言語話者の間では通説のようなものですが、英語で話すと日本語で話している時よりも思考のパフォーマンスが下がります。その結果、口から出てくる言葉も稚拙になり、話している相手からは頭が悪いように見えます。これは人間が思考に言語野を用いていて、 + + 持っている言語の単語や表現力に思考範囲が制限されるため + + だと言われています。そのため、英語を使っていると「日本語だったらもっとうまく説明できるのに」みたいなシチュエーションが発生します。\\n\\n僕は英語を勉強している中でこの話を知りましたが、実際に働いてみてその存在を確かに実感しました。この本当の意味はおそらく日本語 (ネイティブ言語) で働いているうちは一生気付くことができなかったです。それまでの自分の思考のパフォーマンスが自分にとって当たり前すぎて「いざ奪われるとこうなるのか」という稀有な体験でした。\\n\\n上でも話した通り僕の英語のスキルはまだまだ下の下なので、前もってドキュメントを作ってミーティングに臨むなどしてオーラルコミュニケーションのコストを減らしたり、そのドキュメントの中でも図をたくさん使うなどして補っています。これはこれで仕事全般として良いプラクティスだとは思いますが、いずれはこういうことをしなくてもいいようにあえて徐々にギプスを外していこうと思っています。\\n\\n::rich-heading-3[英語力の客観的な評価]{#e58c07b6}\\n\\n少し恥ずかしいですが、ざっくり周りの評価を書いておこうと思います。「実際どれくらいのレベルの人がこういうこと言ってるのか」という参考になれば...。\\n\\n* テストでのスコア\\n * 実はIELTS・TOEFLやTOEICなどの統一的なテストを受けたことがありません...。あればよかったんですが...\\n * 周りの人と比べると、おそらくIELTS Overall 6.0か6.5付近で、現時点ではどうあがいてもOverall 7.0以上は取れないと思います\\n * TODO: 何か受ける\\n * TOEICだとリスニングとリーディングが主ですが、仕事で必要なのはスピーキングなのでパフォーマンス計測にはIELTSやTOEFLが良いと思います\\n* 同僚からの評価\\n * + + 『テキストコミュニケーションは完璧、口頭でのコミュニケーションは詳細を欠くことがある』 + + \\n * + + 『君の英語はとてもじゃないがパーフェクトとは言えない、研鑽して欲しい』 + + \\n * 言われた日と次の日は正直めちゃくちゃ凹みました\\n * 5ヶ月くらい経った後で同じ人から + + 『かなり向上を感じた。今はプロフェッショナルな感じがある』 + + と評価が向上しました💪\\n\\nただ、日によってめちゃくちゃ英語が上手な日とものすごく下手な日があります。おそらく身体的なパフォーマンスと同じように、頭脳やメンタルのパフォーマンスによって変動があるんだと思います。また、休みの日に日本語のYouTubeを一日中見ていた次の日の月曜日なども英語がすごく下手になったりします。\\n\\n:::callout{variant="info"}\\nフィードバックで質問をいただいたので追記:\\n\\n> Q. 記事内に「君の英語はとてもじゃないがパーフェクトとは言えない、研鑽して欲しい」と言われたとありますが、"英語"でどのように言われたのか教えて頂くことは可能でしょうか?\\n\\nA. 1-on-1 ミーティングの中で口頭で言われたことなので正確にどんな表現で言われたかは覚えていません...。が、 + + Your English has room to improve to some extent. I think you are doing hard work on your own practice but please keep it going. + + (君の英語はまだ完璧じゃないから、めっちゃ向上させようと頑張ってると思うけどそれを続けて欲しい) みたいなニュアンスだったと思います...。\\n:::\\n\\n::image-figure{src="https://media.graphcms.com/output=format + /G2yGF4i4SLa2smyFwZsA" caption="ボードゲームのミートアップにて。英語でのルール説明はするのもされるのも難しい。雰囲気でボドゲをやっている" width="1920" height="1440"}\\n\\n::rich-heading-2[ポジション]{#9b76c1cb}\\n\\n北米の会社だと経験やスキルによってレベルが何段階かに分かれているのが普通です。定義や基準は会社によってまちまちですが、大まかに次のような感じです。\\n\\n* + + Junior + + : 同僚などの助けを借りてタスクをこなせる。複雑な仕事は誰かに咀嚼してシンプルにしてもらう必要がある\\n* + + Intermediate + + : 誰の力も借りずに自走できる。即戦力といえる\\n* + + Senior + + : プロジェクトやタスクを主導できる。専門的な知識を持つ\\n\\n::rich-heading-3[立ち回り]{#7b628e31}\\n\\n上でも書いていますが、シニアポジションになるとコミュニケーション的な仕事の割合が増えてきます。今のところコードを書く6:コミュニケーション4といったバランス感です。会社に長くいればいるほど中心人物になっていくので、いずれコミュニケーションタスクが半分を超える日が来ると思います。\\n\\n他者からの評価は「技術に明るい」「綺麗なコードを書く」という感じなので (自分の認知している中では...ですが) 、自分の立ち回りもその期待に寄せているところがあります。具体的には、会社の中の文化的な話題よりも、技術的な議論に積極的に参加しています。自分の得意分野の仕事が増えていくように印象付けしている形です。先日 (2021年3月) いくつかある年間賞のうちの1つを獲得したので、今のところまあまあうまくいっているのかなと思います。\\n\\n::image-figure{src="https://media.graphcms.com/output=format + /A14T2AlAQdGhsY83XWq5" caption="自宅の作業環境。ベッド下にデスクを置いてマイクを吊るしている" width="1920" height="1920"}\\n\\n::rich-heading-2[技術]{#85e0729d}\\n\\n会社で使っているのはいたって普通の技術スタックです。対外的に話せる範囲 (面接で候補者に話している範囲) だと、\\n\\n* TypeScript、React (Web)、React Native (iOS/Android)、Electron (デスクトップ)\\n* + + Draft.js + + でのリッチテキストエディタやWebSocketでのソケット通信など\\n* 自社 + + デザインシステム + + を構築\\n * いつでも参照できるように + + Abstract + + で定義を管理\\n * Webの実装はReactと + + Styled System + + を利用、Storybookドリブンで開発)\\n* 十数ヶ国語のi18n、翻訳SaaSを通じての自動反映フロー\\n* Web APIは古い箇所は + + Apache Thrift + + 、新しい箇所はGraphQLに移行中\\n* 既存フロントエンドがでかいので + + マイクロフロントエンドアーキテクチャ + + に移行中\\n\\nといった感じです。\\n\\n::rich-heading-3[北米は進んでいるのか? / 優秀なのか?]{#02fbd058}\\n\\nよく聞かれますが、どうなんでしょう...。ただ、以下のことは共通して言えると思います。\\n\\n* 新しい技術トピックの認知具合はどこの国でもほとんど同じです。アンテナを張っている人は知っていますし、レガシーな技術で仕事をしている人もいます\\n* 「ある技術の名前だけ知っている」という状態の人は少ないです。ドキュメントが母国語 (英語) なので読めばわかるというのが理由だと思います\\n* クリーンアーキテクチャやDDDのような技術的方法論の素養については二極化しているように感じます。知っている人は本当にめちゃくちゃ詳しくて、知識も実経験もある所謂 + + アンクル・ボブ + + みたいな人がたまにいます\\n * 特定的に「DDDに明るい」みたいな人は珍しいですが (そもそもDDD自体がが古すぎて認知されていないのかもしれません) 、Technical Design Document作りの一環・基本として似たような知識を持っている人が多いです\\n * ただ、マイクロサービスとかオニオンアーキテクチャみたいな構成系の方法論は認知度が高いです\\n* 僕が渡航してきた2018年の夏時点ですでにReact Nativeを教えている専門学校がちらほらありました\\n\\n::image-figure{src="https://media.graphcms.com/output=format + /LwZcSQ7tSeOVl8pH0BbU" caption="React Vancouverというミートアップの時の様子" width="1920" height="1080"}\\n\\n::rich-heading-2[採用]{#d3c0f99a}\\n\\nエンジニア採用にも絡んでいます。この箇所を書いている時点 (2021/03/19) まででだいたい20人くらいの候補者と面接したと思います。北米でのエンジニア採用について興味のある方は以下の記事もよかったら読んでみてください。\\n\\n:::callout{variant="info"}\\nTODO: あとで書く\\n:::\\n\\nまた、通年とはいきませんが弊社は色々なエンジニア職種で積極的に採用しています。基本的に即戦力になるようなレベルの方を探していますが、実力があれば経験年数は問いません。ただ、経験が少なすぎるとテクニカルリクルーターの目に止まらない可能性もあるので、興味のある人はTwitterか + + こちらのフォームから連絡 + + してもらえるとそういう運要素なしに紹介できます。\\n +

+
+
+`; + +exports[`
skips rendering author and last published date when either of them are omitted 1`] = ` +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua +

+ +
+

+ :::callout{variant="info"}\\n2021年3月に最初に書きました。同じURLで随時このページを更新していく予定です。\\n\\n何か質問などがあればTwitterか、 + + こちらのフォームから送って + + もらえれば順次書き足します。\\n:::\\n\\n::rich-heading-2[働いている会社 / 背景]{#641db2a7}\\n\\nParsable Inc. という会社で、ざっくり言うと工業系のフロントライナーのお客さんたちの手順書やチェックリストをデジタル化するSaaSを作っています。ユーザーが入力した項目の内容や時間からボトルネックを探して手順を改善したりするための機能もあって、単なる電子化というよりかは全体最適化を目指すプラットフォームという感じです。\\n\\n技術的には、GUIでポチポチしながら柔軟に手順書やチェックリストのテンプレートを作れる機能とパフォーマンス計測ができる機能を持ったダッシュボードがあるという感じで、かなり歯応えのある複雑なフロントエンドです。\\n\\n本社はアメリカのサンフランシスコのダウンタウンのど真ん中にありますが、僕は2020年3月にカナダのバンクーバーオフィスでシニアフロントエンドエンジニアとして採用されました。\\n\\n採用されてからすぐにCOVID-19の影響でオフィスがシャットダウンされ、それ以来ずっとリモートで働いています。同僚とイベントで何度か顔を合わせてますが、主にオンラインでのやりとりがメインです。\\n\\n::image-figure{src="https://media.graphcms.com/output=format + /VE7swp8LSZCbftKg1TJ9" caption="同僚とBBQに行った時に遊んだミニスポーツ。Spikeballというらしい" width="1920" height="1200"}\\n\\n::rich-heading-2[英語]{#590b5b4b}\\n\\n「USの会社で働く」ということでまず頭に浮かぶのは英語だと思います。結論から言うと、僕の英語力は徐々に伸びてはいるもののまだ充分に流暢ではないという感じです。シニアポジションの特性上、コミュニケーションの仕事の比重がめちゃくちゃ増えます。ミーティングの中で会話をリードする機会や気の利いたコメント(というより説明)を求められる場面が多くなります。\\n\\n最初の頃は「こういうことを聞かれるだろうな」というのをミーティングの中で想定しておいて、テキストエディタに英語で書き出しておいて話すというのを試していました。元々英語でのライティングが得意だったので使っていたハックという感じです。最近ではそういう下準備なしに複雑な会話もできるようになってきました。\\n\\n::rich-heading-3[英語で喋ると頭が悪くなる]{#51373d1d}\\n\\n第二言語話者の間では通説のようなものですが、英語で話すと日本語で話している時よりも思考のパフォーマンスが下がります。その結果、口から出てくる言葉も稚拙になり、話している相手からは頭が悪いように見えます。これは人間が思考に言語野を用いていて、 + + 持っている言語の単語や表現力に思考範囲が制限されるため + + だと言われています。そのため、英語を使っていると「日本語だったらもっとうまく説明できるのに」みたいなシチュエーションが発生します。\\n\\n僕は英語を勉強している中でこの話を知りましたが、実際に働いてみてその存在を確かに実感しました。この本当の意味はおそらく日本語 (ネイティブ言語) で働いているうちは一生気付くことができなかったです。それまでの自分の思考のパフォーマンスが自分にとって当たり前すぎて「いざ奪われるとこうなるのか」という稀有な体験でした。\\n\\n上でも話した通り僕の英語のスキルはまだまだ下の下なので、前もってドキュメントを作ってミーティングに臨むなどしてオーラルコミュニケーションのコストを減らしたり、そのドキュメントの中でも図をたくさん使うなどして補っています。これはこれで仕事全般として良いプラクティスだとは思いますが、いずれはこういうことをしなくてもいいようにあえて徐々にギプスを外していこうと思っています。\\n\\n::rich-heading-3[英語力の客観的な評価]{#e58c07b6}\\n\\n少し恥ずかしいですが、ざっくり周りの評価を書いておこうと思います。「実際どれくらいのレベルの人がこういうこと言ってるのか」という参考になれば...。\\n\\n* テストでのスコア\\n * 実はIELTS・TOEFLやTOEICなどの統一的なテストを受けたことがありません...。あればよかったんですが...\\n * 周りの人と比べると、おそらくIELTS Overall 6.0か6.5付近で、現時点ではどうあがいてもOverall 7.0以上は取れないと思います\\n * TODO: 何か受ける\\n * TOEICだとリスニングとリーディングが主ですが、仕事で必要なのはスピーキングなのでパフォーマンス計測にはIELTSやTOEFLが良いと思います\\n* 同僚からの評価\\n * + + 『テキストコミュニケーションは完璧、口頭でのコミュニケーションは詳細を欠くことがある』 + + \\n * + + 『君の英語はとてもじゃないがパーフェクトとは言えない、研鑽して欲しい』 + + \\n * 言われた日と次の日は正直めちゃくちゃ凹みました\\n * 5ヶ月くらい経った後で同じ人から + + 『かなり向上を感じた。今はプロフェッショナルな感じがある』 + + と評価が向上しました💪\\n\\nただ、日によってめちゃくちゃ英語が上手な日とものすごく下手な日があります。おそらく身体的なパフォーマンスと同じように、頭脳やメンタルのパフォーマンスによって変動があるんだと思います。また、休みの日に日本語のYouTubeを一日中見ていた次の日の月曜日なども英語がすごく下手になったりします。\\n\\n:::callout{variant="info"}\\nフィードバックで質問をいただいたので追記:\\n\\n> Q. 記事内に「君の英語はとてもじゃないがパーフェクトとは言えない、研鑽して欲しい」と言われたとありますが、"英語"でどのように言われたのか教えて頂くことは可能でしょうか?\\n\\nA. 1-on-1 ミーティングの中で口頭で言われたことなので正確にどんな表現で言われたかは覚えていません...。が、 + + Your English has room to improve to some extent. I think you are doing hard work on your own practice but please keep it going. + + (君の英語はまだ完璧じゃないから、めっちゃ向上させようと頑張ってると思うけどそれを続けて欲しい) みたいなニュアンスだったと思います...。\\n:::\\n\\n::image-figure{src="https://media.graphcms.com/output=format + /G2yGF4i4SLa2smyFwZsA" caption="ボードゲームのミートアップにて。英語でのルール説明はするのもされるのも難しい。雰囲気でボドゲをやっている" width="1920" height="1440"}\\n\\n::rich-heading-2[ポジション]{#9b76c1cb}\\n\\n北米の会社だと経験やスキルによってレベルが何段階かに分かれているのが普通です。定義や基準は会社によってまちまちですが、大まかに次のような感じです。\\n\\n* + + Junior + + : 同僚などの助けを借りてタスクをこなせる。複雑な仕事は誰かに咀嚼してシンプルにしてもらう必要がある\\n* + + Intermediate + + : 誰の力も借りずに自走できる。即戦力といえる\\n* + + Senior + + : プロジェクトやタスクを主導できる。専門的な知識を持つ\\n\\n::rich-heading-3[立ち回り]{#7b628e31}\\n\\n上でも書いていますが、シニアポジションになるとコミュニケーション的な仕事の割合が増えてきます。今のところコードを書く6:コミュニケーション4といったバランス感です。会社に長くいればいるほど中心人物になっていくので、いずれコミュニケーションタスクが半分を超える日が来ると思います。\\n\\n他者からの評価は「技術に明るい」「綺麗なコードを書く」という感じなので (自分の認知している中では...ですが) 、自分の立ち回りもその期待に寄せているところがあります。具体的には、会社の中の文化的な話題よりも、技術的な議論に積極的に参加しています。自分の得意分野の仕事が増えていくように印象付けしている形です。先日 (2021年3月) いくつかある年間賞のうちの1つを獲得したので、今のところまあまあうまくいっているのかなと思います。\\n\\n::image-figure{src="https://media.graphcms.com/output=format + /A14T2AlAQdGhsY83XWq5" caption="自宅の作業環境。ベッド下にデスクを置いてマイクを吊るしている" width="1920" height="1920"}\\n\\n::rich-heading-2[技術]{#85e0729d}\\n\\n会社で使っているのはいたって普通の技術スタックです。対外的に話せる範囲 (面接で候補者に話している範囲) だと、\\n\\n* TypeScript、React (Web)、React Native (iOS/Android)、Electron (デスクトップ)\\n* + + Draft.js + + でのリッチテキストエディタやWebSocketでのソケット通信など\\n* 自社 + + デザインシステム + + を構築\\n * いつでも参照できるように + + Abstract + + で定義を管理\\n * Webの実装はReactと + + Styled System + + を利用、Storybookドリブンで開発)\\n* 十数ヶ国語のi18n、翻訳SaaSを通じての自動反映フロー\\n* Web APIは古い箇所は + + Apache Thrift + + 、新しい箇所はGraphQLに移行中\\n* 既存フロントエンドがでかいので + + マイクロフロントエンドアーキテクチャ + + に移行中\\n\\nといった感じです。\\n\\n::rich-heading-3[北米は進んでいるのか? / 優秀なのか?]{#02fbd058}\\n\\nよく聞かれますが、どうなんでしょう...。ただ、以下のことは共通して言えると思います。\\n\\n* 新しい技術トピックの認知具合はどこの国でもほとんど同じです。アンテナを張っている人は知っていますし、レガシーな技術で仕事をしている人もいます\\n* 「ある技術の名前だけ知っている」という状態の人は少ないです。ドキュメントが母国語 (英語) なので読めばわかるというのが理由だと思います\\n* クリーンアーキテクチャやDDDのような技術的方法論の素養については二極化しているように感じます。知っている人は本当にめちゃくちゃ詳しくて、知識も実経験もある所謂 + + アンクル・ボブ + + みたいな人がたまにいます\\n * 特定的に「DDDに明るい」みたいな人は珍しいですが (そもそもDDD自体がが古すぎて認知されていないのかもしれません) 、Technical Design Document作りの一環・基本として似たような知識を持っている人が多いです\\n * ただ、マイクロサービスとかオニオンアーキテクチャみたいな構成系の方法論は認知度が高いです\\n* 僕が渡航してきた2018年の夏時点ですでにReact Nativeを教えている専門学校がちらほらありました\\n\\n::image-figure{src="https://media.graphcms.com/output=format + /LwZcSQ7tSeOVl8pH0BbU" caption="React Vancouverというミートアップの時の様子" width="1920" height="1080"}\\n\\n::rich-heading-2[採用]{#d3c0f99a}\\n\\nエンジニア採用にも絡んでいます。この箇所を書いている時点 (2021/03/19) まででだいたい20人くらいの候補者と面接したと思います。北米でのエンジニア採用について興味のある方は以下の記事もよかったら読んでみてください。\\n\\n:::callout{variant="info"}\\nTODO: あとで書く\\n:::\\n\\nまた、通年とはいきませんが弊社は色々なエンジニア職種で積極的に採用しています。基本的に即戦力になるようなレベルの方を探していますが、実力があれば経験年数は問いません。ただ、経験が少なすぎるとテクニカルリクルーターの目に止まらない可能性もあるので、興味のある人はTwitterか + + こちらのフォームから連絡 + + してもらえるとそういう運要素なしに紹介できます。\\n +

+
+
+`; + +exports[`
skips rendering tags when they are omitted 1`] = ` +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua +

+ +
+

+ :::callout{variant="info"}\\n2021年3月に最初に書きました。同じURLで随時このページを更新していく予定です。\\n\\n何か質問などがあればTwitterか、 + + こちらのフォームから送って + + もらえれば順次書き足します。\\n:::\\n\\n::rich-heading-2[働いている会社 / 背景]{#641db2a7}\\n\\nParsable Inc. という会社で、ざっくり言うと工業系のフロントライナーのお客さんたちの手順書やチェックリストをデジタル化するSaaSを作っています。ユーザーが入力した項目の内容や時間からボトルネックを探して手順を改善したりするための機能もあって、単なる電子化というよりかは全体最適化を目指すプラットフォームという感じです。\\n\\n技術的には、GUIでポチポチしながら柔軟に手順書やチェックリストのテンプレートを作れる機能とパフォーマンス計測ができる機能を持ったダッシュボードがあるという感じで、かなり歯応えのある複雑なフロントエンドです。\\n\\n本社はアメリカのサンフランシスコのダウンタウンのど真ん中にありますが、僕は2020年3月にカナダのバンクーバーオフィスでシニアフロントエンドエンジニアとして採用されました。\\n\\n採用されてからすぐにCOVID-19の影響でオフィスがシャットダウンされ、それ以来ずっとリモートで働いています。同僚とイベントで何度か顔を合わせてますが、主にオンラインでのやりとりがメインです。\\n\\n::image-figure{src="https://media.graphcms.com/output=format + /VE7swp8LSZCbftKg1TJ9" caption="同僚とBBQに行った時に遊んだミニスポーツ。Spikeballというらしい" width="1920" height="1200"}\\n\\n::rich-heading-2[英語]{#590b5b4b}\\n\\n「USの会社で働く」ということでまず頭に浮かぶのは英語だと思います。結論から言うと、僕の英語力は徐々に伸びてはいるもののまだ充分に流暢ではないという感じです。シニアポジションの特性上、コミュニケーションの仕事の比重がめちゃくちゃ増えます。ミーティングの中で会話をリードする機会や気の利いたコメント(というより説明)を求められる場面が多くなります。\\n\\n最初の頃は「こういうことを聞かれるだろうな」というのをミーティングの中で想定しておいて、テキストエディタに英語で書き出しておいて話すというのを試していました。元々英語でのライティングが得意だったので使っていたハックという感じです。最近ではそういう下準備なしに複雑な会話もできるようになってきました。\\n\\n::rich-heading-3[英語で喋ると頭が悪くなる]{#51373d1d}\\n\\n第二言語話者の間では通説のようなものですが、英語で話すと日本語で話している時よりも思考のパフォーマンスが下がります。その結果、口から出てくる言葉も稚拙になり、話している相手からは頭が悪いように見えます。これは人間が思考に言語野を用いていて、 + + 持っている言語の単語や表現力に思考範囲が制限されるため + + だと言われています。そのため、英語を使っていると「日本語だったらもっとうまく説明できるのに」みたいなシチュエーションが発生します。\\n\\n僕は英語を勉強している中でこの話を知りましたが、実際に働いてみてその存在を確かに実感しました。この本当の意味はおそらく日本語 (ネイティブ言語) で働いているうちは一生気付くことができなかったです。それまでの自分の思考のパフォーマンスが自分にとって当たり前すぎて「いざ奪われるとこうなるのか」という稀有な体験でした。\\n\\n上でも話した通り僕の英語のスキルはまだまだ下の下なので、前もってドキュメントを作ってミーティングに臨むなどしてオーラルコミュニケーションのコストを減らしたり、そのドキュメントの中でも図をたくさん使うなどして補っています。これはこれで仕事全般として良いプラクティスだとは思いますが、いずれはこういうことをしなくてもいいようにあえて徐々にギプスを外していこうと思っています。\\n\\n::rich-heading-3[英語力の客観的な評価]{#e58c07b6}\\n\\n少し恥ずかしいですが、ざっくり周りの評価を書いておこうと思います。「実際どれくらいのレベルの人がこういうこと言ってるのか」という参考になれば...。\\n\\n* テストでのスコア\\n * 実はIELTS・TOEFLやTOEICなどの統一的なテストを受けたことがありません...。あればよかったんですが...\\n * 周りの人と比べると、おそらくIELTS Overall 6.0か6.5付近で、現時点ではどうあがいてもOverall 7.0以上は取れないと思います\\n * TODO: 何か受ける\\n * TOEICだとリスニングとリーディングが主ですが、仕事で必要なのはスピーキングなのでパフォーマンス計測にはIELTSやTOEFLが良いと思います\\n* 同僚からの評価\\n * + + 『テキストコミュニケーションは完璧、口頭でのコミュニケーションは詳細を欠くことがある』 + + \\n * + + 『君の英語はとてもじゃないがパーフェクトとは言えない、研鑽して欲しい』 + + \\n * 言われた日と次の日は正直めちゃくちゃ凹みました\\n * 5ヶ月くらい経った後で同じ人から + + 『かなり向上を感じた。今はプロフェッショナルな感じがある』 + + と評価が向上しました💪\\n\\nただ、日によってめちゃくちゃ英語が上手な日とものすごく下手な日があります。おそらく身体的なパフォーマンスと同じように、頭脳やメンタルのパフォーマンスによって変動があるんだと思います。また、休みの日に日本語のYouTubeを一日中見ていた次の日の月曜日なども英語がすごく下手になったりします。\\n\\n:::callout{variant="info"}\\nフィードバックで質問をいただいたので追記:\\n\\n> Q. 記事内に「君の英語はとてもじゃないがパーフェクトとは言えない、研鑽して欲しい」と言われたとありますが、"英語"でどのように言われたのか教えて頂くことは可能でしょうか?\\n\\nA. 1-on-1 ミーティングの中で口頭で言われたことなので正確にどんな表現で言われたかは覚えていません...。が、 + + Your English has room to improve to some extent. I think you are doing hard work on your own practice but please keep it going. + + (君の英語はまだ完璧じゃないから、めっちゃ向上させようと頑張ってると思うけどそれを続けて欲しい) みたいなニュアンスだったと思います...。\\n:::\\n\\n::image-figure{src="https://media.graphcms.com/output=format + /G2yGF4i4SLa2smyFwZsA" caption="ボードゲームのミートアップにて。英語でのルール説明はするのもされるのも難しい。雰囲気でボドゲをやっている" width="1920" height="1440"}\\n\\n::rich-heading-2[ポジション]{#9b76c1cb}\\n\\n北米の会社だと経験やスキルによってレベルが何段階かに分かれているのが普通です。定義や基準は会社によってまちまちですが、大まかに次のような感じです。\\n\\n* + + Junior + + : 同僚などの助けを借りてタスクをこなせる。複雑な仕事は誰かに咀嚼してシンプルにしてもらう必要がある\\n* + + Intermediate + + : 誰の力も借りずに自走できる。即戦力といえる\\n* + + Senior + + : プロジェクトやタスクを主導できる。専門的な知識を持つ\\n\\n::rich-heading-3[立ち回り]{#7b628e31}\\n\\n上でも書いていますが、シニアポジションになるとコミュニケーション的な仕事の割合が増えてきます。今のところコードを書く6:コミュニケーション4といったバランス感です。会社に長くいればいるほど中心人物になっていくので、いずれコミュニケーションタスクが半分を超える日が来ると思います。\\n\\n他者からの評価は「技術に明るい」「綺麗なコードを書く」という感じなので (自分の認知している中では...ですが) 、自分の立ち回りもその期待に寄せているところがあります。具体的には、会社の中の文化的な話題よりも、技術的な議論に積極的に参加しています。自分の得意分野の仕事が増えていくように印象付けしている形です。先日 (2021年3月) いくつかある年間賞のうちの1つを獲得したので、今のところまあまあうまくいっているのかなと思います。\\n\\n::image-figure{src="https://media.graphcms.com/output=format + /A14T2AlAQdGhsY83XWq5" caption="自宅の作業環境。ベッド下にデスクを置いてマイクを吊るしている" width="1920" height="1920"}\\n\\n::rich-heading-2[技術]{#85e0729d}\\n\\n会社で使っているのはいたって普通の技術スタックです。対外的に話せる範囲 (面接で候補者に話している範囲) だと、\\n\\n* TypeScript、React (Web)、React Native (iOS/Android)、Electron (デスクトップ)\\n* + + Draft.js + + でのリッチテキストエディタやWebSocketでのソケット通信など\\n* 自社 + + デザインシステム + + を構築\\n * いつでも参照できるように + + Abstract + + で定義を管理\\n * Webの実装はReactと + + Styled System + + を利用、Storybookドリブンで開発)\\n* 十数ヶ国語のi18n、翻訳SaaSを通じての自動反映フロー\\n* Web APIは古い箇所は + + Apache Thrift + + 、新しい箇所はGraphQLに移行中\\n* 既存フロントエンドがでかいので + + マイクロフロントエンドアーキテクチャ + + に移行中\\n\\nといった感じです。\\n\\n::rich-heading-3[北米は進んでいるのか? / 優秀なのか?]{#02fbd058}\\n\\nよく聞かれますが、どうなんでしょう...。ただ、以下のことは共通して言えると思います。\\n\\n* 新しい技術トピックの認知具合はどこの国でもほとんど同じです。アンテナを張っている人は知っていますし、レガシーな技術で仕事をしている人もいます\\n* 「ある技術の名前だけ知っている」という状態の人は少ないです。ドキュメントが母国語 (英語) なので読めばわかるというのが理由だと思います\\n* クリーンアーキテクチャやDDDのような技術的方法論の素養については二極化しているように感じます。知っている人は本当にめちゃくちゃ詳しくて、知識も実経験もある所謂 + + アンクル・ボブ + + みたいな人がたまにいます\\n * 特定的に「DDDに明るい」みたいな人は珍しいですが (そもそもDDD自体がが古すぎて認知されていないのかもしれません) 、Technical Design Document作りの一環・基本として似たような知識を持っている人が多いです\\n * ただ、マイクロサービスとかオニオンアーキテクチャみたいな構成系の方法論は認知度が高いです\\n* 僕が渡航してきた2018年の夏時点ですでにReact Nativeを教えている専門学校がちらほらありました\\n\\n::image-figure{src="https://media.graphcms.com/output=format + /LwZcSQ7tSeOVl8pH0BbU" caption="React Vancouverというミートアップの時の様子" width="1920" height="1080"}\\n\\n::rich-heading-2[採用]{#d3c0f99a}\\n\\nエンジニア採用にも絡んでいます。この箇所を書いている時点 (2021/03/19) まででだいたい20人くらいの候補者と面接したと思います。北米でのエンジニア採用について興味のある方は以下の記事もよかったら読んでみてください。\\n\\n:::callout{variant="info"}\\nTODO: あとで書く\\n:::\\n\\nまた、通年とはいきませんが弊社は色々なエンジニア職種で積極的に採用しています。基本的に即戦力になるようなレベルの方を探していますが、実力があれば経験年数は問いません。ただ、経験が少なすぎるとテクニカルリクルーターの目に止まらない可能性もあるので、興味のある人はTwitterか + + こちらのフォームから連絡 + + してもらえるとそういう運要素なしに紹介できます。\\n +

+
+
+`; diff --git a/components/__snapshots__/aside-navigation.spec.tsx.snap b/components/__snapshots__/aside-navigation.spec.tsx.snap new file mode 100644 index 00000000..92252224 --- /dev/null +++ b/components/__snapshots__/aside-navigation.spec.tsx.snap @@ -0,0 +1,713 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders the list of all posts and the table of contents of the article currently shown (when the user is at index page) 1`] = ` +
+ + +
+`; + +exports[` renders the list of all posts and the table of contents of the article currently shown (when the user is at post page) 1`] = ` + +`; diff --git a/components/__snapshots__/page-layout.spec.tsx.snap b/components/__snapshots__/page-layout.spec.tsx.snap new file mode 100644 index 00000000..b80214c5 --- /dev/null +++ b/components/__snapshots__/page-layout.spec.tsx.snap @@ -0,0 +1,55 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders children with styles 1`] = ` +
+
+
+ This is main +
+
+ + +
+
+`; diff --git a/components/article.spec.tsx b/components/article.spec.tsx new file mode 100644 index 00000000..e14a1308 --- /dev/null +++ b/components/article.spec.tsx @@ -0,0 +1,65 @@ +import { render } from "@testing-library/react"; +import * as React from "react"; +import { TestApp } from "../core/test-app"; +import { Article } from "./article"; + +describe("
", () => { + it("renders content by the given props", () => { + const { getByTestId } = render( + +
+ + ); + + expect(getByTestId("article")).toMatchSnapshot(); + }); + + it("skips rendering author and last published date when either of them are omitted", () => { + const { getByTestId } = render( + +
+ + ); + + expect(getByTestId("article")).toMatchSnapshot(); + }); + + it("skips rendering tags when they are omitted", () => { + const { getByTestId } = render( + +
+ + ); + + expect(getByTestId("article")).toMatchSnapshot(); + }); +}); diff --git a/components/article.stories.tsx b/components/article.stories.tsx new file mode 100644 index 00000000..b68f22d8 --- /dev/null +++ b/components/article.stories.tsx @@ -0,0 +1,56 @@ +import { Story, Meta } from "@storybook/react"; +import * as React from "react"; +import { Article, ArticleProps } from "./article"; + +export default { + title: "Components/Article", + component: Article, + argTypes: { + title: { + control: { type: "text" }, + }, + coverImageUrl: { + control: { type: "text" }, + }, + tags: { + control: { type: "array" }, + }, + lastPublishedAt: { + control: { type: "date" }, + }, + author: { + control: { type: "object" }, + }, + body: { + control: { type: "text" }, + }, + }, + args: { + title: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua", + coverImageUrl: + "https://i.picsum.photos/id/650/2048/1170.jpg?hmac=XYuLh7sFn9PAmpUQsMgPaZ_lhmJbfYgMdTFQp3hnLzI", + tags: ["Career", "Frontend", "Security", "Travel"], + lastPublishedAt: new Date(), + author: { + name: "John Due", + avatarUrl: + "https://i.picsum.photos/id/1011/256/256.jpg?hmac=Zu92sdcdhGjlD7pgy52JKhO_7kTORVxeI67oCdVePx0", + }, + body: + ':::callout{variant="info"}\n2021年3月に最初に書きました。同じURLで随時このページを更新していく予定です。\n\n何か質問などがあればTwitterか、:feedback[こちらのフォームから送って]もらえれば順次書き足します。\n:::\n\n::rich-heading-2[働いている会社 / 背景]{#641db2a7}\n\nParsable Inc. という会社で、ざっくり言うと工業系のフロントライナーのお客さんたちの手順書やチェックリストをデジタル化するSaaSを作っています。ユーザーが入力した項目の内容や時間からボトルネックを探して手順を改善したりするための機能もあって、単なる電子化というよりかは全体最適化を目指すプラットフォームという感じです。\n\n技術的には、GUIでポチポチしながら柔軟に手順書やチェックリストのテンプレートを作れる機能とパフォーマンス計測ができる機能を持ったダッシュボードがあるという感じで、かなり歯応えのある複雑なフロントエンドです。\n\n本社はアメリカのサンフランシスコのダウンタウンのど真ん中にありますが、僕は2020年3月にカナダのバンクーバーオフィスでシニアフロントエンドエンジニアとして採用されました。\n\n採用されてからすぐにCOVID-19の影響でオフィスがシャットダウンされ、それ以来ずっとリモートで働いています。同僚とイベントで何度か顔を合わせてますが、主にオンラインでのやりとりがメインです。\n\n::image-figure{src="https://media.graphcms.com/output=format:jpg/VE7swp8LSZCbftKg1TJ9" caption="同僚とBBQに行った時に遊んだミニスポーツ。Spikeballというらしい" width="1920" height="1200"}\n\n::rich-heading-2[英語]{#590b5b4b}\n\n「USの会社で働く」ということでまず頭に浮かぶのは英語だと思います。結論から言うと、僕の英語力は徐々に伸びてはいるもののまだ充分に流暢ではないという感じです。シニアポジションの特性上、コミュニケーションの仕事の比重がめちゃくちゃ増えます。ミーティングの中で会話をリードする機会や気の利いたコメント(というより説明)を求められる場面が多くなります。\n\n最初の頃は「こういうことを聞かれるだろうな」というのをミーティングの中で想定しておいて、テキストエディタに英語で書き出しておいて話すというのを試していました。元々英語でのライティングが得意だったので使っていたハックという感じです。最近ではそういう下準備なしに複雑な会話もできるようになってきました。\n\n::rich-heading-3[英語で喋ると頭が悪くなる]{#51373d1d}\n\n第二言語話者の間では通説のようなものですが、英語で話すと日本語で話している時よりも思考のパフォーマンスが下がります。その結果、口から出てくる言葉も稚拙になり、話している相手からは頭が悪いように見えます。これは人間が思考に言語野を用いていて、**持っている言語の単語や表現力に思考範囲が制限されるため**だと言われています。そのため、英語を使っていると「日本語だったらもっとうまく説明できるのに」みたいなシチュエーションが発生します。\n\n僕は英語を勉強している中でこの話を知りましたが、実際に働いてみてその存在を確かに実感しました。この本当の意味はおそらく日本語 (ネイティブ言語) で働いているうちは一生気付くことができなかったです。それまでの自分の思考のパフォーマンスが自分にとって当たり前すぎて「いざ奪われるとこうなるのか」という稀有な体験でした。\n\n上でも話した通り僕の英語のスキルはまだまだ下の下なので、前もってドキュメントを作ってミーティングに臨むなどしてオーラルコミュニケーションのコストを減らしたり、そのドキュメントの中でも図をたくさん使うなどして補っています。これはこれで仕事全般として良いプラクティスだとは思いますが、いずれはこういうことをしなくてもいいようにあえて徐々にギプスを外していこうと思っています。\n\n::rich-heading-3[英語力の客観的な評価]{#e58c07b6}\n\n少し恥ずかしいですが、ざっくり周りの評価を書いておこうと思います。「実際どれくらいのレベルの人がこういうこと言ってるのか」という参考になれば...。\n\n* テストでのスコア\n * 実はIELTS・TOEFLやTOEICなどの統一的なテストを受けたことがありません...。あればよかったんですが...\n * 周りの人と比べると、おそらくIELTS Overall 6.0か6.5付近で、現時点ではどうあがいてもOverall 7.0以上は取れないと思います\n * TODO: 何か受ける\n * TOEICだとリスニングとリーディングが主ですが、仕事で必要なのはスピーキングなのでパフォーマンス計測にはIELTSやTOEFLが良いと思います\n* 同僚からの評価\n * *『テキストコミュニケーションは完璧、口頭でのコミュニケーションは詳細を欠くことがある』*\n * *『君の英語はとてもじゃないがパーフェクトとは言えない、研鑽して欲しい』*\n * 言われた日と次の日は正直めちゃくちゃ凹みました\n * 5ヶ月くらい経った後で同じ人から *『かなり向上を感じた。今はプロフェッショナルな感じがある』* と評価が向上しました💪\n\nただ、日によってめちゃくちゃ英語が上手な日とものすごく下手な日があります。おそらく身体的なパフォーマンスと同じように、頭脳やメンタルのパフォーマンスによって変動があるんだと思います。また、休みの日に日本語のYouTubeを一日中見ていた次の日の月曜日なども英語がすごく下手になったりします。\n\n:::callout{variant="info"}\nフィードバックで質問をいただいたので追記:\n\n> Q. 記事内に「君の英語はとてもじゃないがパーフェクトとは言えない、研鑽して欲しい」と言われたとありますが、"英語"でどのように言われたのか教えて頂くことは可能でしょうか?\n\nA. 1-on-1 ミーティングの中で口頭で言われたことなので正確にどんな表現で言われたかは覚えていません...。が、*Your English has room to improve to some extent. I think you are doing hard work on your own practice but please keep it going.* (君の英語はまだ完璧じゃないから、めっちゃ向上させようと頑張ってると思うけどそれを続けて欲しい) みたいなニュアンスだったと思います...。\n:::\n\n::image-figure{src="https://media.graphcms.com/output=format:jpg/G2yGF4i4SLa2smyFwZsA" caption="ボードゲームのミートアップにて。英語でのルール説明はするのもされるのも難しい。雰囲気でボドゲをやっている" width="1920" height="1440"}\n\n::rich-heading-2[ポジション]{#9b76c1cb}\n\n北米の会社だと経験やスキルによってレベルが何段階かに分かれているのが普通です。定義や基準は会社によってまちまちですが、大まかに次のような感じです。\n\n* **Junior**: 同僚などの助けを借りてタスクをこなせる。複雑な仕事は誰かに咀嚼してシンプルにしてもらう必要がある\n* **Intermediate**: 誰の力も借りずに自走できる。即戦力といえる\n* **Senior**: プロジェクトやタスクを主導できる。専門的な知識を持つ\n\n::rich-heading-3[立ち回り]{#7b628e31}\n\n上でも書いていますが、シニアポジションになるとコミュニケーション的な仕事の割合が増えてきます。今のところコードを書く6:コミュニケーション4といったバランス感です。会社に長くいればいるほど中心人物になっていくので、いずれコミュニケーションタスクが半分を超える日が来ると思います。\n\n他者からの評価は「技術に明るい」「綺麗なコードを書く」という感じなので (自分の認知している中では...ですが) 、自分の立ち回りもその期待に寄せているところがあります。具体的には、会社の中の文化的な話題よりも、技術的な議論に積極的に参加しています。自分の得意分野の仕事が増えていくように印象付けしている形です。先日 (2021年3月) いくつかある年間賞のうちの1つを獲得したので、今のところまあまあうまくいっているのかなと思います。\n\n::image-figure{src="https://media.graphcms.com/output=format:jpg/A14T2AlAQdGhsY83XWq5" caption="自宅の作業環境。ベッド下にデスクを置いてマイクを吊るしている" width="1920" height="1920"}\n\n::rich-heading-2[技術]{#85e0729d}\n\n会社で使っているのはいたって普通の技術スタックです。対外的に話せる範囲 (面接で候補者に話している範囲) だと、\n\n* TypeScript、React (Web)、React Native (iOS/Android)、Electron (デスクトップ)\n* [Draft.js](https://draftjs.org/)でのリッチテキストエディタやWebSocketでのソケット通信など\n* 自社[デザインシステム](https://note.com/yoshigorou/n/n102e933d4f58)を構築\n * いつでも参照できるように[Abstract](https://www.abstract.com/)で定義を管理\n * Webの実装はReactと[Styled System](https://styled-system.com/)を利用、Storybookドリブンで開発)\n* 十数ヶ国語のi18n、翻訳SaaSを通じての自動反映フロー\n* Web APIは古い箇所は[Apache Thrift](https://thrift.apache.org/)、新しい箇所はGraphQLに移行中\n* 既存フロントエンドがでかいので[マイクロフロントエンドアーキテクチャ](https://micro-frontends.org/)に移行中\n\nといった感じです。\n\n::rich-heading-3[北米は進んでいるのか? / 優秀なのか?]{#02fbd058}\n\nよく聞かれますが、どうなんでしょう...。ただ、以下のことは共通して言えると思います。\n\n* 新しい技術トピックの認知具合はどこの国でもほとんど同じです。アンテナを張っている人は知っていますし、レガシーな技術で仕事をしている人もいます\n* 「ある技術の名前だけ知っている」という状態の人は少ないです。ドキュメントが母国語 (英語) なので読めばわかるというのが理由だと思います\n* クリーンアーキテクチャやDDDのような技術的方法論の素養については二極化しているように感じます。知っている人は本当にめちゃくちゃ詳しくて、知識も実経験もある所謂[アンクル・ボブ](https://www.google.com/search?q=%E3%83%AD%E3%83%90%E3%83%BC%E3%83%88%E3%83%BBC%E3%83%BB%E3%83%9E%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3)みたいな人がたまにいます\n * 特定的に「DDDに明るい」みたいな人は珍しいですが (そもそもDDD自体がが古すぎて認知されていないのかもしれません) 、Technical Design Document作りの一環・基本として似たような知識を持っている人が多いです\n * ただ、マイクロサービスとかオニオンアーキテクチャみたいな構成系の方法論は認知度が高いです\n* 僕が渡航してきた2018年の夏時点ですでにReact Nativeを教えている専門学校がちらほらありました\n\n::image-figure{src="https://media.graphcms.com/output=format:jpg/LwZcSQ7tSeOVl8pH0BbU" caption="React Vancouverというミートアップの時の様子" width="1920" height="1080"}\n\n::rich-heading-2[採用]{#d3c0f99a}\n\nエンジニア採用にも絡んでいます。この箇所を書いている時点 (2021/03/19) まででだいたい20人くらいの候補者と面接したと思います。北米でのエンジニア採用について興味のある方は以下の記事もよかったら読んでみてください。\n\n:::callout{variant="info"}\nTODO: あとで書く\n:::\n\nまた、通年とはいきませんが弊社は色々なエンジニア職種で積極的に採用しています。基本的に即戦力になるようなレベルの方を探していますが、実力があれば経験年数は問いません。ただ、経験が少なすぎるとテクニカルリクルーターの目に止まらない可能性もあるので、興味のある人はTwitterか:feedback[こちらのフォームから連絡]してもらえるとそういう運要素なしに紹介できます。\n', + }, +} as Meta; + +export const Example: Story = (args) => { + const props = { + ...args, + lastPublishedAt: args.lastPublishedAt + ? new Date(args.lastPublishedAt) + : undefined, + author: + Object.keys(args.author ?? {}).length === 0 ? undefined : args.author, + }; + + return
; +}; diff --git a/components/article.tsx b/components/article.tsx new file mode 100644 index 00000000..299d121a --- /dev/null +++ b/components/article.tsx @@ -0,0 +1,145 @@ +import { css } from "@linaria/core"; +import * as React from "react"; +import { FormattedMessage } from "react-intl"; +import { Badge } from "./badge"; +import { HorizontalList } from "./layout"; +import { Markdown } from "./markdown-article"; + +export interface ArticleProps extends React.Attributes { + title: string; + coverImageUrl: string; + tags?: string[]; + lastPublishedAt?: Date; + author?: { + name: string; + avatarUrl: string; + }; + body: string; + className?: string; + style?: React.CSSProperties; +} + +export const Article: React.VFC = ({ + title, + coverImageUrl, + tags, + lastPublishedAt, + author, + body, + ...props +}) => { + return ( +
+ {title} + +

+ {title} +

+ + {(author && lastPublishedAt) || tags ? ( + + ) : null} + + +
+ ); +}; diff --git a/components/aside-navigation.spec.tsx b/components/aside-navigation.spec.tsx new file mode 100644 index 00000000..7b6b52db --- /dev/null +++ b/components/aside-navigation.spec.tsx @@ -0,0 +1,76 @@ +import { render } from "@testing-library/react"; +import * as React from "react"; +import { TestApp } from "../core/test-app"; +import { AsideNavigation } from "./aside-navigation"; + +const posts = [ + { slug: "47qwqp", title: "aliquid autem eius et non iure vitae" }, + { slug: "lccd43", title: "officiis alias est amet illo saepe omnis" }, + { slug: "mrrw0e", title: "fugiat delectus aut voluptatem beatae ab" }, + { slug: "csvh21", title: "fugit recusandae aut veniam eos iure ut quia" }, + { slug: "pn04ii", title: "dolores laudantium est voluptates ea odio labore" }, + { slug: "6qem31", title: "doloribus" }, + { slug: "r5ylk0", title: "vel maiores id sapiente voluptatem" }, + { slug: "znq279", title: "libero" }, + { slug: "sg4g6o", title: "porro at exercitationem officiis illum molestias" }, + { slug: "sz3nty", title: "non perspiciatis est magnam" }, +]; + +const tableOfContents = [ + { id: "zg2r6g", level: 2, text: "in aut natus eius nesciunt" }, + { id: "2sfiwl", level: 3, text: "et quidem vel labore aut vero eaque" }, + { id: "blmqmw", level: 3, text: "aut omnis saepe totam aut quasi magni" }, + { + id: "gg51id", + level: 4, + text: + "consequatur reprehenderit aut qui voluptatum tempora qui fugiat accusamus", + }, + { id: "jzmtod", level: 3, text: "qui quia quo sunt" }, + { id: "1lpeq1", level: 4, text: "est in inventore rerum" }, + { id: "bf4cdf", level: 2, text: "voluptatem labore" }, + { id: "gu0a2v", level: 2, text: "odit voluptatum optio possimus alias aut" }, + { id: "lcm25k", level: 3, text: "quia facilis tenetur velit aliquam atque" }, + { id: "xw1lzt", level: 4, text: "rerum molestias hic harum at placeat" }, + { id: "lwzd8q", level: 5, text: "accusamus eius aspernatur labore nulla" }, +]; + +describe("", () => { + it("renders the list of all posts and the table of contents of the article currently shown (when the user is at index page)", () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId("aside-navigation")).toMatchSnapshot(); + }); + + it("renders the list of all posts and the table of contents of the article currently shown (when the user is at post page)", () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId("aside-navigation")).toMatchSnapshot(); + }); +}); diff --git a/components/aside-navigation.stories.tsx b/components/aside-navigation.stories.tsx new file mode 100644 index 00000000..fc0de849 --- /dev/null +++ b/components/aside-navigation.stories.tsx @@ -0,0 +1,157 @@ +import * as React from "react"; +import { Story, Meta } from "@storybook/react"; +import { AsideNavigation, AsideNavigationProps } from "./aside-navigation"; + +const posts = [ + { + slug: "a07c7e36-4cc7-4edf-9383-be11cb422ec1", + title: "aliquid autem eius et non iure vitae", + }, + { + slug: "dfe0e318-0907-4798-8463-cbf9b0ff0926", + title: "officiis alias est amet illo saepe omnis", + }, + { + slug: "8925060a-c590-4c50-85d3-eb8aedee84b1", + title: "fugiat delectus aut voluptatem beatae voluptatem ab", + }, + { + slug: "b4a2a0c1-41d9-4e03-aec1-20b57358617a", + title: "fugit recusandae aut veniam eos maxime doloribus iure ut quia", + }, + { + slug: "40c25287-aa2e-448c-a2f5-27878e9cc3aa", + title: + "dolores laudantium est exercitationem accusamus voluptates ea odio labore", + }, + { slug: "6a914fb4-c3cf-4645-889a-f9565bc845be", title: "doloribus" }, + { + slug: "a327ef79-f858-4d4d-9635-786b298f088c", + title: "vel maiores id sapiente voluptatem", + }, + { slug: "0366aa0e-3aa0-478a-9c77-41f18cc09b4f", title: "libero" }, + { + slug: "e0d2ef14-7c95-4349-915a-396a954df124", + title: + "porro ut molestias at exercitationem officiis veritatis illum dolores molestias", + }, + { + slug: "3752118d-55f6-4333-8833-5fc15226cf1b", + title: "non perspiciatis est magnam", + }, + { + slug: "5e8b9c73-f715-48de-b698-14a54d863a8c", + title: + "doloribus necessitatibus veniam magni sed quia sit quo culpa corrupti praesentium beatae", + }, + { + slug: "9feb3159-06d0-49a7-92e1-c88d8042ca71", + title: "et rem voluptas porro quia voluptatum eius vero hic a consectetur", + }, + { slug: "13ff88c4-1453-413b-ae2e-108f27411429", title: "fugiat repudiandae" }, + { slug: "01cbfcb4-5dad-4a4d-949f-2ac090a3f064", title: "a" }, + { + slug: "f9643ba8-40fe-43bd-9c63-ca5aae7fdab4", + title: + "voluptatem placeat facilis amet aut minus nesciunt autem aliquam qui", + }, + { + slug: "95157aca-696d-49bf-81d8-e003eb82349d", + title: + "numquam praesentium eveniet dolorem veniam harum quos ad nihil tempore", + }, + { + slug: "710d1f37-8c69-4bdd-b8aa-06c02b3e4c76", + title: "id qui voluptas numquam quae", + }, + { slug: "c2e426fa-7189-46ec-9282-8ae529adc5bb", title: "quibusdam" }, + { + slug: "c9875243-965b-4024-bda8-fe1dadea42d1", + title: "quia consequatur mollitia ut dolore minima culpa", + }, + { + slug: "422c7b49-0e90-4396-b31b-d141980cd8ca", + title: "a dolores unde in velit", + }, +]; + +const tableOfContents = [ + { + id: "c20e1398-5b65-4c1b-8358-ebbe08ea479a", + level: 2, + text: "in aut natus eius nesciunt", + }, + { + id: "adee5cd0-10d0-49b9-bc84-bcb4a21a0212", + level: 3, + text: "et quidem vel quaerat labore aut vero eaque", + }, + { + id: "71134849-6e99-4c30-baaf-a134b36504de", + level: 3, + text: "aut omnis saepe enim corrupti totam aut quasi magni", + }, + { + id: "1da1fec2-e77c-413b-8581-c3cf19578874", + level: 4, + text: + "consequatur reprehenderit aut qui voluptatum tempora qui fugiat accusamus", + }, + { + id: "7a71e56d-354c-4fbc-a3d7-c1a4a31354cd", + level: 3, + text: "qui quia quo sunt", + }, + { + id: "018ad6fd-56ea-46df-a526-05be022267a7", + level: 4, + text: "est in inventore rerum", + }, + { + id: "9889e69e-2163-49be-b1f8-330dd90ec12a", + level: 2, + text: "voluptatem labore", + }, + { + id: "53cfe389-d691-4859-b7cb-29b72cd92052", + level: 2, + text: "odit voluptatum optio aliquam possimus alias a aut", + }, + { + id: "3703f87d-f1cf-4165-90f8-94772d705fa4", + level: 3, + text: "quia facilis neque tenetur voluptatem aperiam velit aliquam atque", + }, + { + id: "573433dc-dac7-4f36-881e-31a2c2c34222", + level: 4, + text: + "rerum molestias hic harum at dignissimos accusamus assumenda placeat nihil autem commodi", + }, + { + id: "9036887d-d51a-4e18-aa7c-85276f76879a", + level: 5, + text: + "accusamus eum commodi reprehenderit eius aspernatur autem maxime labore nulla qui autem", + }, +]; + +export default { + title: "Components/AsideNavigation", + component: AsideNavigation, + argTypes: {}, + args: { + posts, + tableOfContents, + }, + parameters: { + initialRoute: { + pathname: "/posts/[slug]", + asPath: `/posts/${posts[2]!.slug}`, + }, + }, +} as Meta; + +export const Example: Story = (props) => ( + +); diff --git a/components/aside-navigation.tsx b/components/aside-navigation.tsx new file mode 100644 index 00000000..e623009f --- /dev/null +++ b/components/aside-navigation.tsx @@ -0,0 +1,99 @@ +import { css } from "@linaria/core"; +import { useRouter } from "next/router"; +import * as React from "react"; +import { FormattedMessage } from "react-intl"; +import { LocaleSwitcher } from "./locale-switcher"; +import { PostList, PostListItem } from "./post-list"; +import { TableOfContents, TableOfContentsItem } from "./table-of-contents"; + +export interface AsideNavigationProps extends React.Attributes { + posts: { + slug: string; + title: string; + }[]; + tableOfContents: { + id: string; + level: number; + text: string; + }[]; +} + +export const AsideNavigation: React.VFC = ({ + posts, + tableOfContents, + ...props +}) => { + const router = useRouter(); + + return ( +
+
+ +
+ + + + {tableOfContents.map(({ id, level, text }) => ( + + {text} + + ))} + + ) : undefined + } + > + + + + {posts.map((p) => ( + + {tableOfContents.map(({ id, level, text }) => ( + + {text} + + ))} + + ) : undefined + } + key={p.slug} + > + {p.title} + + ))} + +
+ ); +}; diff --git a/components/page-layout.spec.tsx b/components/page-layout.spec.tsx new file mode 100644 index 00000000..017645fc --- /dev/null +++ b/components/page-layout.spec.tsx @@ -0,0 +1,31 @@ +import { render } from "@testing-library/react"; +import * as React from "react"; +import { TestApp } from "../core/test-app"; +import { + TwoColumnPageLayout, + TwoColumnPageLayoutAside, + TwoColumnPageLayoutFooter, + TwoColumnPageLayoutMain, +} from "./page-layout"; + +describe("", () => { + it("renders children with styles", () => { + const { getByTestId } = render( + + + +
This is main
+
+ + +
This is aside
+
+ + +
+
+ ); + + expect(getByTestId("two-column-page-layout")).toMatchSnapshot(); + }); +}); diff --git a/components/page-layout.stories.tsx b/components/page-layout.stories.tsx new file mode 100644 index 00000000..c558edbb --- /dev/null +++ b/components/page-layout.stories.tsx @@ -0,0 +1,38 @@ +import { Story, Meta } from "@storybook/react"; +import * as React from "react"; +import { + TwoColumnPageLayout, + TwoColumnPageLayoutAside, + TwoColumnPageLayoutFooter, + TwoColumnPageLayoutMain, + TwoColumnPageLayoutProps, +} from "./page-layout"; + +export default { + title: "Components/TwoColumnPageLayout", + component: TwoColumnPageLayout, + subcomponents: { + TwoColumnPageLayoutAside, + TwoColumnPageLayoutFooter, + TwoColumnPageLayoutMain, + }, + argTypes: {}, + args: {}, + parameters: { + layout: "fullscreen", + }, +} as Meta; + +export const TwoColumn: Story = (props) => ( + + +
This is main
+
+ + +
This is aside
+
+ + +
+); diff --git a/components/page-layout.tsx b/components/page-layout.tsx new file mode 100644 index 00000000..fec9bbb0 --- /dev/null +++ b/components/page-layout.tsx @@ -0,0 +1,190 @@ +import { css, cx } from "@linaria/core"; +import * as React from "react"; +import { FormattedMessage } from "react-intl"; +import { ParrotAnchor } from "./anchor"; +import { FloatingSidebarButton } from "./floating-sidebar-button"; + +export interface TwoColumnPageLayoutProps extends React.Attributes { + className?: string; + style?: React.CSSProperties; + children?: React.ReactElement | React.ReactElement[]; +} + +export const TwoColumnPageLayout: React.VFC = ({ + children, + ...props +}) => { + return ( +
+ {children} + +
+
+ ); +}; + +export interface TwoColumnPageLayoutMainProps extends React.Attributes { + className?: string; + style?: React.CSSProperties; +} + +export const TwoColumnPageLayoutMain: React.FC = ({ + className, + children, + ...props +}) => { + return ( +
+ {children} +
+ ); +}; + +export interface TwoColumnPageLayoutAsideProps extends React.Attributes { + className?: string; + style?: React.CSSProperties; +} + +export const TwoColumnPageLayoutAside: React.FC = ({ + className, + children, + ...props +}) => { + return ( + <> + + + + + ); +}; + +export interface TwoColumnPageLayoutFooterProps extends React.Attributes { + className?: string; + style?: React.CSSProperties; +} + +export const TwoColumnPageLayoutFooter: React.VFC = ({ + className, + ...props +}) => { + return ( +
+ + + + +
+ + + +
+
+ ); +}; diff --git a/components/post-list.tsx b/components/post-list.tsx index f591b286..98c0ebca 100644 --- a/components/post-list.tsx +++ b/components/post-list.tsx @@ -40,10 +40,9 @@ export interface PostListItemProps extends React.Attributes { tableOfContents?: React.ReactNode; className?: string; style?: React.CSSProperties; - children?: string; } -export const PostListItem: React.VFC = ({ +export const PostListItem: React.FC = ({ href, as, tableOfContents, diff --git a/jest.config.js b/jest.config.js index 15e2d6ab..6e8d260a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,5 @@ +process.env.TZ = "America/New_York"; + module.exports = { testEnvironment: "node", transform: { diff --git a/pages/_app.tsx b/pages/_app.tsx index 9f7d47db..0c61c0d4 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -3,7 +3,7 @@ import * as Sentry from "@sentry/react"; import { Integrations } from "@sentry/tracing"; import { AppProps } from "next/app"; import { useRouter } from "next/router"; -import { mix, shade, tint } from "polished"; +import { mix, shade, tint, transparentize } from "polished"; import * as React from "react"; import { IntlConfig, IntlProvider } from "react-intl"; import TopLoadingBar from "react-top-loading-bar"; @@ -149,7 +149,8 @@ css` --color-fg-yellow-weak: ${tint(0.5, "#feca57")}; --color-fg-gray: ${tint(0.5, "#8395a7")}; --color-fg-gray-weak: ${tint(0.65, "#8395a7")}; - --color-bg: #ffffff; + --color-bg: #fff; + --color-bg-frosted: ${transparentize(0.15, "#fff")}; --color-bg-input: ${tint(0.85, "#8395a7")}; --color-bg-input-active: ${tint(0.8, "#8395a7")}; --color-bg-red-weak: ${tint(0.75, "#ff6b6b")}; @@ -176,9 +177,6 @@ css` @media screen and (prefers-color-scheme: dark) { html { - --color-bg: #000; - --color-bg-input: ${shade(0.85, "#8395a7")}; - --color-bg-input-active: ${shade(0.8, "#8395a7")}; --color-fg: ${mix(0.666, "#fff", "#8395a7")}; --color-fg-strong: #fff; --color-fg-red-weak: ${shade(0.5, "#ff6b6b")}; @@ -186,6 +184,10 @@ css` --color-fg-yellow-weak: ${shade(0.5, "#feca57")}; --color-fg-gray: ${shade(0.5, "#8395a7")}; --color-fg-gray-weak: ${shade(0.65, "#8395a7")}; + --color-bg: #000; + --color-bg-frosted: ${transparentize(0.15, "#000")}; + --color-bg-input: ${shade(0.85, "#8395a7")}; + --color-bg-input-active: ${shade(0.8, "#8395a7")}; --color-bg-red-weak: ${shade(0.75, "#ff6b6b")}; --color-bg-blue-weak: ${shade(0.75, "#54a0ff")}; --color-bg-yellow-weak: ${shade(0.75, "#feca57")}; diff --git a/pages/index.tsx b/pages/index.tsx index a8bf023c..13385e96 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -4,13 +4,14 @@ import Head from "next/head"; import * as React from "react"; import { useIntl } from "react-intl"; import { PromiseValue } from "type-fest"; -import { LocaleSwitcher } from "../components/locale-switcher"; -import { Markdown } from "../components/markdown-article"; -import { PostList, PostListItem } from "../components/post-list"; +import { Article } from "../components/article"; +import { AsideNavigation } from "../components/aside-navigation"; import { - TableOfContents, - TableOfContentsItem, -} from "../components/table-of-contents"; + TwoColumnPageLayout, + TwoColumnPageLayoutAside, + TwoColumnPageLayoutFooter, + TwoColumnPageLayoutMain, +} from "../components/page-layout"; import { WEBSITE_NAME } from "../constants/app"; import { CACHE_HEADER_VALUE } from "../constants/cache"; import { AVAILABLE_LOCALES } from "../constants/locale"; @@ -65,209 +66,26 @@ const Page: NextPage = (props) => { -
-
-
-
- {title} - -

- {title} -

- - -
-
- -
+ +
img { + object-position: top right; } `} /> + - -
- -
+ + + -
- {intl.formatMessage({ - defaultMessage: "kohei.dev is open source project.", - })} -
-
+ +
); }; diff --git a/pages/posts/[slug].tsx b/pages/posts/[slug].tsx index 7fd2ba10..314ac330 100644 --- a/pages/posts/[slug].tsx +++ b/pages/posts/[slug].tsx @@ -1,18 +1,16 @@ -import { css } from "@linaria/core"; import { GetServerSideProps, NextPage } from "next"; import Head from "next/head"; import * as React from "react"; import { useIntl } from "react-intl"; import { PromiseValue } from "type-fest"; -import { Badge } from "../../components/badge"; -import { HorizontalList } from "../../components/layout"; -import { LocaleSwitcher } from "../../components/locale-switcher"; -import { Markdown } from "../../components/markdown-article"; -import { PostList, PostListItem } from "../../components/post-list"; +import { Article } from "../../components/article"; +import { AsideNavigation } from "../../components/aside-navigation"; import { - TableOfContents, - TableOfContentsItem, -} from "../../components/table-of-contents"; + TwoColumnPageLayout, + TwoColumnPageLayoutAside, + TwoColumnPageLayoutFooter, + TwoColumnPageLayoutMain, +} from "../../components/page-layout"; import { WEBSITE_NAME } from "../../constants/app"; import { CACHE_HEADER_VALUE } from "../../constants/cache"; import { AVAILABLE_LOCALES } from "../../constants/locale"; @@ -106,279 +104,24 @@ const Page: NextPage = (props) => { -
-
-
-
- {coverImageUrl ? ( - {title} - ) : null} - -

- {title} -

- - - - -
-
- -
+ +
+ - -
- -
+ + + -
- {intl.formatMessage({ - defaultMessage: "this webpage is part of an open source project.", - })} -
-
+ + ); };