From 4296ddbdcc9c708603d2ab739c1c4be992990e6c Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 9 Dec 2024 14:45:23 +0100 Subject: [PATCH] Add breadcrumb component --- ...Breadcrumb-Controlled-1-chromium-linux.png | Bin 0 -> 11330 bytes .../Breadcrumb-Default-1-chromium-linux.png | Bin 0 -> 7697 bytes .../Breadcrumb/Breadcrumb.module.css | 65 +++++++++ .../Breadcrumb/Breadcrumb.stories.tsx | 81 +++++++++++ src/components/Breadcrumb/Breadcrumb.test.tsx | 63 +++++++++ src/components/Breadcrumb/Breadcrumb.tsx | 126 ++++++++++++++++++ .../__snapshots__/Breadcrumb.test.tsx.snap | 68 ++++++++++ src/components/Breadcrumb/index.ts | 17 +++ src/index.ts | 1 + 9 files changed, 421 insertions(+) create mode 100644 playwright/visual.test.ts-snapshots/Breadcrumb-Controlled-1-chromium-linux.png create mode 100644 playwright/visual.test.ts-snapshots/Breadcrumb-Default-1-chromium-linux.png create mode 100644 src/components/Breadcrumb/Breadcrumb.module.css create mode 100644 src/components/Breadcrumb/Breadcrumb.stories.tsx create mode 100644 src/components/Breadcrumb/Breadcrumb.test.tsx create mode 100644 src/components/Breadcrumb/Breadcrumb.tsx create mode 100644 src/components/Breadcrumb/__snapshots__/Breadcrumb.test.tsx.snap create mode 100644 src/components/Breadcrumb/index.ts diff --git a/playwright/visual.test.ts-snapshots/Breadcrumb-Controlled-1-chromium-linux.png b/playwright/visual.test.ts-snapshots/Breadcrumb-Controlled-1-chromium-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..3bd0b523d0e9a15a708d2471bfdd4800016d2783 GIT binary patch literal 11330 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 TK7ua=L*Tb9|55gLx0wG1dZg-m literal 0 HcmV?d00001 diff --git a/playwright/visual.test.ts-snapshots/Breadcrumb-Default-1-chromium-linux.png b/playwright/visual.test.ts-snapshots/Breadcrumb-Default-1-chromium-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..7f7320bc6ced3b7f0c685c8f907fbe871c7aa3da GIT binary patch literal 7697 zcmeHM`BPKpyN|_|Rt4H?DOC`@9YJnEpo)MHAX=(4rGf&2EFn^XAb|wgwC*9;5&2XymMyGyFBmnS)Oz5 zU-fj?IduFG2n5oB{`iA82=vjLf3HtJ0-o60y%_{<2jacmzXy>=jKm<&i9zTO7kyKn ztk0%CnMh>FHaF^zfbV8M?J%4FBjUGD|FpEKx^_Hbp%uj`>h$;V`QgpOmqV4rki%zu z;_nAv2sr2vJUV{izuaQo!mbs>l!Qi{=5S^{+SFXvFaBsgJclNnbax$j>HP3rzca zvq_J+G(Xlk(EI7pfpiY&S@X4imdu}j3C=aQZ&to&5dsL z!71xNBO_o7!=04Mnw0L${NX36uEB{3JDpr;^-*odHp(S5t35q`Rz2M;lZN6xiZ-y) z&EwJ{VB&zlwmSnm=5-_zGfw%JQrUU=@}wO)GbnA3Dl+Ha5#h~D&f$tkq?JeHEj=DL zsgYTeg1NS7OqWK64b4!u$(o*AhnbXZLmceQvs@cE^OyM($$q}R<2H?uLQ`n5m$YUPITPMKqXPb7#M!&@L_n$Tn|UYhSQT^ZaiqE_h2#Q1`CT(;dKmu%FfPK zt3ybn$@+jGfB*K0S3wvX0xfR{m-u@4RXW&i96_udJ*L3=Eig zxYj6G29x9Lm;L^Nw|fY@=@`P*r?X>e_N)5$NRZ^^4DMaTTEU86<~a}_MR%5XyiMzc)l zN4AXy}cFpusXcciTIgfh2Gan&d2D8UZvQJvIG9*{a?2{&nt7ZLWmGnK{5Y;6aj(^IR2l%;!(LbPJ~3X7Je9PTmv32#~&E5cM(hU-BT zvJI@}b;KAwdM&XTE*qQLbeN4@{pSpa|8l-N-*%9gs;>0td%z0sf;}gz<+p4JL#LHD zr?cO3f^ouzqkZa~s%f@HoJ~|ZH+NR_gIdP-G@o$PS<>}U>KMM#@~hKn2s+yN)&^rQ zYU~tCJ@?oS+4n%zziep@S4z6}C5+X@Vz`yHHFl0#oxI9dfvbsav9I85Gv5O_k1NNh zzsWM%;e*j5sk@cQg7ghiU7s2BtTL$D_xg1g)zI3{fmh1ZvTp@Ok4*dhv>g@-`hASc zmG8Pf#yPINbkm5Q<#>2_a42v{4EKShJDi!&57tx9sk2N7${pM0!NEcIkl48c+B*U7 zdy&n?L?^MHo`Y-qsfIv?Jrs|EB}YYG2npf8spw1IU?TCR;o;!`k%(VV%5P}a>I1x7 zofa!ZL<|W=Z*!Q5jmMkn>+7Gi>3ILQmg01Zu=UuOarvgK{*SiKTM~BSy-WJ~MGQzg zyXvr>(^KiMSRy)3c>VfByLz7cMR!)r-giYm&0;w!cbyencZVg~2vSuO+1*m=#i6uD ztkcSR^Qm`~C3o-Ivsp6W5T9EcQ{CCdq72+WIO4v_3clEo{FP>&`_>`XBwiM(Es)b8 zFUG_J10;a&rO!>lzTM&sv>{8q0S9cw>^^VJ4cNfwU z#G`F7yrgwrD_X#W(UXG6XAvVvOMkMFy1~akLl$~FK>YNg7Q)%p7K$ghq#GxWKkcjX z^ujN+a@0+6ivwdUm(Ge;I`Vwm%WEv$hBb^9jF~7hGpn>=ZAEd&uxhWet=TWC0bzMo zQ*++Za#fh`uTCAAnV+Ap>&ugHTUTB8*D!o4!VWk?tBn0wSB5mE_wN@ZlA=ETas&dA zf5MiAohwBU8Z~N0x|F|xG{+`RZ!JRcN?@30&z_y}&szb8VeU(|8Md0|6*`iV{bmx) z$s9~i6dXFY)A4-EUcR)T$Qty^Xo-jkWp(RW1;c#<1CwN-S<1y+ht;eT;INy1etu<2 zWw*k(B?&fGos|bpyBZRL^7EVg)*P2OOzX<`BU}7;njYE)uKh(xj5 z<57&c;pd0ly6;wK+RtW#CCe8}Ck%;4F66Yx2Irfbn^#sIpZ{!>3FFOmGZpX01DfF} zj==IZS1*^HE;*Gbo{{JAaJw25WBmn%ILgHAe{qtDnJ9TXJ)PU?yY%CJ&D&dX@$t5- zgBxu@Qa~E+-o1+^+AHTh{uLX7Q;gy_?H$L3zcS6eunBdUhFqD1E*(;avR%^6px!q+ zm_RTG&iG#`x5rdlgLlQvI8RSci=q&C2X^dL`dVXpVLUPe(n*(PcI&xx^v_=R@rmcZ zKJ0p9>(z4~KsI-d_YXe&6Jc0~BxUl5PrZT5f0upj2YoAB1MiF3cP+@T@jqv0BuhW8450 zSBtuG1Svv_k8$cte~Qa-lg>dh-4wd@YBU^vUbzj$pNs2W$)iHRiED3=Qe%oTm46@Z zO2*DH3Qr*r2%@~)$My`&XJ4H4=3!Ax^vG(Gc&65EKS|gdOk4kkVA@aX+NDcptf2Vp zfdRk|jg*6}gLg(c<%h}Ct$FKmuo*N?J^}H-hgFw5QEV*8H8s(^w*Nr!Ma25Nk|QrO zk(-dc)YM^@UuRl9iqSgz1Do!8(aMr|EGp+Y!Zrukisk@@n6V<>Tj%X^bY^A9#)3gNZV>O+SP{ee-v;9)W8p>ql! zuVa(4cMY^vNjjXU=^ZGt#KjGJ(Gw)jk#7nnCMEzE=ty;3TwG)kzH6+iIt9g_R7iP< z4TgK}aa+JN2)xo>s&ywGF@t*n!;>^y>%n2EF4M1z6BKw48=~WCzlSxm=4Gr3Fttk~ z<($_Q_YS!42vS8TAy(0>2T?8$I~edW?2Y3_R5uCR6#55l`$ZoyZBK3hZ>X=gpwy(y zml`ZFnD3^((XwUlEi+ehY}kMWwDfNvIIseUX-(`92Dcq8U9hzQNZQxJPCQpThHx2^ zS;TM`itVr3HWRP_)SSvay$DWqBGhd1Mq|*Gm>3iab+HW4rlC>?R!4^^6wgRo=Ogn8 z$^8ASe1JA2^JZQn*ouk51CjqGxdVWcx6S%xTxt-9sRLginnIJ3_S#J5mS@vh+ibAt zu7(*&!R%?~J~k*%mQ42h%IVCK=&kZ#< ziQoCF0>4rXnS%$_Zd^;D=B+9nzAbR5)M`M8YD>_Obj6RKUFH}dwwjNm*mR`qbmUOJmd^DNkGM9$g>^?_N@L%84E}@5o>^$4Xy{W*^dAR+*hpGwS#HsrYgd-Q+ zngTb^d=1o#yw#i#UTaJthlG^0`2vMN#PH*CT9TEFcjbQRz3^~D2Znd3?dqidl1P*s z#dnmQ(ne2Rjf)j@<5YD)03sIbkdu+O-KI{=YPHdxLia$t|+gE3jN#6^95$7#nhzH?Cl^^9pe>DeXwhYyz) zmnH?VybhW#sft$h^yzTbVK(D^_NG5GqrAO3mpUzH+C~qN%Bg zIfc6^%=AAUl)=4ylG_%O-h1pMi9|x}Ppkv^_`;_;PlrLz_d7lPh-!7?{Sr{mEwx3@ znM99Cc}R-tvC)&CDj%~TdM+&+0I#C@rYGSe)YAI;dcX0@cvCCd=XT~`ut+o6DEm)cxmD7g}{0~Hvie5FYoBOp1`@aOeg9oqb zeYdc<)Pkn(KJT~^MX?9nbRSimG&+`8>zjJ%`Zi0?3=FOVzAb1X5Z<6)dt;EXVZNMk zKfj4{%vxZ(ckNT?x6Pn-U(D*j~evNLT{vIu z3;H$ot`_K1^Yb4%AA;~96F#8e0}4K%-~$Ripx^@vKA_+O3jUv; + +const Template: StoryFn = ( + args: ComponentProps, +) => { + return ; +}; + +export const Default = Template.bind({}); +Default.args = {}; + +function ControlledBreadcrumb( + args: ComponentProps, +) { + const pagesContent = ["Page 1", "Page 2", "Page 3"]; + const [currentIndex, setCurrentIndex] = useState(2); + const currentPage = pagesContent[currentIndex]; + + return ( +
+ setCurrentIndex(index)} + onBackClick={() => + setCurrentIndex((_currentIndex) => + _currentIndex === 0 ? 0 : _currentIndex - 1, + ) + } + /> + {currentPage} + +
+ ); +} + +export const Controlled: StoryFn = ( + args: ComponentProps, +) => { + return ; +}; diff --git a/src/components/Breadcrumb/Breadcrumb.test.tsx b/src/components/Breadcrumb/Breadcrumb.test.tsx new file mode 100644 index 00000000..1105a2d1 --- /dev/null +++ b/src/components/Breadcrumb/Breadcrumb.test.tsx @@ -0,0 +1,63 @@ +/* +Copyright 2024 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { describe, expect, it, vi } from "vitest"; +import { composeStories } from "@storybook/react"; +import * as stories from "./Breadcrumb.stories.tsx"; +import { render, screen } from "@testing-library/react"; +import React from "react"; +import userEvent from "@testing-library/user-event"; +import { Breadcrumb } from "./Breadcrumb.tsx"; + +const { Default } = composeStories(stories); + +describe("Breadcrumb", () => { + it("should render", () => { + render(); + expect(screen.getByRole("navigation")).toMatchSnapshot(); + }); + + it("should call onPageClick when a page is clicked", async () => { + const user = userEvent.setup(); + const onPageClick = vi.fn(); + const onBackClick = vi.fn(); + + render( + , + ); + + // Click listener + await user.click(screen.getByRole("button", { name: "1st level page" })); + expect(onPageClick).toHaveBeenCalledWith("1st level page", 0); + + onPageClick.mockReset(); + // Keyboard listener + await user.type( + screen.getByRole("button", { name: "1st level page" }), + " ", + ); + expect(onPageClick).toHaveBeenCalledWith("1st level page", 0); + + // Back button + await user.click(screen.getByRole("button", { name: "Back" })); + expect(onBackClick).toHaveBeenCalled(); + }); +}); diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx new file mode 100644 index 00000000..dcc82688 --- /dev/null +++ b/src/components/Breadcrumb/Breadcrumb.tsx @@ -0,0 +1,126 @@ +/* +Copyright 2024 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { HTMLProps, JSX, MouseEventHandler, KeyboardEvent } from "react"; +import { IconButton } from "../Button"; +import Chevron from "@vector-im/compound-design-tokens/assets/web/icons/chevron-left"; +import styles from "./Breadcrumb.module.css"; +import { Link } from "../Link/Link.tsx"; +import classNames from "classnames"; + +interface BreadcrumbProps extends HTMLProps { + /** + * The label for the back button. + */ + backLabel: string; + /** + * The click handler for the back button. + */ + onBackClick: MouseEventHandler; + /** + * The pages to display in the breadcrumb. + * All the pages except the last one are displayed as links. + */ + pages: string[]; + /** + * The click handler for a page. + * @param page - The page that was clicked. + * @param index - The index of the page that was clicked. + */ + onPageClick: (page: string, index: number) => void; +} + +/** + * A breadcrumb component. + */ +export function Breadcrumb({ + backLabel, + onBackClick, + pages, + onPageClick, + className, + ...props +}: BreadcrumbProps): JSX.Element { + return ( + + ); +} + +interface PageProps { + /** + * The page to display. + */ + page: string; + /** + * Whether this is the last page in the breadcrumb. + */ + isLastPage: boolean; + /** + * The click handler for the page, ignore for last page. + */ + onClick: () => void; +} + +/** + * A breadcrumb page. + * If not the last page, the page is displayed in a link. + */ +function Page({ page, isLastPage, onClick }: PageProps): JSX.Element { + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === " ") { + onClick(); + } + }; + + return ( +
  • + {isLastPage ? ( + + {page} + + ) : ( + + {page} + + )} +
  • + ); +} diff --git a/src/components/Breadcrumb/__snapshots__/Breadcrumb.test.tsx.snap b/src/components/Breadcrumb/__snapshots__/Breadcrumb.test.tsx.snap new file mode 100644 index 00000000..53e277db --- /dev/null +++ b/src/components/Breadcrumb/__snapshots__/Breadcrumb.test.tsx.snap @@ -0,0 +1,68 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Breadcrumb > should render 1`] = ` + +`; diff --git a/src/components/Breadcrumb/index.ts b/src/components/Breadcrumb/index.ts new file mode 100644 index 00000000..c760b48f --- /dev/null +++ b/src/components/Breadcrumb/index.ts @@ -0,0 +1,17 @@ +/* +Copyright 2024 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export { Breadcrumb } from "./Breadcrumb"; diff --git a/src/index.ts b/src/index.ts index 6bd84107..7017a0bd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -51,6 +51,7 @@ export { ReleaseAnnouncement } from "./components/ReleaseAnnouncement"; export { Toast } from "./components/Toast/Toast"; export { Dropdown } from "./components/Dropdown"; export { InlineSpinner } from "./components/InlineSpinner"; +export { Breadcrumb } from "./components/Breadcrumb"; export { TextControl,