@dub/ui 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +129 -0
- package/dist/accordion.d.ts +9 -0
- package/dist/accordion.mjs +2 -0
- package/dist/avatar.d.ts +15 -0
- package/dist/avatar.mjs +2 -0
- package/dist/background/index.css +1 -0
- package/dist/background/index.d.ts +5 -0
- package/dist/background/index.mjs +2 -0
- package/dist/background.d.ts +5 -0
- package/dist/background.mjs +2 -0
- package/dist/badge.d.ts +12 -0
- package/dist/badge.mjs +2 -0
- package/dist/blur-image.d.ts +6 -0
- package/dist/blur-image.mjs +2 -0
- package/dist/button.css +1 -0
- package/dist/button.d.ts +13 -0
- package/dist/button.mjs +2 -0
- package/dist/chunk-244OSE43.mjs +2 -0
- package/dist/chunk-25PGVVAM.mjs +2 -0
- package/dist/chunk-2CR6AI65.mjs +2 -0
- package/dist/chunk-2KZFWJJX.mjs +2 -0
- package/dist/chunk-2O5K4D5Q.mjs +2 -0
- package/dist/chunk-2SFV6FGR.mjs +2 -0
- package/dist/chunk-2TGXECZQ.mjs +2 -0
- package/dist/chunk-2ZQKPJ36.mjs +2 -0
- package/dist/chunk-33QWEDY7.mjs +8 -0
- package/dist/chunk-3GU5I4KI.mjs +2 -0
- package/dist/chunk-3Q6B4UEP.mjs +2 -0
- package/dist/chunk-3S3WMTNJ.mjs +2 -0
- package/dist/chunk-3YDMSFPQ.mjs +2 -0
- package/dist/chunk-3ZOR7HDE.mjs +2 -0
- package/dist/chunk-4555QYZ6.mjs +2 -0
- package/dist/chunk-46KYE2TK.mjs +2 -0
- package/dist/chunk-4B27GTB4.mjs +3 -0
- package/dist/chunk-4JKAA2S4.mjs +2 -0
- package/dist/chunk-4JXT2OCG.mjs +2 -0
- package/dist/chunk-4OESW4SN.mjs +2 -0
- package/dist/chunk-4QDUUMAW.mjs +2 -0
- package/dist/chunk-4XBYRIVX.mjs +2 -0
- package/dist/chunk-4ZSCYPP2.mjs +2 -0
- package/dist/chunk-56D4WGXU.mjs +2 -0
- package/dist/chunk-56DTUGDB.mjs +2 -0
- package/dist/chunk-5A2BHTHD.mjs +2 -0
- package/dist/chunk-5EWSH7PD.mjs +2 -0
- package/dist/chunk-5JYRCHLM.mjs +2 -0
- package/dist/chunk-5SGX2GJA.mjs +2 -0
- package/dist/chunk-5UODCYNT.mjs +2 -0
- package/dist/chunk-5VNTIKQZ.mjs +2 -0
- package/dist/chunk-5WZZBGKJ.mjs +2 -0
- package/dist/chunk-6C6ANQBQ.mjs +2 -0
- package/dist/chunk-6DKNMNDW.mjs +2 -0
- package/dist/chunk-6GJY5OO4.mjs +2 -0
- package/dist/chunk-6KTUEBPU.mjs +2 -0
- package/dist/chunk-6S57E6BS.mjs +2 -0
- package/dist/chunk-6TNUMRJD.mjs +3 -0
- package/dist/chunk-7AQLFQZX.mjs +2 -0
- package/dist/chunk-7FBJXRA5.mjs +2 -0
- package/dist/chunk-7FTHTS2N.mjs +2 -0
- package/dist/chunk-7GWUKTP3.mjs +2 -0
- package/dist/chunk-7HABTH45.mjs +2 -0
- package/dist/chunk-7JAMW2DV.mjs +2 -0
- package/dist/chunk-7LB36OSM.mjs +2 -0
- package/dist/chunk-7O2ZOJNB.mjs +2 -0
- package/dist/chunk-7QQZYSZX.mjs +2 -0
- package/dist/chunk-7RM43KIQ.mjs +2 -0
- package/dist/chunk-7UOWPARW.mjs +2 -0
- package/dist/chunk-A3PUDJ7V.mjs +1 -0
- package/dist/chunk-AACJEMFS.mjs +2 -0
- package/dist/chunk-ABLQYIFT.mjs +2 -0
- package/dist/chunk-AUBFB4SI.mjs +2 -0
- package/dist/chunk-AUVV5HUC.mjs +2 -0
- package/dist/chunk-AZD2JXYJ.mjs +2 -0
- package/dist/chunk-B3VKF2VA.mjs +2 -0
- package/dist/chunk-B4HABAR3.mjs +2 -0
- package/dist/chunk-B6VWIQYU.mjs +3 -0
- package/dist/chunk-BHTYGPAI.mjs +2 -0
- package/dist/chunk-BZJK6BT5.mjs +2 -0
- package/dist/chunk-C33FCZ32.mjs +2 -0
- package/dist/chunk-C3ZLFBBA.mjs +1 -0
- package/dist/chunk-C4PESNHJ.mjs +2 -0
- package/dist/chunk-C5IOO5UD.mjs +2 -0
- package/dist/chunk-CBHHIJGG.mjs +2 -0
- package/dist/chunk-CCKZ5QDB.mjs +2 -0
- package/dist/chunk-CFCHBBAJ.mjs +2 -0
- package/dist/chunk-D2Q6GSHJ.mjs +2 -0
- package/dist/chunk-D3NFRR3I.mjs +2 -0
- package/dist/chunk-DG5QULNJ.mjs +2 -0
- package/dist/chunk-DHJFK2S6.mjs +2 -0
- package/dist/chunk-DIJAPN4A.mjs +2 -0
- package/dist/chunk-DMNL3RKY.mjs +2 -0
- package/dist/chunk-DNZ5INSW.mjs +2 -0
- package/dist/chunk-DOE7DSDC.mjs +2 -0
- package/dist/chunk-E2TMZ3II.mjs +2 -0
- package/dist/chunk-E5ZJGOXG.mjs +2 -0
- package/dist/chunk-ECNXBGSQ.mjs +2 -0
- package/dist/chunk-EFQSHW3O.mjs +2 -0
- package/dist/chunk-EGZBNRFB.mjs +2 -0
- package/dist/chunk-EHYVEIOL.mjs +2 -0
- package/dist/chunk-EL4XTTMC.mjs +2 -0
- package/dist/chunk-EY55E5DB.mjs +2 -0
- package/dist/chunk-F6OPUNTW.mjs +2 -0
- package/dist/chunk-F6RVKSBN.mjs +2 -0
- package/dist/chunk-FMOY54OP.mjs +2 -0
- package/dist/chunk-FMUX4F5B.mjs +2 -0
- package/dist/chunk-FR336PIX.mjs +2 -0
- package/dist/chunk-G4PPRIHO.mjs +2 -0
- package/dist/chunk-GA4JDGWI.mjs +2 -0
- package/dist/chunk-GF6NH2NZ.mjs +2 -0
- package/dist/chunk-GFMXORYG.mjs +2 -0
- package/dist/chunk-GR726Z6I.mjs +2 -0
- package/dist/chunk-GRAJLMHF.mjs +2 -0
- package/dist/chunk-GSQUGPJ2.mjs +2 -0
- package/dist/chunk-GVANYQ67.mjs +1 -0
- package/dist/chunk-GVCIDQOF.mjs +2 -0
- package/dist/chunk-GXDFY5X2.mjs +2 -0
- package/dist/chunk-HCV274NJ.mjs +2 -0
- package/dist/chunk-HFY7GEA4.mjs +2 -0
- package/dist/chunk-HI3TSQ5W.mjs +2 -0
- package/dist/chunk-HMVFMHDB.mjs +2 -0
- package/dist/chunk-HNCJ4TJR.mjs +2 -0
- package/dist/chunk-HRN3EQEB.mjs +2 -0
- package/dist/chunk-I23BSUWS.mjs +2 -0
- package/dist/chunk-I57LZ6JE.mjs +2 -0
- package/dist/chunk-IG7XYDDR.mjs +2 -0
- package/dist/chunk-IHPV7RPK.mjs +2 -0
- package/dist/chunk-IJBZR4LN.mjs +8 -0
- package/dist/chunk-IJKZ5HB5.mjs +2 -0
- package/dist/chunk-IK4XBUOE.mjs +2 -0
- package/dist/chunk-ITDAQXDS.mjs +1 -0
- package/dist/chunk-IWV22LBB.mjs +2 -0
- package/dist/chunk-IY2HG3NK.mjs +8 -0
- package/dist/chunk-IYAB6FPB.mjs +2 -0
- package/dist/chunk-IZXIJLTW.mjs +2 -0
- package/dist/chunk-J5SA2VPW.mjs +3 -0
- package/dist/chunk-J63GHRZH.mjs +2 -0
- package/dist/chunk-JBL2DUF5.mjs +2 -0
- package/dist/chunk-JEJ4EWU7.mjs +2 -0
- package/dist/chunk-JIFH2LYE.mjs +3 -0
- package/dist/chunk-JJNAD6S4.mjs +3 -0
- package/dist/chunk-JLET4OCX.mjs +3 -0
- package/dist/chunk-JORK4T7Q.mjs +2 -0
- package/dist/chunk-JP7IWWG3.mjs +2 -0
- package/dist/chunk-JVM44TKC.mjs +2 -0
- package/dist/chunk-KBSQGAWB.mjs +2 -0
- package/dist/chunk-KDGZ6H77.mjs +2 -0
- package/dist/chunk-KF5NIC53.mjs +2 -0
- package/dist/chunk-KKCOE2RH.mjs +2 -0
- package/dist/chunk-KMHFLT2M.mjs +2 -0
- package/dist/chunk-KNYITJKS.mjs +2 -0
- package/dist/chunk-KQEO6U4W.mjs +2 -0
- package/dist/chunk-KQFL4H7A.mjs +2 -0
- package/dist/chunk-KUSH4SR2.mjs +2 -0
- package/dist/chunk-KVVEZANZ.mjs +2 -0
- package/dist/chunk-KWZ4DBE4.mjs +2 -0
- package/dist/chunk-L5KPIGCR.mjs +2 -0
- package/dist/chunk-L6UWBEZE.mjs +2 -0
- package/dist/chunk-LAQSQMES.mjs +2 -0
- package/dist/chunk-LFMW2GVW.mjs +3 -0
- package/dist/chunk-LRCJQIJG.mjs +2 -0
- package/dist/chunk-M4Q2EF5G.mjs +2 -0
- package/dist/chunk-MCXA7VY2.mjs +2 -0
- package/dist/chunk-MKKBN6XG.mjs +2 -0
- package/dist/chunk-MN57ZXBI.mjs +2 -0
- package/dist/chunk-MO5Z5S7B.mjs +2 -0
- package/dist/chunk-MOPBLS6N.mjs +2 -0
- package/dist/chunk-MURAUI3W.mjs +2 -0
- package/dist/chunk-MYBLDYMF.mjs +2 -0
- package/dist/chunk-MYKWMBLF.mjs +2 -0
- package/dist/chunk-N7BNEQPU.mjs +2 -0
- package/dist/chunk-NA4EVPAY.mjs +2 -0
- package/dist/chunk-NEH5H6FT.mjs +8 -0
- package/dist/chunk-NFAXHR5T.mjs +2 -0
- package/dist/chunk-NHHVF35L.mjs +2 -0
- package/dist/chunk-NJKAWDPD.mjs +2 -0
- package/dist/chunk-NPDJHIVO.mjs +2 -0
- package/dist/chunk-NSWEINWF.mjs +2 -0
- package/dist/chunk-NWB2AZNS.mjs +2 -0
- package/dist/chunk-NX47KGRW.mjs +2 -0
- package/dist/chunk-NYBTLOVB.mjs +3 -0
- package/dist/chunk-O55CBIBG.mjs +8 -0
- package/dist/chunk-OGAYDFMV.mjs +2 -0
- package/dist/chunk-OI77O6SL.mjs +2 -0
- package/dist/chunk-OIU4BN7T.mjs +2 -0
- package/dist/chunk-OJXGRTYX.mjs +2 -0
- package/dist/chunk-OKOSJ4DF.mjs +2 -0
- package/dist/chunk-OLQNYINM.mjs +2 -0
- package/dist/chunk-OMIP4OG5.mjs +2 -0
- package/dist/chunk-OTLHHZOF.mjs +2 -0
- package/dist/chunk-OX66FZYY.mjs +2 -0
- package/dist/chunk-OXANJWH4.mjs +2 -0
- package/dist/chunk-OYEGFR5W.mjs +2 -0
- package/dist/chunk-PGXM347Z.mjs +2 -0
- package/dist/chunk-PIEMQYVU.mjs +2 -0
- package/dist/chunk-PISF5LGD.mjs +2 -0
- package/dist/chunk-PMAKRWPB.mjs +2 -0
- package/dist/chunk-PPNWHWNQ.mjs +2 -0
- package/dist/chunk-PPR7AQWS.mjs +2 -0
- package/dist/chunk-PRUCD3QQ.mjs +2 -0
- package/dist/chunk-PVPFL6D6.mjs +2 -0
- package/dist/chunk-Q64KZRX2.mjs +2 -0
- package/dist/chunk-QDT2R7NG.mjs +2 -0
- package/dist/chunk-QLD74AUU.mjs +2 -0
- package/dist/chunk-QP2EHAF7.mjs +2 -0
- package/dist/chunk-QS3K2BUJ.mjs +2 -0
- package/dist/chunk-QTY5FUJ3.mjs +2 -0
- package/dist/chunk-QXM65Z6A.mjs +2 -0
- package/dist/chunk-QZJO3GBP.mjs +2 -0
- package/dist/chunk-RF3BM6RY.mjs +2 -0
- package/dist/chunk-RF6IPNCO.mjs +2 -0
- package/dist/chunk-RF757N25.mjs +3 -0
- package/dist/chunk-RQBEORWD.mjs +2 -0
- package/dist/chunk-RRZDOFPB.mjs +2 -0
- package/dist/chunk-RT4DYHL4.mjs +2 -0
- package/dist/chunk-RWND7PHS.mjs +2 -0
- package/dist/chunk-S7GVW42L.mjs +2 -0
- package/dist/chunk-SGDLNADF.mjs +2 -0
- package/dist/chunk-SJHDGCSL.mjs +2 -0
- package/dist/chunk-SN3ZPBNA.mjs +2 -0
- package/dist/chunk-SRB37H2M.mjs +2 -0
- package/dist/chunk-SVXEWZS2.mjs +2 -0
- package/dist/chunk-SWY5WJTL.mjs +2 -0
- package/dist/chunk-T2HLWVVR.mjs +3 -0
- package/dist/chunk-T4YFARK5.mjs +2 -0
- package/dist/chunk-TEYRN5YH.mjs +2 -0
- package/dist/chunk-TFO3ND3W.mjs +2 -0
- package/dist/chunk-TLCO7PTI.mjs +2 -0
- package/dist/chunk-TN7DEVEE.mjs +2 -0
- package/dist/chunk-TNTITL6S.mjs +3 -0
- package/dist/chunk-TRZ3KXED.mjs +8 -0
- package/dist/chunk-TSU5QRYZ.mjs +2 -0
- package/dist/chunk-TTICNZG7.mjs +2 -0
- package/dist/chunk-U7OUT2WV.mjs +1 -0
- package/dist/chunk-UBZTDYQG.mjs +2 -0
- package/dist/chunk-UEZD34PU.mjs +2 -0
- package/dist/chunk-UQPZMB6C.mjs +2 -0
- package/dist/chunk-UTIBO7SO.mjs +2 -0
- package/dist/chunk-UVSGUIBW.mjs +8 -0
- package/dist/chunk-VAQHITBV.mjs +2 -0
- package/dist/chunk-VBY3N2TS.mjs +3 -0
- package/dist/chunk-VE55VEAS.mjs +2 -0
- package/dist/chunk-VFWL5XAA.mjs +2 -0
- package/dist/chunk-VIDJ5SRV.mjs +2 -0
- package/dist/chunk-W2MLKAZN.mjs +2 -0
- package/dist/chunk-W65Y5YRU.mjs +2 -0
- package/dist/chunk-W6R5YWL6.mjs +2 -0
- package/dist/chunk-WB7VZJZA.mjs +2 -0
- package/dist/chunk-WDSNVW3W.mjs +2 -0
- package/dist/chunk-WURMVTJP.mjs +2 -0
- package/dist/chunk-WWM6WQCY.mjs +2 -0
- package/dist/chunk-X2XFCFUL.mjs +2 -0
- package/dist/chunk-X374ODLH.mjs +2 -0
- package/dist/chunk-X7ADEEVD.mjs +8 -0
- package/dist/chunk-XE5CT2M3.mjs +2 -0
- package/dist/chunk-XE5QS5VD.mjs +8 -0
- package/dist/chunk-XHGXSXB3.mjs +2 -0
- package/dist/chunk-XQ47XIIO.mjs +2 -0
- package/dist/chunk-XYVV643U.mjs +8 -0
- package/dist/chunk-XZMMZD3Z.mjs +2 -0
- package/dist/chunk-XZSQWDME.mjs +2 -0
- package/dist/chunk-YBWQTHOI.mjs +2 -0
- package/dist/chunk-YOWYEBY3.mjs +2 -0
- package/dist/chunk-YQXTW7IL.mjs +2 -0
- package/dist/chunk-YVROMH7J.mjs +2 -0
- package/dist/chunk-YVVQMYLM.mjs +2 -0
- package/dist/chunk-Z46XMMXR.mjs +2 -0
- package/dist/chunk-Z4MSMGYC.mjs +2 -0
- package/dist/chunk-Z5P2KHPP.mjs +2 -0
- package/dist/chunk-ZGUSKAF7.mjs +2 -0
- package/dist/chunk-ZMZG5IED.mjs +8 -0
- package/dist/chunk-ZNLJMM2N.mjs +2 -0
- package/dist/chunk-ZSF6CHQK.mjs +2 -0
- package/dist/chunk-ZXOXZAH4.mjs +2 -0
- package/dist/copy-button.css +1 -0
- package/dist/copy-button.d.ts +8 -0
- package/dist/copy-button.mjs +2 -0
- package/dist/footer.css +1 -0
- package/dist/footer.d.ts +5 -0
- package/dist/footer.mjs +2 -0
- package/dist/form.css +1 -0
- package/dist/form.d.ts +14 -0
- package/dist/form.mjs +2 -0
- package/dist/google-b9019ca9.d.ts +7 -0
- package/dist/grid-AJXQWCY6.svg +5 -0
- package/dist/icon-menu.d.ts +10 -0
- package/dist/icon-menu.mjs +2 -0
- package/dist/icons/copy.d.ts +7 -0
- package/dist/icons/copy.mjs +2 -0
- package/dist/icons/expanding-arrow.d.ts +7 -0
- package/dist/icons/expanding-arrow.mjs +2 -0
- package/dist/icons/facebook.d.ts +8 -0
- package/dist/icons/facebook.mjs +2 -0
- package/dist/icons/github.d.ts +7 -0
- package/dist/icons/github.mjs +2 -0
- package/dist/icons/google.d.ts +7 -0
- package/dist/icons/google.mjs +2 -0
- package/dist/icons/index.css +1 -0
- package/dist/icons/index.d.ts +16 -0
- package/dist/icons/index.mjs +2 -0
- package/dist/icons/linkedin.d.ts +8 -0
- package/dist/icons/linkedin.mjs +2 -0
- package/dist/icons/loading-circle.d.ts +7 -0
- package/dist/icons/loading-circle.mjs +2 -0
- package/dist/icons/loading-dots.css +1 -0
- package/dist/icons/loading-dots.d.ts +5 -0
- package/dist/icons/loading-dots.mjs +2 -0
- package/dist/icons/loading-spinner.css +1 -0
- package/dist/icons/loading-spinner.d.ts +7 -0
- package/dist/icons/loading-spinner.mjs +2 -0
- package/dist/icons/logo.d.ts +7 -0
- package/dist/icons/logo.mjs +2 -0
- package/dist/icons/logotype.d.ts +7 -0
- package/dist/icons/logotype.mjs +2 -0
- package/dist/icons/photo.d.ts +7 -0
- package/dist/icons/photo.mjs +2 -0
- package/dist/icons/tick.d.ts +7 -0
- package/dist/icons/tick.mjs +2 -0
- package/dist/icons/twitter.d.ts +7 -0
- package/dist/icons/twitter.mjs +2 -0
- package/dist/icons/unsplash.d.ts +7 -0
- package/dist/icons/unsplash.mjs +2 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +128 -0
- package/dist/index.mjs +2 -0
- package/dist/link-preview.css +1 -0
- package/dist/link-preview.d.ts +7 -0
- package/dist/link-preview.mjs +2 -0
- package/dist/max-width-wrapper.d.ts +9 -0
- package/dist/max-width-wrapper.mjs +2 -0
- package/dist/modal.d.ts +14 -0
- package/dist/modal.mjs +2 -0
- package/dist/nav-mobile.css +1 -0
- package/dist/nav-mobile.d.ts +5 -0
- package/dist/nav-mobile.mjs +2 -0
- package/dist/nav.css +1 -0
- package/dist/nav.d.ts +9 -0
- package/dist/nav.mjs +2 -0
- package/dist/number-tooltip.d.ts +12 -0
- package/dist/number-tooltip.mjs +2 -0
- package/dist/number.d.ts +12 -0
- package/dist/number.mjs +2 -0
- package/dist/popover.d.ts +13 -0
- package/dist/popover.mjs +2 -0
- package/dist/switch.d.ts +14 -0
- package/dist/switch.mjs +2 -0
- package/dist/tab-select.d.ts +9 -0
- package/dist/tab-select.mjs +2 -0
- package/dist/tooltip.d.ts +32 -0
- package/dist/tooltip.mjs +2 -0
- package/package.json +65 -0
- package/postcss.config.js +9 -0
- package/src/accordion.tsx +60 -0
- package/src/avatar.tsx +47 -0
- package/src/background.tsx +71 -0
- package/src/badge.tsx +33 -0
- package/src/button.tsx +60 -0
- package/src/content.ts +230 -0
- package/src/copy-button.tsx +39 -0
- package/src/footer.tsx +203 -0
- package/src/form.tsx +77 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/use-current-anchor.ts +65 -0
- package/src/hooks/use-intersection-observer.ts +41 -0
- package/src/hooks/use-local-storage.ts +24 -0
- package/src/hooks/use-media-query.ts +46 -0
- package/src/hooks/use-scroll.ts +21 -0
- package/src/icon-menu.tsx +15 -0
- package/src/icons/copy.tsx +18 -0
- package/src/icons/expanding-arrow.tsx +39 -0
- package/src/icons/facebook.tsx +23 -0
- package/src/icons/github.tsx +14 -0
- package/src/icons/google.tsx +12 -0
- package/src/icons/index.tsx +22 -0
- package/src/icons/linkedin.tsx +22 -0
- package/src/icons/loading-circle.tsx +25 -0
- package/src/icons/loading-dots.css +40 -0
- package/src/icons/loading-dots.tsx +13 -0
- package/src/icons/loading-spinner.tsx +34 -0
- package/src/icons/logo.tsx +29 -0
- package/src/icons/logotype.tsx +51 -0
- package/src/icons/photo.tsx +20 -0
- package/src/icons/tick.tsx +18 -0
- package/src/icons/twitter.tsx +31 -0
- package/src/icons/unsplash.tsx +17 -0
- package/src/index.tsx +35 -0
- package/src/link-preview.tsx +111 -0
- package/src/max-width-wrapper.tsx +21 -0
- package/src/modal.tsx +102 -0
- package/src/nav-mobile.tsx +108 -0
- package/src/nav.tsx +205 -0
- package/src/popover.tsx +61 -0
- package/src/styles.css +3 -0
- package/src/switch.tsx +60 -0
- package/src/tab-select.tsx +27 -0
- package/src/tooltip.tsx +184 -0
- package/tailwind.config.ts +9 -0
- package/tsconfig.json +5 -0
- package/tsup.config.ts +15 -0
package/src/content.ts
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { Airplay, BarChart, Link2, QrCode, Users } from "lucide-react";
|
|
2
|
+
|
|
3
|
+
export const FEATURES_LIST = [
|
|
4
|
+
{
|
|
5
|
+
title: "Powerful Analytics For The Modern Marketer",
|
|
6
|
+
shortTitle: "Advanced Analytics",
|
|
7
|
+
accordionTitle: "Analytics that matter",
|
|
8
|
+
description:
|
|
9
|
+
"Dub provides powerful analytics for your links, including geolocation, device, browser, and referrer information.",
|
|
10
|
+
icon: BarChart,
|
|
11
|
+
slug: "analytics",
|
|
12
|
+
thumbnail: "https://d2vwwcvoksz7ty.cloudfront.net/features/analytics.png",
|
|
13
|
+
thumbnailBlurhash:
|
|
14
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAdCAYAAADoxT9SAAAACXBIWXMAAAsTAAALEwEAmpwYAAAE+0lEQVR4nL2Y63qiShBF5/2f8czkpjGCgoLQV+yzq6saGpVMYjznx/ok0JBa7GrQ/mWsC7fQhLE3MEEJl8eMteP51hH+L8g4e81SXUv8WhS5KFJpwlyhdSZicpGpYCf8VSgT+09EliQYPs5j3ZVIknA+41O5+9K5S6RXE0lEJZEok6Xhhwx/zaLU99L5lkguMJGOuyiiMxHnWMB/wi2py3TMF5L5skifiXQzbJTpIZJkpkQykWGBeHxJRtpLtn8s0pNILtGbcIrYSEcoJzIe53hcY2CRJCOFDxfM0xlGmVtz50EiNt79Too/dUxLn72LRBnlRWaYZPwZEsxwwf8iYpKItE+SaEWgOREu0naEx3EPoQEyQyZDBU8yudRcYrieJ9bHVr17sjOTCKWRJBpwhMShJdzIsfXY73GchIYo1Eehc5QhHOEzpP1szlh8zo9FXBQZJSBwhEDd2FAdbdgfXNhl7I8e+32o2wFSA6SGKNXrc0QRhtH2HFOb468Rkfw99U0RPpl6n+YAtc8RbVQ3VDAKr10oKhe2xF6oPPb5UNYeYgOkBqTFQi3RE2dc74zrnmNqiT4m6EdorqlR5oEiNBeohaqG7z5JfKD4zc6F91LYefztsd9HKRLaQ4YSOkhKKak2kzsJnfIjc5kHiVAaSYTSKGuWoOLXhQurrQtvwqrwYV36KEVClBClQ0KUUNVcivkoRnMrPTBOSUY5kZEX7iNExraSNCgJkqDiXz9ceNlM0N9vWwgVnFBquaJObedZiuZTQ3PKQ2x6WLCQm95PhnmoCLXVVtJYicTzuwtPxFrA9rMI0RgaG1vuou121HoHzw+JJskg/Y7/J8lwKm5M5X6RNNHH+WEhYlGcxV23SMCicBv+rG34vWJom/Y9gyhTcHrrMknxQ4KSpTblJx7fqPgoP00ieSp3iWiJs5NHL4nQY/emCAr/IxL/yOfvNQs9b0iGxxKrwkLIRpmPPQslmUpkUiozEf0TEW0zEXslskJhrxuDO29QtIFAxpr3PeHYy4eJMq+ZzPuOZPhaRWVDeZEKPVwelAh/841vdXkZHhp6CRr8Y4N+N+G9MCgMhYrMkxSfBIjnDUmALfOGc9alYZG9iNR8g/afJXKXSP4VPooYiBikYkJ9NJikkNlPMqstF5uEqHjiZaORhoasUGikwSIbnPuBa2xxU0oRqXCj6ofOkZkIf3VvSQYih0YjFR3KSk8yKGxNRVKxeeHCqmDWJbPZ6VGiwE3hNLh16Tscpc8i9gdPrdmPKjOKUCrtSSMVjVRYZldDBkLbPQpDcRsU+V5OBSfedxMbGkvngDJKGEiYmAZJHE/p54Edf7j9UMRkIjp0nQ4niDQQOSKVA2TqwyQUExKpSBKMRauRgsaCHZ2La1SQqBskjbRJohGJUWS2LpCWnr4gkiR0WnBQOvQkEmUUZBRkmCjUcEKVSEUxoYyoGbujYgE6j6B2pfl3MlMSszQmrtfSFkTGQeOqiRYRFSGRJEO0klBMqWWpQyqw4WKrRuGuK/4UahpL55yYhtpWfj4vSdwSSSyIpDRYQilIKCUy/UyIkbYDbSdyHcOFqjkYk463PUNrAGlBo8/baXEN7SsicQVRi4jK6IPqM2bHcmk90gknyEdwvItgP67fC2Oxhlcvk4ReWBi8KXJl+ImIvkSrYCI6bjPYNowai1UZ2G8meKy5zeIK51zoX56UB7vUOlYfAAAAAElFTkSuQmCC",
|
|
15
|
+
videoUrl:
|
|
16
|
+
"https://www.youtube.com/embed/i05FamHTn_I?si=gRnWs5Kf2n6Qyqmd&autoplay=1",
|
|
17
|
+
videoLength: "2:56",
|
|
18
|
+
bentoTitle: "Detailed insights for every click",
|
|
19
|
+
bentoDescription:
|
|
20
|
+
"Dub provides detailed analytics for every click on your links. See where your audience is coming from and what devices they are using.",
|
|
21
|
+
bentoFeatures: [
|
|
22
|
+
{
|
|
23
|
+
title: "Time-series data",
|
|
24
|
+
description:
|
|
25
|
+
"See how your links are performing over time with a beautiful time-series chart.",
|
|
26
|
+
image:
|
|
27
|
+
"https://d2vwwcvoksz7ty.cloudfront.net/features/time-series-data.png",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
title: "Geographic data",
|
|
31
|
+
description:
|
|
32
|
+
"Understand your audience with geographic data – on both a country and city level.",
|
|
33
|
+
image: "https://d2vwwcvoksz7ty.cloudfront.net/features/geo-data.png",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
title: "Device data",
|
|
37
|
+
description:
|
|
38
|
+
"See what devices your audience is using to click on your links",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
title: "Referrer data",
|
|
42
|
+
description:
|
|
43
|
+
"Understand which websites are driving the most traffic to your links.",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
title: "404 monitoring",
|
|
47
|
+
description:
|
|
48
|
+
"Monitor your links for 404 errors and notifies you when they occur.",
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
title: "Branded Links That Stand Out",
|
|
54
|
+
shortTitle: "Branded Links",
|
|
55
|
+
accordionTitle: "Use your own domain",
|
|
56
|
+
description:
|
|
57
|
+
"Dub offers free and unlimited custom domains on all plans for you to create branded links that stand out.",
|
|
58
|
+
icon: Airplay,
|
|
59
|
+
slug: "branded-links",
|
|
60
|
+
thumbnail:
|
|
61
|
+
"https://d2vwwcvoksz7ty.cloudfront.net/features/branded-links.png",
|
|
62
|
+
thumbnailBlurhash:
|
|
63
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAdCAYAAADoxT9SAAAACXBIWXMAAAsTAAALEwEAmpwYAAADYElEQVR4nMWY2XLDIAxF/f9/28QGs5myCSQBXtLM9OFO09axdXwlIVjccXjnotyJjqZw/UHkv6B83/gsY63XWvt9V15K6YUQfts2v64rVfjbtgkvwjX7vvsFgrVRNsoyuV7p+iPJcR0j2GulGMLzjDFeKZWCwxDv99u/ol7v9DlqXQNMuCYCLzMAg2Xgs6vK3zkqUAf2RxBwI775BPF6UUWYABhB43WL5QGHG0VpIjtQADKOwR0V7inQFGSlID8/ABNdKSkWQXLwWTVwTaWIbFWEwSJAD2F6kJZab0itgSMIxHQAOWAdbpi1KzOQRVAuCbuUa+k+TAeSakSmguY18io1QkD6N48BivYsuZsqgOFAZzBTiA5Ep2LnXSumUi70NUOsuXNtAXhRGIBDVACuBiQLDABp3dIMOtyVKxQkt1/oXA2mACWorbbf6EZswQsEX99+ukG0tkiqcCGW9ltQ/CmIQwimuPIcpKwjJr/YncAUIIAS2YkIIUI9LRxCIggCIFT4YpBUCSTDmAYzAAFXnoBAeun6ggGmAFWoBhH/t7Q64C7sSZtoWgvMWqGoM7FucEdrIO6y4OvCDOuYgbqFNCtAAAWfS8yXIBRGUU1AtM5rDQe504LppEHXN9pZNanrBYq6r4nY/rgjIIABEH0DxN1owf3c10YjDGUrFDSrhUBwEJZa3BVRHJEcpDzMfhuEL95PQMQEYisdDFpyghiBuPupNYKxCAaPUpcgo5QaCbqZLGtNdsOktDKD+rg/c30T5ARGJDWI7IauNxyn1ZMp+EMQ+YEjojgiU0ssIMwR2Mu4/0st2noBLAe/lwUIIFQdaVJbTFuAEYR7tDdpQPPOxYfcOQh6+y2lKExapDCIxiAWFfonjhydIyOQi641d0RiRwpEXJSuHLm3IJ61X/dJ+/0wtYojBu/3B4cYVy58ZR3BqTXURWrR9HJdwXOYuxB1xK8aguh5sU8gKsgO6WXmrkxg7kFQNxJEGoMGINNZawIiCojEICS9TH0wP1I6Oz8bHT2Zk5SiIGgnWIHkaEPV/o5rBIN0riBNz8guxO/zfRCUWgRGzWHwin8n6A6CpVQFUQbvR/oUmwoBcAgMMoN5Ihx8fzQ1Aqn7Ev0YpINh52K6jC51oDwNmmtwxlYn7fb7L/LDQmuPkz48AAAAAElFTkSuQmCC",
|
|
64
|
+
videoUrl:
|
|
65
|
+
"https://www.youtube.com/embed/LbgvQxLVqq4?si=7_sJVXFZINPxbAdM&autoplay=1",
|
|
66
|
+
videoLength: "3:23",
|
|
67
|
+
bentoTitle: "Impress your audience with branded links",
|
|
68
|
+
bentoDescription:
|
|
69
|
+
"Create custom short links that match your brand and stand out from the crowd, with free and unlimited custom domains on all plans.",
|
|
70
|
+
bentoFeatures: [
|
|
71
|
+
{
|
|
72
|
+
title: "Unlimited custom domains",
|
|
73
|
+
description:
|
|
74
|
+
"Add as many custom domains as you want to your project at no extra cost.",
|
|
75
|
+
image: "https://d2vwwcvoksz7ty.cloudfront.net/features/domains.png",
|
|
76
|
+
href: "/help/article/how-to-add-custom-domain",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
title: "Custom Social Media Cards",
|
|
80
|
+
description:
|
|
81
|
+
"Customize the title, description, and image of your links when shared on social media.",
|
|
82
|
+
image:
|
|
83
|
+
"https://d2vwwcvoksz7ty.cloudfront.net/features/social-cards.png",
|
|
84
|
+
href: "/help/article/how-to-create-link#custom-social-media-cards",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
title: "Free SSL certificates",
|
|
88
|
+
description:
|
|
89
|
+
"Dub automatically provisions SSL certificates for your custom domains.",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
title: "Link cloaking",
|
|
93
|
+
description:
|
|
94
|
+
"Hide your destination URL with link cloaking to make your links look cleaner.",
|
|
95
|
+
href: "/help/article/how-to-create-link#link-cloaking",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
title: "Custom QR codes",
|
|
99
|
+
description:
|
|
100
|
+
"With Dub Pro, you can create branded QR codes for your links.",
|
|
101
|
+
href: "/features/qr-codes",
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
title: "Free QR Code Generator",
|
|
107
|
+
shortTitle: "QR Codes",
|
|
108
|
+
accordionTitle: "Free QR Code Generator",
|
|
109
|
+
description:
|
|
110
|
+
"QR codes and short links are like peas in a pod. Dub offers free QR codes for every short link you create.",
|
|
111
|
+
icon: QrCode,
|
|
112
|
+
slug: "qr-codes",
|
|
113
|
+
thumbnail: "https://d2vwwcvoksz7ty.cloudfront.net/features/qr-codes.png",
|
|
114
|
+
thumbnailBlurhash:
|
|
115
|
+
"data:image/png;base64,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",
|
|
116
|
+
videoUrl:
|
|
117
|
+
"https://www.youtube.com/embed/fcWkUZoJJ7w?si=LKNOO67aL6Q-3xN4&autoplay=1",
|
|
118
|
+
videoLength: "1:07",
|
|
119
|
+
bentoTitle: "Gorgeous QR codes for your links",
|
|
120
|
+
bentoDescription:
|
|
121
|
+
"Create beautiful flyers and posters with QR codes for your links.",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
title: "Personalize Your Short Links",
|
|
125
|
+
shortTitle: "Personalization",
|
|
126
|
+
accordionTitle: "Personalize Your Short links",
|
|
127
|
+
description:
|
|
128
|
+
"Customize your link's behavior with device targeting, geo targeting, link cloaking, and more.",
|
|
129
|
+
icon: Link2,
|
|
130
|
+
slug: "personalization",
|
|
131
|
+
thumbnail:
|
|
132
|
+
"https://d2vwwcvoksz7ty.cloudfront.net/features/personalization.png",
|
|
133
|
+
thumbnailBlurhash:
|
|
134
|
+
"data:image/png;base64,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",
|
|
135
|
+
videoUrl:
|
|
136
|
+
"https://www.youtube.com/embed/K2UdLK8k1I4?si=79hu5XSW9pSjODOm&autoplay=1",
|
|
137
|
+
videoLength: "2:25",
|
|
138
|
+
bentoTitle: "Optimize your links for every audience",
|
|
139
|
+
bentoDescription:
|
|
140
|
+
"Get the most out of every click by delivering the right experience to the right audience.",
|
|
141
|
+
bentoFeatures: [
|
|
142
|
+
{
|
|
143
|
+
title: "Device targeting",
|
|
144
|
+
description:
|
|
145
|
+
"Redirect your audience to different destinations based on their device type (e.g. iOS/Android).",
|
|
146
|
+
image:
|
|
147
|
+
"https://d2vwwcvoksz7ty.cloudfront.net/features/device-targeting.png",
|
|
148
|
+
href: "/help/article/how-to-create-link#device-targeting-ios--android",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
title: "Geo targeting",
|
|
152
|
+
description:
|
|
153
|
+
"Redirect your audience to different destinations based on their country of origin.",
|
|
154
|
+
image:
|
|
155
|
+
"https://d2vwwcvoksz7ty.cloudfront.net/features/geo-targeting.png",
|
|
156
|
+
href: "/help/article/how-to-create-link#geo-targeting",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
title: "Expiration dates",
|
|
160
|
+
description:
|
|
161
|
+
"Automatically disable your links after a certain date & time.",
|
|
162
|
+
href: "/help/article/how-to-create-link#expiration-date",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
title: "Password protection",
|
|
166
|
+
description:
|
|
167
|
+
"Protect your links with passwords to prevent unauthorized access.",
|
|
168
|
+
href: "/help/article/how-to-create-link#password-protection",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
title: "Link cloaking",
|
|
172
|
+
description:
|
|
173
|
+
"Hide your destination URL with link cloaking to make your links look cleaner.",
|
|
174
|
+
href: "/help/article/how-to-create-link#link-cloaking",
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
// { title: "Programmatic Link Creation", shortTitle: "API", slug: "api" },
|
|
179
|
+
{
|
|
180
|
+
title: "Collaborate With Your Team",
|
|
181
|
+
shortTitle: "Team Collaboration",
|
|
182
|
+
accordionTitle: "Collaborate With Your Team",
|
|
183
|
+
description:
|
|
184
|
+
"Invite your teammates to collaborate on your links. For enterprises, Dub offers SAML SSO for better security.",
|
|
185
|
+
icon: Users,
|
|
186
|
+
slug: "collaboration",
|
|
187
|
+
thumbnail:
|
|
188
|
+
"https://d2vwwcvoksz7ty.cloudfront.net/features/collaboration.png",
|
|
189
|
+
thumbnailBlurhash:
|
|
190
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAdCAYAAADoxT9SAAAACXBIWXMAAAsTAAALEwEAmpwYAAAE0UlEQVR4nM2YC3eiWBCE5///yIlRQN6vyxu0p6ovILLZTMzDXc+pg6AJ/VnVfYFf8g2v6/Uql8tl1TRNq8ZxvNP2s+X7/HvqK69f3wGxgOyLH4bhTW2hvgvmW0D2EEvBfd+/qbdg/hOQ5aQW4opiLijqBsFiu65TtW27UTcfvwfaw3wG6CGQPYB1wkJYgEE6FMiCGxTeNI2qrqH5/QJFoC3M00DuIS4rBH/NYRhXFxRCi6+lqqhqo1qPL0DWme9x5UMg70GwiH5xAQWyWGOMFGUpRVFInlO5brlf4jg/r2agxZnFlc/2yxdAFohBWhTD+BhTKUCW5ZKmmSRJInFMxbrlPo8TjEB0aYFhxL7S+A+B3ENMa2M3TatOlKXRIpMk1cLDKJIwDCWAuI2wz+Npms4wRqPGmP04yJsQu0jRDQtRKEQUxRIEofh+IOezv4r7BKJDdIZRW1zZxut5IPOYZbNaNxCpotTiothCsHDXO4vjepArLrYe9i1MpDGjK+yXLQj//2ca/iGQ26ide2N1o0JfFPilU0BEcvZ8cRxPTkdXjkdnleO4KwxdIzh7hfFin/w8yOW6cwMgHUCaDm40cMOgqFyiMBH/HIrrnBXi9eDI4XCCjnh/UhgXgHSLrqSIIZ2sq/qu4Xm+HwHRxW8Zt4AY4Ea/xMrUUuQASXIJgxhuBOKcznJ8deXwAoiX46rjK1w5eepYGETaTytI+0yQ8QbStYM0dSumrCXPjCQxQRI5u+E9yO8F5rQBCRQ6SbLng0wzyNAzVqOC1FUnpmgkTwESAcSfQY6+HA+evL4gWr8hbBmzE+AYO8YvCtEjACkRyxrxtCCjnucG8mGOx0DGLUizBykAkgIkEvcUoGi48kIYNPzBVYec41k8N5DAx3oSpZKluQ4KDgxOwKeBTOMFINMK0gCkKlopMkytuJQoyCXw4IpDGDjz6lvBIffki+dgbTlH+B4WRUQxz0rtMfYahwdje9mDfBDmQUf2IP0MUgPESBzClXMmvmthPMCoAMDI+V6s8UuiTLKk0CFRmUann714nJ4DMg1bkHEG6aTMEK+kkiSEK36uMHSGQL4bK0BwTjR6cZihNwodEKbA1XCFq+CWV8HjT4PIPLWuGq1RQSYFaSv0SdmLyeFKSlcsTBwUChQBSOVnOJbpQEhjC1EWlbrR1J0ODvbe9FwQuAKQvpmkrQFitq4AJsKaApgEMSNQjL7hew6DFH3EwVDklY5tuqGx6v4HII0ZFMRkdGWGgTMpgaJSxUGQJcZCYDAwUuoGhkXb9BZkuEWL53t0cr0Pcv1LtGYQxkthEDE6QyBGTYVBUFJ5o6O6Klt1gpEiBGPF/hgGe2swzSCXnwJZxu+4aXYFQcMv8TKYYAqjQuFafKsiQGVaXXuaup8hrBsWZLyBLK48sLp/AOTfxi9BAMHJZex6woIJsBedMGWjcbrBdHcw7BGuI9Pqyg3myyDX90AQiQWkJkjZasElldc3FVYEMcbC2GjxKQsfVvChhX0Cc+fK5fJQvB4EmWaQ5aKx11+XvzILVYii1qm0VUlh3JZlrdPKwjR3MP0MM+5gPhqvv4D88yHcclPFSLCIum71nsSCAKKo9P6Eq/aqwoq3w7y2MqaeHw01enliH9z1O1ceezz0B49+MX0EZnFXAAAAAElFTkSuQmCC",
|
|
191
|
+
videoUrl:
|
|
192
|
+
"https://www.youtube.com/embed/_m9RM8L6TOw?si=49_XIkQ7g-61dRtV&autoplay=1",
|
|
193
|
+
videoLength: "2:13",
|
|
194
|
+
bentoTitle: "Seamless collaboration for marketing teams",
|
|
195
|
+
bentoDescription:
|
|
196
|
+
"View and manage your team's links in one place, with fine-grained permissions and SAML SSO for enterprises.",
|
|
197
|
+
bentoFeatures: [
|
|
198
|
+
{
|
|
199
|
+
title: "Unlimited teammates",
|
|
200
|
+
description:
|
|
201
|
+
"All paid plans come with unlimited teammates, so you can work with your team without worrying about extra costs.",
|
|
202
|
+
image: "https://d2vwwcvoksz7ty.cloudfront.net/features/teammates.png",
|
|
203
|
+
href: "/help/article/how-to-invite-teammates",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
title: "SAML SSO",
|
|
207
|
+
description:
|
|
208
|
+
"Dub offers SAML SSO for enterprises to provide better security and control over their projects.",
|
|
209
|
+
image: "https://d2vwwcvoksz7ty.cloudfront.net/features/saml.png",
|
|
210
|
+
href: "/help/category/saml-sso",
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
title: "Tags & comments",
|
|
214
|
+
description:
|
|
215
|
+
"Organize your links with tags and comments to provide context for your team.",
|
|
216
|
+
href: "/help/article/how-to-use-tags",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
title: "Fine-grained permissions",
|
|
220
|
+
description:
|
|
221
|
+
"Enterprises on Dub can set role-based access controls (RBAC) to control who can create, edit, and delete links.",
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
title: "Team analytics",
|
|
225
|
+
description:
|
|
226
|
+
"See how your team is performing with team analytics, including link clicks and top referrers.",
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@dub/utils";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { toast } from "sonner";
|
|
6
|
+
import { Copy, Tick } from "./icons";
|
|
7
|
+
|
|
8
|
+
export function CopyButton({
|
|
9
|
+
value,
|
|
10
|
+
className,
|
|
11
|
+
}: {
|
|
12
|
+
value: string;
|
|
13
|
+
className?: string;
|
|
14
|
+
}) {
|
|
15
|
+
const [copied, setCopied] = useState(false);
|
|
16
|
+
return (
|
|
17
|
+
<button
|
|
18
|
+
onClick={(e) => {
|
|
19
|
+
e.stopPropagation();
|
|
20
|
+
setCopied(true);
|
|
21
|
+
navigator.clipboard.writeText(value).then(() => {
|
|
22
|
+
toast.success("Copied to clipboard!");
|
|
23
|
+
});
|
|
24
|
+
setTimeout(() => setCopied(false), 3000);
|
|
25
|
+
}}
|
|
26
|
+
className={cn(
|
|
27
|
+
"group rounded-full bg-gray-100 p-1.5 transition-all duration-75 hover:scale-105 hover:bg-blue-100 active:scale-95",
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
>
|
|
31
|
+
<span className="sr-only">Copy</span>
|
|
32
|
+
{copied ? (
|
|
33
|
+
<Tick className="text-gray-700 transition-all group-hover:text-blue-800" />
|
|
34
|
+
) : (
|
|
35
|
+
<Copy className="text-gray-700 transition-all group-hover:text-blue-800" />
|
|
36
|
+
)}
|
|
37
|
+
</button>
|
|
38
|
+
);
|
|
39
|
+
}
|
package/src/footer.tsx
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import va from "@vercel/analytics";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { useParams } from "next/navigation";
|
|
6
|
+
import { FEATURES_LIST } from "./content";
|
|
7
|
+
import { Github, LinkedIn, LogoType, Twitter } from "./icons";
|
|
8
|
+
import { MaxWidthWrapper } from "./max-width-wrapper";
|
|
9
|
+
|
|
10
|
+
const navigation = {
|
|
11
|
+
features: FEATURES_LIST.map(({ shortTitle, slug }) => ({
|
|
12
|
+
name: shortTitle,
|
|
13
|
+
href: `/features/${slug}`,
|
|
14
|
+
})),
|
|
15
|
+
product: [
|
|
16
|
+
{ name: "Blog", href: "/blog" },
|
|
17
|
+
{ name: "Changelog", href: "/changelog" },
|
|
18
|
+
{ name: "Customer Stories", href: "/customers" },
|
|
19
|
+
{ name: "Help Center", href: "/help" },
|
|
20
|
+
{ name: "Pricing", href: "/pricing" },
|
|
21
|
+
],
|
|
22
|
+
tools: [
|
|
23
|
+
{ name: "Metatags API", href: "/tools/metatags" },
|
|
24
|
+
{ name: "Link Inspector", href: "/tools/inspector" },
|
|
25
|
+
],
|
|
26
|
+
legal: [
|
|
27
|
+
{ name: "Privacy", href: "/privacy" },
|
|
28
|
+
{ name: "Terms", href: "/terms" },
|
|
29
|
+
{ name: "Abuse", href: "/abuse" },
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function Footer() {
|
|
34
|
+
const { domain = "dub.co" } = useParams() as { domain: string };
|
|
35
|
+
|
|
36
|
+
const createHref = (href: string) =>
|
|
37
|
+
domain === "dub.co" ? href : `https://dub.co${href}`;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<footer className="z-10 border-t border-gray-200 bg-white/50 py-8 backdrop-blur-lg">
|
|
41
|
+
<MaxWidthWrapper className="pt-10">
|
|
42
|
+
<div className="xl:grid xl:grid-cols-5 xl:gap-8">
|
|
43
|
+
<div className="space-y-8 xl:col-span-2">
|
|
44
|
+
<Link
|
|
45
|
+
href={createHref("/")}
|
|
46
|
+
{...(domain !== "dub.co" && {
|
|
47
|
+
onClick: () => {
|
|
48
|
+
va.track("Referred from custom domain", {
|
|
49
|
+
domain,
|
|
50
|
+
medium: `footer item (logo)`,
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
})}
|
|
54
|
+
>
|
|
55
|
+
<span className="sr-only">Dub.co Logo</span>
|
|
56
|
+
<LogoType className="h-7 text-gray-600" />
|
|
57
|
+
</Link>
|
|
58
|
+
<p className="max-w-xs text-sm text-gray-500">
|
|
59
|
+
Giving modern marketing teams superpowers with short links that
|
|
60
|
+
stand out.
|
|
61
|
+
</p>
|
|
62
|
+
<div className="flex items-center space-x-2">
|
|
63
|
+
<a
|
|
64
|
+
href="https://twitter.com/dubdotco"
|
|
65
|
+
target="_blank"
|
|
66
|
+
rel="noreferrer"
|
|
67
|
+
className="group rounded-md p-2 transition-colors hover:bg-gray-100 active:bg-gray-200"
|
|
68
|
+
>
|
|
69
|
+
<span className="sr-only">Twitter</span>
|
|
70
|
+
<Twitter className="h-5 w-5 text-gray-600" />
|
|
71
|
+
</a>
|
|
72
|
+
<div className="h-8 border-l border-gray-200" />
|
|
73
|
+
<a
|
|
74
|
+
href="https://github.com/steven-tey/dub"
|
|
75
|
+
target="_blank"
|
|
76
|
+
rel="noreferrer"
|
|
77
|
+
className="rounded-md p-2 transition-colors hover:bg-gray-100 active:bg-gray-200"
|
|
78
|
+
>
|
|
79
|
+
<span className="sr-only">Github</span>
|
|
80
|
+
<Github className="h-5 w-5 text-gray-600" />
|
|
81
|
+
</a>
|
|
82
|
+
<div className="h-8 border-l border-gray-200" />
|
|
83
|
+
<a
|
|
84
|
+
href="https://www.linkedin.com/company/dubhq/"
|
|
85
|
+
target="_blank"
|
|
86
|
+
rel="noreferrer"
|
|
87
|
+
className="rounded-md p-2 transition-colors hover:bg-gray-100 active:bg-gray-200"
|
|
88
|
+
>
|
|
89
|
+
<span className="sr-only">LinkedIn</span>
|
|
90
|
+
<LinkedIn className="h-5 w-5" fill="#52525B" />
|
|
91
|
+
</a>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
<div className="mt-16 grid grid-cols-2 gap-8 xl:col-span-3 xl:mt-0">
|
|
95
|
+
<div className="md:grid md:grid-cols-2 md:gap-8">
|
|
96
|
+
<div>
|
|
97
|
+
<h3 className="text-sm font-semibold text-gray-900">
|
|
98
|
+
Features
|
|
99
|
+
</h3>
|
|
100
|
+
<ul role="list" className="mt-4 space-y-4">
|
|
101
|
+
{navigation.features.map((item) => (
|
|
102
|
+
<li key={item.name}>
|
|
103
|
+
<Link
|
|
104
|
+
href={createHref(item.href)}
|
|
105
|
+
{...(domain !== "dub.co" && {
|
|
106
|
+
onClick: () => {
|
|
107
|
+
va.track("Referred from custom domain", {
|
|
108
|
+
domain,
|
|
109
|
+
medium: `footer item (${item.name})`,
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
})}
|
|
113
|
+
className="text-sm text-gray-500 hover:text-gray-900"
|
|
114
|
+
>
|
|
115
|
+
{item.name}
|
|
116
|
+
</Link>
|
|
117
|
+
</li>
|
|
118
|
+
))}
|
|
119
|
+
</ul>
|
|
120
|
+
</div>
|
|
121
|
+
<div className="mt-10 md:mt-0">
|
|
122
|
+
<h3 className="text-sm font-semibold text-gray-600">Product</h3>
|
|
123
|
+
<ul role="list" className="mt-4 space-y-4">
|
|
124
|
+
{navigation.product.map((item) => (
|
|
125
|
+
<li key={item.name}>
|
|
126
|
+
<Link
|
|
127
|
+
href={createHref(item.href)}
|
|
128
|
+
{...(domain !== "dub.co" && {
|
|
129
|
+
onClick: () => {
|
|
130
|
+
va.track("Referred from custom domain", {
|
|
131
|
+
domain,
|
|
132
|
+
medium: `footer item (${item.name})`,
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
})}
|
|
136
|
+
className="text-sm text-gray-500 hover:text-gray-900"
|
|
137
|
+
>
|
|
138
|
+
{item.name}
|
|
139
|
+
</Link>
|
|
140
|
+
</li>
|
|
141
|
+
))}
|
|
142
|
+
</ul>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
<div className="md:grid md:grid-cols-2 md:gap-8">
|
|
146
|
+
<div>
|
|
147
|
+
<h3 className="text-sm font-semibold text-gray-600">Tools</h3>
|
|
148
|
+
<ul role="list" className="mt-4 space-y-4">
|
|
149
|
+
{navigation.tools.map((item) => (
|
|
150
|
+
<li key={item.name}>
|
|
151
|
+
<Link
|
|
152
|
+
href={createHref(item.href)}
|
|
153
|
+
{...(domain !== "dub.co" && {
|
|
154
|
+
onClick: () => {
|
|
155
|
+
va.track("Referred from custom domain", {
|
|
156
|
+
domain,
|
|
157
|
+
medium: `footer item (${item.name})`,
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
})}
|
|
161
|
+
className="text-sm text-gray-500 hover:text-gray-900"
|
|
162
|
+
>
|
|
163
|
+
{item.name}
|
|
164
|
+
</Link>
|
|
165
|
+
</li>
|
|
166
|
+
))}
|
|
167
|
+
</ul>
|
|
168
|
+
</div>
|
|
169
|
+
<div className="mt-10 md:mt-0">
|
|
170
|
+
<h3 className="text-sm font-semibold text-gray-600">Legal</h3>
|
|
171
|
+
<ul role="list" className="mt-4 space-y-4">
|
|
172
|
+
{navigation.legal.map((item) => (
|
|
173
|
+
<li key={item.name}>
|
|
174
|
+
<Link
|
|
175
|
+
href={createHref(item.href)}
|
|
176
|
+
{...(domain !== "dub.co" && {
|
|
177
|
+
onClick: () => {
|
|
178
|
+
va.track("Referred from custom domain", {
|
|
179
|
+
domain,
|
|
180
|
+
medium: `footer item (${item.name})`,
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
})}
|
|
184
|
+
className="text-sm text-gray-500 hover:text-gray-900"
|
|
185
|
+
>
|
|
186
|
+
{item.name}
|
|
187
|
+
</Link>
|
|
188
|
+
</li>
|
|
189
|
+
))}
|
|
190
|
+
</ul>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
<div className="mt-16 border-t border-gray-900/10 pt-8 sm:mt-20 lg:mt-24">
|
|
196
|
+
<p className="text-sm leading-5 text-gray-500">
|
|
197
|
+
© {new Date().getFullYear()} Dub.co
|
|
198
|
+
</p>
|
|
199
|
+
</div>
|
|
200
|
+
</MaxWidthWrapper>
|
|
201
|
+
</footer>
|
|
202
|
+
);
|
|
203
|
+
}
|
package/src/form.tsx
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { cn } from "@dub/utils";
|
|
2
|
+
import { InputHTMLAttributes, ReactNode, useMemo, useState } from "react";
|
|
3
|
+
import { Button } from "./button";
|
|
4
|
+
|
|
5
|
+
export function Form({
|
|
6
|
+
title,
|
|
7
|
+
description,
|
|
8
|
+
inputData,
|
|
9
|
+
helpText,
|
|
10
|
+
buttonText = "Save Changes",
|
|
11
|
+
disabledTooltip,
|
|
12
|
+
handleSubmit,
|
|
13
|
+
}: {
|
|
14
|
+
title: string;
|
|
15
|
+
description: string;
|
|
16
|
+
inputData: InputHTMLAttributes<HTMLInputElement>;
|
|
17
|
+
helpText?: string;
|
|
18
|
+
buttonText?: string;
|
|
19
|
+
disabledTooltip?: string | ReactNode;
|
|
20
|
+
handleSubmit: (data: any) => Promise<any>;
|
|
21
|
+
}) {
|
|
22
|
+
const [value, setValue] = useState(inputData.defaultValue);
|
|
23
|
+
const [saving, setSaving] = useState(false);
|
|
24
|
+
const saveDisabled = useMemo(() => {
|
|
25
|
+
return saving || !value || value === inputData.defaultValue;
|
|
26
|
+
}, [saving, value, inputData.defaultValue]);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<form
|
|
30
|
+
onSubmit={async (e) => {
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
setSaving(true);
|
|
33
|
+
await handleSubmit({
|
|
34
|
+
[inputData.name as string]: value,
|
|
35
|
+
});
|
|
36
|
+
setSaving(false);
|
|
37
|
+
}}
|
|
38
|
+
className="rounded-lg border border-gray-200 bg-white"
|
|
39
|
+
>
|
|
40
|
+
<div className="relative flex flex-col space-y-6 p-5 sm:p-10">
|
|
41
|
+
<div className="flex flex-col space-y-3">
|
|
42
|
+
<h2 className="text-xl font-medium">{title}</h2>
|
|
43
|
+
<p className="text-sm text-gray-500">{description}</p>
|
|
44
|
+
</div>
|
|
45
|
+
{typeof inputData.defaultValue === "string" ? (
|
|
46
|
+
<input
|
|
47
|
+
{...inputData}
|
|
48
|
+
type="text"
|
|
49
|
+
required
|
|
50
|
+
disabled={disabledTooltip ? true : false}
|
|
51
|
+
onChange={(e) => setValue(e.target.value)}
|
|
52
|
+
className={cn(
|
|
53
|
+
"w-full max-w-md rounded-md border border-gray-300 text-gray-900 placeholder-gray-300 focus:border-gray-500 focus:outline-none focus:ring-gray-500 sm:text-sm",
|
|
54
|
+
{
|
|
55
|
+
"cursor-not-allowed bg-gray-100 text-gray-400": disabledTooltip,
|
|
56
|
+
},
|
|
57
|
+
)}
|
|
58
|
+
/>
|
|
59
|
+
) : (
|
|
60
|
+
<div className="h-[2.35rem] w-full max-w-md animate-pulse rounded-md bg-gray-200" />
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div className="flex items-center justify-between rounded-b-lg border-t border-gray-200 bg-gray-50 p-3 sm:px-10">
|
|
65
|
+
<p className="text-sm text-gray-500">{helpText}</p>
|
|
66
|
+
<div>
|
|
67
|
+
<Button
|
|
68
|
+
text={buttonText}
|
|
69
|
+
loading={saving}
|
|
70
|
+
disabled={saveDisabled}
|
|
71
|
+
disabledTooltip={disabledTooltip}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</form>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as useCurrentAnchor } from "./use-current-anchor";
|
|
2
|
+
export { default as useIntersectionObserver } from "./use-intersection-observer";
|
|
3
|
+
export { default as useLocalStorage } from "./use-local-storage";
|
|
4
|
+
export { default as useMediaQuery } from "./use-media-query";
|
|
5
|
+
export { default as useScroll } from "./use-scroll";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export default function useCurrentAnchor() {
|
|
4
|
+
const [currentAnchor, setCurrentAnchor] = useState<string | null>(null);
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const mdxContainer: HTMLElement | null = document.querySelector(
|
|
8
|
+
"[data-mdx-container]",
|
|
9
|
+
);
|
|
10
|
+
if (!mdxContainer) return;
|
|
11
|
+
|
|
12
|
+
const offsetTop = mdxContainer.offsetTop - 1;
|
|
13
|
+
|
|
14
|
+
const observer = new IntersectionObserver(
|
|
15
|
+
(entries) => {
|
|
16
|
+
let currentEntry = entries[0];
|
|
17
|
+
if (!currentEntry) return;
|
|
18
|
+
|
|
19
|
+
const offsetBottom =
|
|
20
|
+
(currentEntry.rootBounds?.height || 0) * 0.3 + offsetTop;
|
|
21
|
+
|
|
22
|
+
for (let i = 1; i < entries.length; i++) {
|
|
23
|
+
const entry = entries[i];
|
|
24
|
+
if (!entry) break;
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
entry.boundingClientRect.top <
|
|
28
|
+
currentEntry.boundingClientRect.top ||
|
|
29
|
+
currentEntry.boundingClientRect.bottom < offsetTop
|
|
30
|
+
) {
|
|
31
|
+
currentEntry = entry;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let target: Element | undefined = currentEntry.target;
|
|
36
|
+
|
|
37
|
+
// if the target is too high up, we need to find the next sibling
|
|
38
|
+
while (target && target.getBoundingClientRect().bottom < offsetTop) {
|
|
39
|
+
target = siblings.get(target)?.next;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// if the target is too low, we need to find the previous sibling
|
|
43
|
+
while (target && target.getBoundingClientRect().top > offsetBottom) {
|
|
44
|
+
target = siblings.get(target)?.prev;
|
|
45
|
+
}
|
|
46
|
+
if (target) setCurrentAnchor(target.getAttribute("href"));
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
threshold: 1,
|
|
50
|
+
rootMargin: `-${offsetTop}px 0px 0px 0px`,
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const siblings = new Map();
|
|
55
|
+
|
|
56
|
+
const anchors = mdxContainer?.querySelectorAll("[data-mdx-heading]");
|
|
57
|
+
anchors.forEach((anchor) => observer.observe(anchor));
|
|
58
|
+
|
|
59
|
+
return () => {
|
|
60
|
+
observer.disconnect();
|
|
61
|
+
};
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
return currentAnchor?.replace("#", "");
|
|
65
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { RefObject, useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
interface Args extends IntersectionObserverInit {
|
|
4
|
+
freezeOnceVisible?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default function useIntersectionObserver(
|
|
8
|
+
elementRef: RefObject<Element>,
|
|
9
|
+
{
|
|
10
|
+
threshold = 0,
|
|
11
|
+
root = null,
|
|
12
|
+
rootMargin = "0%",
|
|
13
|
+
freezeOnceVisible = false,
|
|
14
|
+
}: Args,
|
|
15
|
+
): IntersectionObserverEntry | undefined {
|
|
16
|
+
const [entry, setEntry] = useState<IntersectionObserverEntry>();
|
|
17
|
+
|
|
18
|
+
const frozen = entry?.isIntersecting && freezeOnceVisible;
|
|
19
|
+
|
|
20
|
+
const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {
|
|
21
|
+
setEntry(entry);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const node = elementRef?.current; // DOM Ref
|
|
26
|
+
const hasIOSupport = !!window.IntersectionObserver;
|
|
27
|
+
|
|
28
|
+
if (!hasIOSupport || frozen || !node) return;
|
|
29
|
+
|
|
30
|
+
const observerParams = { threshold, root, rootMargin };
|
|
31
|
+
const observer = new IntersectionObserver(updateEntry, observerParams);
|
|
32
|
+
|
|
33
|
+
observer.observe(node);
|
|
34
|
+
|
|
35
|
+
return () => observer.disconnect();
|
|
36
|
+
|
|
37
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
38
|
+
}, [elementRef, JSON.stringify(threshold), root, rootMargin, frozen]);
|
|
39
|
+
|
|
40
|
+
return entry;
|
|
41
|
+
}
|