Skip to content

Repository files navigation

Koyori UI

Koyori のプロジェクトで共有する UI。Mitosis の共通ソースから Vue・React 向けのコンポーネントを生成します。

開発

Node.js 24 と pnpm 12.4.1 を使います。

pnpm install --frozen-lockfile
pnpm storybook:vue    # http://localhost:6006
pnpm storybook:react  # http://localhost:6007(別ターミナル)
pnpm typecheck
pnpm build
pnpm build-storybook
パッケージ ディレクトリ Storybook 出力
@koyori-app/ui-vue packages/vue/src packages/vue/storybook-static
@koyori-app/ui-react packages/react/src packages/react/storybook-static

共通の実装とスタイルは components/<コンポーネント名>/ にまとめます。 例えば Picker は components/Picker/Picker.lite.tsxcomponents/Picker/picker.module.css に置きます。 複数の部品が使うスタイル・計算は components/shared/ に置きます。 アイコンも同じ構成にし、共通の型宣言 css.d.tscomponents/ 直下に置きます。 変更後に pnpm generate を実行すると、packages/{vue,react}/src/generated が更新されます。 Storybook 起動中もこのコマンドで変更を反映できます。生成物は直接編集・コミットしません。 Story は各パッケージの src/*.stories.* に追加します。

角丸・文字サイズ・行高・余白・最小高さ・色・影・フォーカス・モーション・メニュー配置の共通トークンは components/tokens.css で管理します。 各コンポーネントが参照し、生成先と配布用の style.css にも含まれます。 Button・Dropdown・Picker・Accordion・SidebarLink は sm(14px 相当)の文字と 36px の最小高さ、操作要素の角丸は md(8px)を使います。 配色はくすんだラベンダーとセージを使い、背景に色を持たせながら文字は濃く保ちます。 主ボタン・ホバー・選択背景はラベンダーの濃淡で揃え、選択時はホバーより少し濃くします。 補助ボタンはセージ、選択チェックは丸背景のない濃い紫です。 選択背景とチェックの色は --koyori-color-selection--koyori-color-selection-indicator で調整できます。 カードなど角丸のある要素を入れ子にする場合は、外側の角丸を 内側の角丸 + 外側の padding にします。 Dropdown のパネルは calc(var(--koyori-radius-md) + var(--koyori-space-xs))(既定値: 8px + 4px = 12px)です。 計算は外側の要素で行い、その範囲で内側の角丸や余白を上書きした場合も追従させます。 文字サイズは rem、行高は倍率なので、利用側のルート文字サイズに追従します。 共通値はここで変更し、利用側で調整するときは style.css の読み込み後に上書きします。 :root なら全体、親要素のクラスならその範囲だけに適用できます。

.settings-panel {
  --koyori-radius-md: 6px;
  --koyori-font-size-sm: 1rem;
}

Button は variant="primary" | "secondary" | "tertiary" | "ghost" | "danger" で切り替えます(既定値: primary)。 danger は削除など取り消せない操作に使い、ラベルにも結果が分かる言葉を入れてください。 各種類の通常・無効状態の Story と、Docs・アクセシビリティ確認用のアドオンを用意しています。 共通 CSS で hover 時の色の遷移と押下時の縮小を実装し、prefers-reduced-motion に対応しています。 押下時は内側の背景・ラベルだけを縮め、外側の寸法・クリック領域・フォーカス枠を維持します。 アイコンは React では icon={<EllipsisIcon size={16} />}、Vue では #icon スロットで渡し、ラベルの前に表示します。 label を省略するとアイコンだけの正方形のボタンになります。その場合は ariaLabel(Vue も ariaLabel)で名前を付けてください。 label がなければ ariaLabel を必須にする型を React・Vue の両方で公開します。空文字は避けてください。 UI の開発では アクセシビリティの指針 を参照してください。

ButtonGroup は子の Button をつなげ、隣り合う枠線を1本に重ねて外側の角だけを丸めます。 labelrole="group"aria-label に使います。グループ内では押下時の縮小を止めます。

Accordion は idlabel と本文を受け取る開閉セクションです。id はページ内で一意にします。 defaultOpen は初期表示、openonOpenChange は外側からの制御に使います。省略時は各セクションが独立して開閉します。 同時にひとつだけ開く例は Storybook の SingleOpen、複数を開く例は MultipleOpen にあります。 headingLevel(既定 3)で見出し階層、disabled で操作の無効化、icon(Vue は #icon)で先頭アイコンを指定できます。 Enter / Space で開閉し、閉じた内容は Tab 移動・読み上げから除きます。本文の DOM と入力内容は保持します。

Sidebar は label をナビゲーションの名前にし、headerfooter(Vue は同名スロット)と本文を縦に並べます。 SidebarLink の labelhref でページ移動、current で現在地、badge で補足、disabled で移動の無効化を指定します。 アイコンは icon(Vue は #icon)で渡します。通常のリンクなので新しいタブでも開け、現在地はアプリの URL から渡します。 本文に Accordion を置くとグループを開閉できます。幅は --koyori-sidebar-width(240px)、高さは親要素で指定します。 open(と id)を渡すと開閉でき、閉じると幅(placementtopbottom なら高さ、既定 --koyori-sidebar-height)を 0 まで畳んで隣の領域を広げます。 開閉ボタンは Button の ariaExpandedariaControls で状態と対象を伝え、閉じるときに中にあったフォーカスはそのボタンへ戻します。 rail を渡すと --koyori-sidebar-rail-width(64px)まで縮み、アイコンだけの表示になります。ラベルと badge は見た目だけ隠し、読み上げ名は保ちます。 open と組み合わせると、展開・アイコンだけ・完全に閉じるの 3 状態になります。レールで使うリンクには icon を渡してください。 onRailChange を渡すと端から張り出すつまみが出て、押すと rail を反転します。状態は aria-pressed、名前は railLabel です。 項目が多い場合はナビゲーション部分だけがスクロールします。Accordion 本文の余白は --koyori-accordion-padding で変更できます。

Avatar は利用者を表す円形の画像です。namesrcsize(既定: 32px)を受け取ります。 src がない場合と読み込みに失敗した場合はイニシャルを表示し、読み上げは常に name になります。 イニシャルは空白区切りの語の先頭を2つまで使います(「山田 太郎」→「山太」、「山田太郎」→「山」)。 直径と文字サイズの計算・イニシャルの算出は components/Avatar/avatar.ts にまとめ、AvatarGroup と共有します。

AvatarGroup は items: { name, src? }[] を少し重ねて並べ、max を超えた分を +N にまとめます。 labelrole="group"aria-labelformatOverflow(count)+N の読み上げ文(既定: 他 N 人)に使います。 重なりは 8px、隣との境界は --koyori-color-surface の縁取りで作るため、背景の異なる場所ではこの値を合わせてください。

Dialog はネイティブの dialog 要素と showModal() を使うモーダルです。opentitledescriptiononClose を受け取り、 本文は既定スロット、操作ボタンは actions(Vue は #actions スロット)に置きます。表示状態はアプリ側で持ち、onClose で false に戻します。 フォーカスの閉じ込め・背景の操作停止・Escape・フォーカス復帰はブラウザーに任せ、open 属性はバインドしません(非モーダルになるため)。 背景クリックは pointerdown の対象が dialog 自身のときだけ閉じ、開いている間は body:has(dialog:modal) で背景のスクロールを止めます。 幅は --koyori-dialog-width、覆いの色は --koyori-color-backdrop、角丸は内側のボタンの角丸 + --koyori-space-xl です。 中身が自前でレイアウトを持つ場合は plain を使います。見出しと説明は読み上げにだけ残し、内側の余白を外して本文が全面に広がります。 幅と高さ(--koyori-dialog-height、既定 auto)はダイアログを囲む要素で指定します。2 列の組み方は Web ドキュメントのブロックに載せています。

Drawer は画面端から滑り出るモーダルで、ハンバーガーボタン(Button + MenuIcon)で開閉するナビゲーションなどに使います。 openlabelplacementleft 既定、righttopbottom)・onClose を受け取り、開閉は Dialog と同じ syncDialog で同期します。 画面端に付き、内側の角だけが丸くなります。開閉とも弾まないスライドで、閉じるアニメーションは transition-behavior: allow-discrete 対応ブラウザーだけで動きます。 幅は --koyori-drawer-width(280px)、上下の高さは --koyori-drawer-height(60vh)です。中の Sidebar が rail のときは --koyori-sidebar-rail-width に縮みます。 modalfalse を渡すと dialog を使わず中身をその場所に描くため、広い画面では常時表示、狭い画面ではモーダル、という出し分けを同じ記述でできます。中の Sidebar は --koyori-sidebar-border--koyori-sidebar-radius で枠と角丸を外します。

ConfirmDialog は Dialog と Button を組み合わせた二択の確認です。titlemessageconfirmLabel は必須で、 destructive で実行ボタンを danger にします。キャンセルを先頭に置くため、初期フォーカスは常に取り消し側です。

Field は idlabeldescriptionerrorrequired を受け取り、中の Input(1行)・Textarea(複数行)・Picker(選択)に渡します。Field 内には対象を1つだけ置きます。 id は入力欄に付き、ラベルの for と説明・エラーの aria-describedby に使います。エラーがあると aria-invalid="true" を付けます。 エラー領域は常設の aria-live="polite" で、後から出たエラーも通知します。requiredText で「必須」の表示を差し替えられます。 Picker ではトリガーに Field の id が付き、requiredText を名前に含めて閉じた状態でも必須を伝えます。 説明・エラーの aria-describedby はトリガーにだけ付けます。一覧は aria-requiredaria-invalid で状態を伝えます。必須チェックと送信値の管理はアプリ側で行います。 Input・Textarea の値は valueonValueChange(value) で扱い、入力のたびに呼ばれます。Field の外で使う場合は ariaLabel で名前を付けます。 入力欄の枠線は --koyori-color-border-strong(背景比 3:1 以上)、エラーは --koyori-color-danger(4.5:1 以上)を使います。

Dropdown はアクションメニューです。labelitems: { value, label, disabled? }[] を渡し、 項目を実行すると閉じて onSelect(value) を呼び出します。選択状態は持ちません。value は一覧内で一意にしてください。 disabled で全体を無効化し、defaultOpen で初期表示を開けます。emptyMessage で空表示を変更できます。 トリガーは aria-haspopup="menu"aria-controls、一覧は menu、項目は menuitem を使います。 Enter・Space・↓ は先頭、↑ は最後へフォーカスを移して開きます。上下キー・Home・End・先頭文字で移動できます。 無効な項目も矢印キーでフォーカスできますが、実行はできません。 Tab / Shift + Tab は閉じて次・前の操作へ移動し、Escape は閉じてトリガーへ戻ります。外側クリックでも閉じます。 Storybook の Actions で実行通知と Tab 移動を確認できます。

Picker は単一・複数選択用です。labelitems: { value, label, disabled? }[] を渡します。 検索は既定で有効です。searchable={false}(Vue: :searchable="false")で検索欄を省略できます。 単一選択は選ぶと閉じ、selectionMode="multiple" は選択・解除しても開いたままです。 avatars を付けると、選択肢の先頭に 24px の Avatar を出します。画像は itemssrc、なければイニシャルです。 行の上下の余白を詰めて高さは 36px のままにし、アバターは装飾として読み上げから外します。選択肢の名前は label のままです。 トリガーの表示は trigger(React の要素、Vue は #trigger スロット)で差し替えられます。 アバターなどを置いても、開閉・キーボード操作・読み上げ名(label と選択中のラベル)はそのままです。 中身は button の内容として正しい要素にしてください(AvatarGroup のルートは span です)。 選んだラベルはトリガーとアクセシブルネームに反映します。label は選ぶ対象の名前として固定してください。 selectedValues: string[]onSelectionChange(values) で外側から状態を管理できます。 省略すると内部で管理し、初期値は defaultSelectedValues です。 存在しない値は表示から除き、単一選択では項目順で先頭の1件を使います。単一選択の解除は外側から空配列を渡します。 検索で見えなくなった項目の選択も保持します。検索はラベルの部分一致で、大文字・小文字と全角・半角を揃えます。 開き直すと検索文字列をクリアします。searchPlaceholdersearchLabelemptyMessage で文言を差し替えられます。 候補数の通知は入力が300ms止まってから更新します。候補と空表示はすぐ更新し、閉じると保留中の通知を取り消します。 formatResultsCount(count) は候補数の通知文、selectionSeparator は複数の選択ラベルの区切り(既定: )です。 一覧は listbox で、上下キー・Home・End・先頭文字で移動し、Enter / Space で選択、Escape で閉じます。 検索ありなら開くと検索欄にフォーカスし、検索なしなら現在の選択項目(なければ先頭の有効な項目)に移ります。 チェックと背景はアニメーションし、隣の選択済み項目から背景が伸びてつながります。 Storybook の WithoutSearch・MultipleWithoutSearch・ControlledWithoutSearch・AdjacentSelection で検索なしの選択を確認できます。

候補の選択・隣接状態は get view() にまとめています。Vue は computed として共有し、React は参照ごとに再計算します。 各計算は候補数に比例し、1,001 件の Story で検索・選択を確認できます。 全候補の DOM を描画するため、数万件のデータはアプリ側で検索・分割してください。

Button と両トリガーは components/shared/control.module.css を共有し、通常のトリガーには ghost の見た目を使います。 Field 内の Picker は Input と同じ枠線・背景・エラー色を持ち、Field の幅に揃います。パネル幅は独立しています。 フォーカスは共通の --koyori-focus-*、無効背景は --koyori-color-surface-disabled、 動きは --koyori-motion-fast/base/slow--koyori-ease-out/pop を使います。 一覧と検索欄のフォーカス枠だけは、スクロール領域で切れないよう内側に置きます。

Dropdown と Picker のパネル・項目は components/shared/menu.module.css、 配置・検索文字の正規化・外側クリック等のリスナーは components/shared/menu.ts にまとめています。 Dropdown・Picker・Sidebar は components/shared/highlight.tshighlight.module.css を共有し、マウスの入った位置から背景が広がって項目間を移動します。ホバーできない端末(スマホなど)では、タップの瞬間に動いて見えないよう、背景を動かさずその場に表示します。Sidebar 内の Accordion の見出しと入れ子のリンクも同じ動きになり、現在地の色は保ちます。 --koyori-menu-width(200px)・--koyori-menu-max-height(360px)・--koyori-menu-gap(8px)・ --koyori-menu-viewport-margin(8px)・--koyori-z-menu(10)で配置を調整できます。 配置計算に使う最大高・間隔・画面端の余白は、ブラウザーで長さを解決するため remcalc() でも指定できます。長さの計測は開くときだけ行い、スクロール・リサイズ時は再利用します。開いている間にこれらの値を変更した場合は、開き直すと反映されます。 文字入力の連結待ちは MENU_TYPEAHEAD_TIMEOUT(500ms)に集約しています。 メニュー幅は親から独立し、狭い画面では縮小して長い項目を折り返します。 下に収まらず上の空きが広い場合は上に開き、両側とも足りなければ高さを制限して一覧をスクロールします。 開いている間のリサイズ・スクロールにも追従します。親要素の overflow: hidden 等による切り取りは回避しません。 アイコンは回転しません。未指定では矢印、icon={null}(Vue: :icon="null")で非表示です。 React では icon={<EllipsisIcon />}、Vue では <template #icon><EllipsisIcon /></template> で差し替えます。 マウスが入った位置から背景が広がり、その後は項目間を移動します。動きを減らす設定ではアニメーションを停止します。 背景色の上書きは引き続き --dropdown-hover-background、Picker の選択背景は --dropdown-selected-background を使えます。

以前 Dropdown の選択モードを使っていた箇所は、コンポーネントを Picker に置き換えて searchable={false} を指定します。 selectionModeselectedValuesdefaultSelectedValuesonSelectionChange は Picker に移しました。 onSelect は Dropdown の操作実行専用です。選択値の変更通知には Picker の onSelectionChange を使ってください。 単一選択は「選択すると閉じる」動作に統一し、トリガーの表示文言は Picker が更新します。 ブラウザー検証は Storybook のビルド後に node scripts/check-picker.cjs で実行します(Dropdown と同じ実行環境)。

アイコンは Lucide の必要な SVG を Mitosis の共通コンポーネントとして取り込みます。 ChevronDownIconEllipsisIconCheckIconXIconMenuIcon を公開し、Dropdown の既定の矢印には ChevronDownIcon を使っています。 XIcon はダイアログの閉じるボタンなどに使います。MenuIcon は Drawer を開くボタンに使います。アイコンだけのボタンには ariaLabel で名前を付けてください。 size で縦横のサイズを指定できます(既定値: 16px)。色は親の color を引き継ぎます。 装飾用として読み上げから除外するため、アイコンだけのボタンにはボタン側で aria-label を付けてください。 アイコンを追加するときは出典・コミット・ライセンスを THIRD_PARTY_NOTICES.md に記録します。

ビルドは ESM・型定義・CSS を dist に出力します。 利用側ではコンポーネントと @koyori-app/ui-vue/style.css または @koyori-app/ui-react/style.css を import します。共通トークンはバンドル時にまとめられ、配布 CSS 内では1回だけ定義されます。 pnpm --filter @koyori-app/ui-vue pack(React は ui-react)で、ライセンスを含む配布物を生成できます。

ブラウザー検証は Storybook のビルド後に node scripts/check-dropdown.cjs で実行します。 node scripts/check-picker-logic.cjs はブラウザーなしで選択計算を検証します。 node scripts/check-sidebar.cjs は、ブラウザーなしで Sidebar の開閉・フォーカスの戻し先・Button の開閉用属性・配布 CSS の規則を検証します(pnpm build のあとに実行)。 node scripts/check-dialog.cjs は、ブラウザーなしで Dialog の開閉同期・生成物の構造・配布 CSS の規則を検証します(pnpm build のあとに実行)。 node scripts/check-components.cjs は Field 連携・文言・共通スタイル・大量候補を検証します。 node scripts/check-navigation.cjs は Accordion の開閉・キーボード操作・入力保持、Sidebar のリンク・現在地・スクロールを両フレームワークのブラウザーで検証します。 Playwright と Chromium、日本語フォント、および Python 3 が必要です。別の場所にある Playwright を使う場合は PLAYWRIGHT_MODULE にモジュールのパスを指定してください。

Calendar

Calendar は月のカレンダーをページ内に表示し、1 日を選びます。値は 'YYYY-MM-DD' の文字列で、 valuedefaultValueonValueChange で扱います。minmaxisDateDisabled で選べる日を絞り、 localefirstDayOfWeek で表示を合わせます。キーボード操作は W3C の Date Picker の例に合わせています。 使い方とプレビューを参照してください。 日付の計算は components/Calendar/calendar.ts にまとめ、node scripts/check-calendar.cjs でブラウザーなしに検証します(pnpm build のあとに実行)。

Checkbox

Checkboxlabel を必須にし、hideLabel で読み上げ名を保ったまま表示ラベルを隠せます。 checkedonCheckedChange で状態を管理でき、省略時は標準の入力要素に任せます。 チェック時は SVG の線を描画し、外枠や操作領域のサイズは変えません。動きを減らす設定にも対応します。 使い方とプレビューを参照してください。 Storybook のビルド後に node scripts/check-checkbox.cjs で操作と描画を検証できます。

DataList

DataListDataListRow で、列の揃った一覧を組み立てます。各 DataList が1グループになり、 見出し・件数・折り畳み、行の選択表示、空・読み込み中・エラー・再試行を扱えます。 セルは通常の td / th scope="row" で渡し、Button・Avatar・Picker などを組み合わせます。 データ取得・更新・ソート・グループ分けは利用側で管理します。 DataList 内の Dropdown・Picker は Popover API 対応ブラウザーで一覧の枠外にも表示できます。 仕様と実行できる Vue/React の例は DataList のページ にあります。 Storybook のビルド後に node scripts/check-data-list.cjs で操作とアクセシビリティを検証できます。

ProgressBar は達成率や進捗を横棒で示す表示専用の部品です。labelvaluemax(既定 100。0 以下や非有限な値は 100 扱い)を受け取り、 valueText で「3 / 5 件」のような表示と読み上げに差し替えられます。hideLabelhideValue で表示を省けます。 role="progressbar"aria-valuenowaria-valuemaxaria-valuetext で値を伝え、値の変化は読み上げません。 高さは --koyori-progress-height(8px)、色は棒の要素で --progress-fill--progress-track を上書きします。 満たしたときは data-complete="true" が付きます。値の計算は components/ProgressBar/progress.ts にまとめ、 node scripts/check-progress-bar.cjs でブラウザーなしに検証します(pnpm build のあとに実行)。

ContextMenu は右クリック(キーボードは Shift+F10・メニューキー)で開く操作メニューです。openxylabelitemsonSelectonClose を受け取り、 座標と開閉はアプリが持ちます。座標は contextMenuPosition(event) で取得し、キーボード起動のときは対象の左下を返します。 配置・外側クリック・トップレイヤーは Dropdown と同じ components/shared/menu.ts を使い、矢印キーと先頭文字の移動は nextMenuIndextypeaheadTarget として共有します。項目の destructive は danger の色になります(ラベルにも結果を書くこと)。 項目に items を渡すと 1 階層だけサブメニューを持てます。サブメニューは右に入らなければ左へ反転し、下にはみ出せば上へずれます。 右クリックできない利用者(スマホなど)のために、三点ボタンからも同じ ContextMenu を開いてください。menuButtonPosition(event) がボタンの左下の座標を返し、 ボタンの ariaControls と ContextMenu の id を揃えると、開いている間のボタンの押下で閉じます。Button には ariaHasPopup を足しました。node scripts/check-context-menu.cjs で ブラウザーなしに移動・座標・生成物・配布 CSS を検証します(pnpm build のあとに実行)。

Web ドキュメント

Astro + Starlight のサイトを apps/docs に置いています。 導入手順・Accordion・Avatar・Button・ButtonGroup・Calendar・Dialog・Drawer・Dropdown・Picker・ProgressBar・Sidebar・Field のページに、Vue/React のデモ・コピーできるコード・API・キーボード操作を掲載します。 コード例は実行するデモのソースから読み込みます。サイト内検索は本番ビルドで有効になります。 複数のコンポーネントを組み合わせた例は「ブロック」にまとめ、src/content/docs/blocks に置きます。 現在は「担当者の選択」(Picker で選んだ人を AvatarGroup で表示)があります。

pnpm dev:docs          # http://localhost:4321
pnpm build:docs    # パッケージとサイトをビルド → apps/docs/dist
pnpm preview:docs  # ビルド済みサイトをローカルで確認

ページは apps/docs/src/content/docs、デモは apps/docs/src/examples で編集します。 デモはパッケージの公開エントリーと CSS を使います。共通コンポーネントを変更したら pnpm dev:docs を起動し直すと、生成・パッケージビルドも反映されます。 pnpm typecheck は型定義を含むパッケージをビルドしてから、ドキュメントと Vue/React の例も検査します。

Docs CI は PR・main への push・手動実行でビルドと型検査を行います。 公開先へのデプロイは未設定です。静的ホスティングではリポジトリのルートから pnpm build:docs を実行し、apps/docs/dist を出力ディレクトリに指定します。

VRT CI

ワークフロー は PR・main への push・手動実行で、 共通ソースから Vue と React を生成してそれぞれ型検査・ビルドし、Storybook を Koyori VRT に送信します。 全 Story を撮影し、差分・撮影失敗は各フレームワークのジョブに反映します。 Fork・Dependabot の PR はビルドまで実行し、VRT への送信は省略します。

実行前に GitHub リポジトリの Settings → Secrets and variables → Actions に以下を設定します。

種類 名前
Secret VRT_TOKEN 両プロジェクトにアクセスできる PAT。write:buildread:build スコープが必要
Variable VRT_PROJECT_VUE Vue 用の tenant-slug/project-slug(例: koyori/ui-vue
Variable VRT_PROJECT_REACT React 用の tenant-slug/project-slug(例: koyori/ui-react

VRT 側で 2 つのプロジェクトを作成し、比較基準のブランチを main に設定してください。 同じプロジェクトを指定すると比較基準が混ざるため、CI で拒否します。 初回は main のビルドを実行し、VRT 上で結果を確認・承認して比較基準を用意します。 必要な設定が欠けている場合も、送信前に設定エラーとして終了します。

ライセンス

独自コードは MIT License で提供します。著作権者は yupix と sousuke0422 です。 task・vrt から移すコードも、両者が権利を持ち、MIT での提供に合意した部分を対象とします。

第三者由来のコードには、そのコードの元のライセンスが適用されます。 コピーや Vue への移植を行う場合も、元の著作権表示とライセンス文を保持し、 出典・対象ファイル・取り込み元コミットを THIRD_PARTY_NOTICES.md に記録します。

npm パッケージなどを配布する際は、各配布物に LICENSE と、含まれる第三者コードに対応する 著作権表示・ライセンス文を同梱します。フォント・アイコンなどの素材も、それぞれのライセンスに従います。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages