@spree/dashboard-ui 6.0.0 → 6.0.2

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.
@@ -0,0 +1,36 @@
1
+ import { cn } from '../lib/utils'
2
+
3
+ /** A browser with a logo in `BrowserIcon`. */
4
+ export type BrowserName = 'chrome' | 'firefox' | 'opera' | 'safari'
5
+
6
+ // Path data from Simple Icons (https://simpleicons.org, CC0), copied rather
7
+ // than depended on: the package is a single 5 MB module. Microsoft's logos,
8
+ // Edge's among them, are no longer in Simple Icons.
9
+ const LOGOS: Record<BrowserName, { color: string; path: string }> = {
10
+ chrome: {
11
+ color: '#4285F4',
12
+ path: 'M12 0C8.21 0 4.831 1.757 2.632 4.501l3.953 6.848A5.454 5.454 0 0 1 12 6.545h10.691A12 12 0 0 0 12 0zM1.931 5.47A11.943 11.943 0 0 0 0 12c0 6.012 4.42 10.991 10.189 11.864l3.953-6.847a5.45 5.45 0 0 1-6.865-2.29zm13.342 2.166a5.446 5.446 0 0 1 1.45 7.09l.002.001h-.002l-5.344 9.257c.206.01.413.016.621.016 6.627 0 12-5.373 12-12 0-1.54-.29-3.011-.818-4.364zM12 16.364a4.364 4.364 0 1 1 0-8.728 4.364 4.364 0 0 1 0 8.728Z',
13
+ },
14
+ firefox: {
15
+ color: '#FF7139',
16
+ path: 'M8.824 7.287c.008 0 .004 0 0 0zm-2.8-1.4c.006 0 .003 0 0 0zm16.754 2.161c-.505-1.215-1.53-2.528-2.333-2.943.654 1.283 1.033 2.57 1.177 3.53l.002.02c-1.314-3.278-3.544-4.6-5.366-7.477-.091-.147-.184-.292-.273-.446a3.545 3.545 0 01-.13-.24 2.118 2.118 0 01-.172-.46.03.03 0 00-.027-.03.038.038 0 00-.021 0l-.006.001a.037.037 0 00-.01.005L15.624 0c-2.585 1.515-3.657 4.168-3.932 5.856a6.197 6.197 0 00-2.305.587.297.297 0 00-.147.37c.057.162.24.24.396.17a5.622 5.622 0 012.008-.523l.067-.005a5.847 5.847 0 011.957.222l.095.03a5.816 5.816 0 01.616.228c.08.036.16.073.238.112l.107.055a5.835 5.835 0 01.368.211 5.953 5.953 0 012.034 2.104c-.62-.437-1.733-.868-2.803-.681 4.183 2.09 3.06 9.292-2.737 9.02a5.164 5.164 0 01-1.513-.292 4.42 4.42 0 01-.538-.232c-1.42-.735-2.593-2.121-2.74-3.806 0 0 .537-2 3.845-2 .357 0 1.38-.998 1.398-1.287-.005-.095-2.029-.9-2.817-1.677-.422-.416-.622-.616-.8-.767a3.47 3.47 0 00-.301-.227 5.388 5.388 0 01-.032-2.842c-1.195.544-2.124 1.403-2.8 2.163h-.006c-.46-.584-.428-2.51-.402-2.913-.006-.025-.343.176-.389.206-.406.29-.787.616-1.136.974-.397.403-.76.839-1.085 1.303a9.816 9.816 0 00-1.562 3.52c-.003.013-.11.487-.19 1.073-.013.09-.026.181-.037.272a7.8 7.8 0 00-.069.667l-.002.034-.023.387-.001.06C.386 18.795 5.593 24 12.016 24c5.752 0 10.527-4.176 11.463-9.661.02-.149.035-.298.052-.448.232-1.994-.025-4.09-.753-5.844z',
17
+ },
18
+ opera: {
19
+ color: '#FF1B2D',
20
+ path: 'M8.051 5.238c-1.328 1.566-2.186 3.883-2.246 6.48v.564c.061 2.598.918 4.912 2.246 6.479 1.721 2.236 4.279 3.654 7.139 3.654 1.756 0 3.4-.537 4.807-1.471C17.879 22.846 15.074 24 12 24c-.192 0-.383-.004-.57-.014C5.064 23.689 0 18.436 0 12 0 5.371 5.373 0 12 0h.045c3.055.012 5.84 1.166 7.953 3.055-1.408-.93-3.051-1.471-4.81-1.471-2.858 0-5.417 1.42-7.14 3.654h.003zM24 12c0 3.556-1.545 6.748-4.002 8.945-3.078 1.5-5.946.451-6.896-.205 3.023-.664 5.307-4.32 5.307-8.74 0-4.422-2.283-8.075-5.307-8.74.949-.654 3.818-1.703 6.896-.205C22.455 5.25 24 8.445 24 12z',
21
+ },
22
+ safari: {
23
+ color: '#006CFF',
24
+ path: 'M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm-.004.953h.006c.063 0 .113.05.113.113v1.842c0 .063-.05.113-.113.113h-.006a.112.112 0 0 1-.113-.113V1.066c0-.063.05-.113.113-.113zm-.941.041c.056.001.104.046.11.104l.077.918a.112.112 0 0 1-.101.12h-.01a.11.11 0 0 1-.12-.1l-.08-.919a.112.112 0 0 1 .102-.12h.01l.012-.003zm1.892 0H12.965a.113.113 0 0 1 .103.121l-.08.92a.111.111 0 0 1-.12.102h-.009a.111.111 0 0 1-.101-.121l.078-.92a.112.112 0 0 1 .111-.102zm-2.838.123a.11.11 0 0 1 .106.092l.32 1.818c.01.06-.03.119-.09.13l-.01.001a.111.111 0 0 1-.128-.09l-.32-1.818a.111.111 0 0 1 .09-.129l.01-.002a.103.103 0 0 1 .022-.002zm3.784.002h.021l.008.002c.061.01.102.07.092.131l-.32 1.814c-.011.062-.07.101-.132.09h-.005a.113.113 0 0 1-.092-.13l.32-1.815a.111.111 0 0 1 .108-.092zm-4.715.203c.048.002.09.035.103.084l.239.893a.112.112 0 0 1-.079.139l-.005.001a.114.114 0 0 1-.14-.08l-.237-.894a.11.11 0 0 1 .078-.137l.006-.002a.123.123 0 0 1 .035-.004zm5.644 0a.11.11 0 0 1 .033.004l.006.002c.06.016.097.079.08.139l-.24.892a.112.112 0 0 1-.137.08l-.005-.002a.114.114 0 0 1-.08-.138l.24-.893a.112.112 0 0 1 .103-.084zm-6.562.285a.11.11 0 0 1 .107.073L9 3.42a.107.107 0 0 1-.064.139l-.012.005a.11.11 0 0 1-.14-.066L8.15 1.76a.11.11 0 0 1 .065-.14l.014-.005a.106.106 0 0 1 .03-.008zm7.469.002c.014 0 .028.001.042.006l.012.006c.057.02.087.082.067.139l-.633 1.738a.11.11 0 0 1-.14.066l-.013-.003A.11.11 0 0 1 15 3.42l.633-1.738a.108.108 0 0 1 .096-.073zm-8.352.366a.112.112 0 0 1 .105.064l.393.838a.112.112 0 0 1-.055.148l-.008.004a.11.11 0 0 1-.146-.054l-.395-.838a.112.112 0 0 1 .055-.149l.008-.004a.11.11 0 0 1 .043-.01zm9.246 0a.11.11 0 0 1 .043.01l.006.003a.11.11 0 0 1 .053.149l-.391.838a.112.112 0 0 1-.148.054l-.006-.002a.112.112 0 0 1-.055-.148l.393-.84a.112.112 0 0 1 .105-.064zm-10.092.44c.04-.002.08.018.102.056l.922 1.597a.113.113 0 0 1-.041.155l-.006.002a.113.113 0 0 1-.154-.041l-.922-1.598a.113.113 0 0 1 .04-.154l.007-.002a.11.11 0 0 1 .052-.016zm10.94.001c.018 0 .035.004.052.014l.004.002a.114.114 0 0 1 .041.156l-.923 1.596a.114.114 0 0 1-.157.04l-.004-.001a.112.112 0 0 1-.04-.155l.925-1.595a.113.113 0 0 1 .102-.057zM5.729 2.93a.11.11 0 0 1 .093.047l.532.753a.114.114 0 0 1-.028.159l-.004.002a.114.114 0 0 1-.158-.028l-.531-.752a.114.114 0 0 1 .027-.158l.006-.002a.113.113 0 0 1 .063-.021zm12.542 0a.11.11 0 0 1 .063.02l.006.003a.112.112 0 0 1 .027.156l-.531.756a.112.112 0 0 1-.156.028l-.006-.004a.112.112 0 0 1-.028-.157l.532-.755a.11.11 0 0 1 .093-.047zm.747.578a.11.11 0 0 1 .08.027l.006.004c.047.04.053.111.013.158L17.932 5.11a.11.11 0 0 1-.157.016l-.005-.006a.11.11 0 0 1-.014-.156l1.185-1.414a.114.114 0 0 1 .077-.041zM4.984 3.51a.11.11 0 0 1 .077.039L6.244 4.96a.112.112 0 0 1-.014.158l-.003.004a.112.112 0 0 1-.159-.014L4.883 3.697a.112.112 0 0 1 .013-.158l.006-.004a.111.111 0 0 1 .082-.025zm-.714.64c.027 0 .055.01.076.032l.658.66a.107.107 0 0 1 0 .152l-.01.01a.107.107 0 0 1-.152 0l-.66-.658a.11.11 0 0 1 0-.155l.01-.01a.111.111 0 0 1 .078-.03zm15.462 0c.028 0 .055.01.077.032l.007.007a.109.109 0 0 1 0 .155l-.658.66a.109.109 0 0 1-.154 0l-.008-.008a.109.109 0 0 1 0-.154l.658-.66a.11.11 0 0 1 .078-.032zm.707.66c.038 0 .071.02.092.075a.112.112 0 0 1-.023.117l-7.606 8.08c-3.084 2.024-6.149 4.04-9.222 6.05-.078.051-.17.082-.211-.028a.112.112 0 0 1 .023-.118l7.594-8.08c3.084-2.023 6.161-4.039 9.234-6.049a.247.247 0 0 1 .12-.046zm-16.824.045a.109.109 0 0 1 .08.026l1.416 1.187a.11.11 0 0 1 .014.157l-.006.005a.11.11 0 0 1-.156.014L3.549 5.057a.109.109 0 0 1-.014-.155l.006-.007a.108.108 0 0 1 .074-.04zm17.336.756c.036 0 .072.017.094.05l.004.003a.114.114 0 0 1-.028.158l-.753.53a.112.112 0 0 1-.157-.028l-.004-.004a.114.114 0 0 1 .028-.158l.754-.53a.113.113 0 0 1 .062-.02zm-17.904.002c.02 0 .042.007.06.02l.76.531c.05.035.06.103.026.152l-.006.01a.109.109 0 0 1-.153.026l-.76-.532a.109.109 0 0 1-.025-.152l.006-.01a.108.108 0 0 1 .092-.045zm-.512.803c.018 0 .036.006.053.016l1.596.923a.111.111 0 0 1 .04.153l-.003.006a.111.111 0 0 1-.153.04L2.473 6.63a.111.111 0 0 1-.041-.152l.004-.006a.11.11 0 0 1 .1-.055zm18.932 0a.11.11 0 0 1 .1.055l.001.004a.113.113 0 0 1-.04.154l-1.596.926a.113.113 0 0 1-.155-.041l-.002-.004a.113.113 0 0 1 .041-.155l1.596-.925a.115.115 0 0 1 .055-.014zm-19.373.846c.014 0 .029.003.043.01l.838.392a.11.11 0 0 1 .052.147l-.004.01a.11.11 0 0 1-.146.052l-.838-.393a.11.11 0 0 1-.053-.146l.004-.01a.109.109 0 0 1 .104-.062zm19.81.002a.11.11 0 0 1 .106.062l.002.008a.11.11 0 0 1-.053.146l-.838.393a.11.11 0 0 1-.146-.053l-.004-.008a.11.11 0 0 1 .052-.146l.838-.393a.11.11 0 0 1 .043-.01zm-20.183.88c.014 0 .028.001.043.006l1.732.631a.112.112 0 0 1 .067.145l-.002.006a.11.11 0 0 1-.143.066l-1.732-.63a.113.113 0 0 1-.069-.145l.002-.004a.115.115 0 0 1 .102-.074zm20.549 0a.113.113 0 0 1 .11.075l.003.004a.115.115 0 0 1-.069.146l-1.732.629a.112.112 0 0 1-.145-.066l-.001-.006a.113.113 0 0 1 .068-.145l1.732-.63a.11.11 0 0 1 .034-.006zm-20.836.909a.11.11 0 0 1 .033.004l.892.24c.06.016.096.077.08.137l-.002.007a.11.11 0 0 1-.136.079l-.895-.239a.113.113 0 0 1-.078-.138l.002-.006a.113.113 0 0 1 .104-.084zm21.13.002a.115.115 0 0 1 .106.084v.004a.112.112 0 0 1-.078.138l-.893.239a.112.112 0 0 1-.138-.079v-.005a.112.112 0 0 1 .078-.14l.892-.237a.11.11 0 0 1 .033-.004zm-21.335.93.023.001 1.814.323c.062.01.101.069.09.13v.006a.111.111 0 0 1-.13.09l-1.815-.322a.113.113 0 0 1-.092-.131l.002-.006a.11.11 0 0 1 .108-.092zm21.519.001h.022c.052.002.1.038.109.092v.006c.01.062-.03.12-.092.13l-1.814.321a.113.113 0 0 1-.131-.092v-.005a.113.113 0 0 1 .092-.131l1.814-.32zm-21.644.944h.011l.922.084a.11.11 0 0 1 .102.119l-.002.01a.11.11 0 0 1-.121.1l-.922-.083a.11.11 0 0 1-.1-.12v-.009a.111.111 0 0 1 .11-.101zm21.779.002h.012c.056 0 .106.043.11.101v.008a.111.111 0 0 1-.1.121l-.923.08a.111.111 0 0 1-.12-.101v-.008a.111.111 0 0 1 .1-.121l.92-.08zm-11.82.73L6.091 16.95c2.02-1.324 4.039-2.646 6.066-3.976l-1.095-1.31zm11.87.219c.063 0 .114.05.114.113v.004c0 .063-.05.113-.113.113l-1.844.004a.113.113 0 0 1-.113-.113v-.004c0-.063.05-.113.113-.113l1.844-.004zm-21.869.002h1.844c.062 0 .112.05.112.111v.008c0 .062-.05.111-.112.111H1.064a.111.111 0 0 1-.11-.111v-.008c0-.061.049-.111.11-.111zm.952.875h.011a.11.11 0 0 1 .11.101v.006a.111.111 0 0 1-.102.121l-.922.08a.11.11 0 0 1-.119-.101l-.002-.006a.111.111 0 0 1 .102-.121l.922-.08zm19.955 0h.011l.922.08a.11.11 0 0 1 .102.119v.008a.112.112 0 0 1-.121.101l-.922-.08a.11.11 0 0 1-.102-.119v-.008a.111.111 0 0 1 .11-.101zm-18.924.705c.053.001.098.04.107.094l.002.004c.011.061-.03.12-.092.13l-1.812.32a.113.113 0 0 1-.13-.091v-.004a.115.115 0 0 1 .09-.133l1.811-.318a.117.117 0 0 1 .024-.002zm17.902 0c.008 0 .016 0 .024.002l1.816.32c.061.011.1.07.09.131v.004a.113.113 0 0 1-.131.092l-1.816-.32a.112.112 0 0 1-.09-.131v-.004a.113.113 0 0 1 .107-.094zM2.332 14.477a.11.11 0 0 1 .104.082l.002.005c.016.06-.02.121-.08.137l-.891.24a.112.112 0 0 1-.137-.08l-.002-.006a.112.112 0 0 1 .08-.136l.89-.239a.112.112 0 0 1 .034-.003zm19.332 0c.011 0 .024 0 .035.003l.893.239c.06.016.096.077.08.136l-.002.006a.111.111 0 0 1-.137.078l-.894-.238a.111.111 0 0 1-.078-.137l.002-.005a.109.109 0 0 1 .101-.082zm-18.213.517a.11.11 0 0 1 .11.074l.002.004a.112.112 0 0 1-.067.145l-1.732.63a.113.113 0 0 1-.145-.068l-.002-.004a.113.113 0 0 1 .069-.144L3.418 15a.11.11 0 0 1 .033-.006zm17.086 0c.015 0 .029 0 .043.006l1.734.63a.111.111 0 0 1 .067.143l-.002.008a.111.111 0 0 1-.143.067l-1.734-.631a.111.111 0 0 1-.066-.143l.002-.008a.111.111 0 0 1 .1-.072zM2.92 16.117a.109.109 0 0 1 .103.063l.004.01a.108.108 0 0 1-.052.144l-.838.393a.11.11 0 0 1-.147-.055l-.004-.008a.11.11 0 0 1 .053-.146l.838-.391a.112.112 0 0 1 .043-.01zm18.158 0a.11.11 0 0 1 .043.01l.838.39c.056.027.08.093.055.149l-.002.004a.112.112 0 0 1-.149.055l-.838-.391a.112.112 0 0 1-.054-.148l.002-.004a.112.112 0 0 1 .105-.065zm-16.957.315c.04-.001.078.02.1.056l.004.004a.11.11 0 0 1-.041.153l-1.596.921a.113.113 0 0 1-.154-.04l-.002-.005a.113.113 0 0 1 .04-.154l1.596-.922a.109.109 0 0 1 .053-.013zm15.756 0c.018 0 .036.004.053.013l1.597.924a.11.11 0 0 1 .041.152l-.002.004a.11.11 0 0 1-.152.041l-1.598-.921a.113.113 0 0 1-.04-.155l.001-.002a.111.111 0 0 1 .1-.056zm.328 1.193a.11.11 0 0 1 .06.021l.758.534c.05.035.061.102.026.152l-.004.008a.111.111 0 0 1-.154.027l-.756-.535a.109.109 0 0 1-.028-.152l.006-.008a.11.11 0 0 1 .092-.047zm-16.412.002c.035 0 .072.016.094.047l.004.008a.109.109 0 0 1-.028.152l-.756.531a.108.108 0 0 1-.152-.025l-.006-.008a.109.109 0 0 1 .028-.152l.755-.534a.107.107 0 0 1 .061-.019zm15.162.102a.112.112 0 0 1 .082.025l1.414 1.187a.11.11 0 0 1 .014.157l-.004.004a.113.113 0 0 1-.158.013L18.89 17.93a.11.11 0 0 1-.014-.157l.004-.005a.108.108 0 0 1 .074-.04zm-12.812 1.12a.11.11 0 0 1 .08.026l.007.008a.11.11 0 0 1 .014.154L5.06 20.451a.11.11 0 0 1-.155.012l-.008-.006a.11.11 0 0 1-.013-.154l1.185-1.414a.11.11 0 0 1 .075-.04zm11.703 0c.032 0 .065.015.088.042l1.181 1.41c.04.048.035.12-.013.16l-.002.002a.114.114 0 0 1-.16-.014l-1.182-1.41a.114.114 0 0 1 .013-.16l.002-.002a.115.115 0 0 1 .073-.027zm-12.928.114c.027 0 .054.01.074.031l.014.012a.107.107 0 0 1 0 .15l-.662.66a.105.105 0 0 1-.149 0l-.011-.011a.105.105 0 0 1 0-.149l.66-.662a.105.105 0 0 1 .074-.031zm14.164 0c.027 0 .053.01.074.031l.66.662a.106.106 0 0 1 0 .15l-.011.012a.106.106 0 0 1-.15-.002l-.66-.66a.106.106 0 0 1 .001-.15l.01-.012a.108.108 0 0 1 .076-.031zm-11.627.797c.018 0 .034.006.05.015l.007.004a.11.11 0 0 1 .04.15l-.921 1.598a.11.11 0 0 1-.15.041l-.008-.004a.111.111 0 0 1-.04-.152l.922-1.596a.113.113 0 0 1 .1-.056zm9.088.002a.11.11 0 0 1 .1.054l.925 1.596a.113.113 0 0 1-.04.154h-.005a.11.11 0 0 1-.152-.039l-.926-1.595a.113.113 0 0 1 .041-.155l.004-.002a.108.108 0 0 1 .053-.013zm-10.285.324c.021 0 .043.008.062.021l.004.002c.051.036.063.106.028.157l-.53.755a.112.112 0 0 1-.156.028l-.004-.002a.112.112 0 0 1-.027-.156l.53-.756a.113.113 0 0 1 .093-.05zm11.484.002c.036 0 .072.015.094.047l.53.756c.035.05.023.12-.028.156l-.004.002a.112.112 0 0 1-.156-.028l-.53-.755a.112.112 0 0 1 .028-.157l.004-.002a.112.112 0 0 1 .062-.02zm-8.863.342a.11.11 0 0 1 .043.006l.012.005c.056.02.084.081.064.137l-.633 1.74a.105.105 0 0 1-.136.063l-.014-.004a.106.106 0 0 1-.065-.137l.633-1.74a.107.107 0 0 1 .096-.07zm6.232 0a.107.107 0 0 1 .106.07l.633 1.738a.107.107 0 0 1-.065.137l-.015.006a.107.107 0 0 1-.137-.065L15 20.578a.107.107 0 0 1 .064-.137l.014-.005a.117.117 0 0 1 .033-.006zm-4.695.41c.008 0 .014 0 .021.002l.006.002c.062.01.101.067.09.129l-.318 1.812a.113.113 0 0 1-.131.092l-.004-.002a.111.111 0 0 1-.092-.129l.32-1.812a.113.113 0 0 1 .108-.094zm3.146.002c.008-.002.015 0 .022 0a.111.111 0 0 1 .107.092l.32 1.812c.012.061-.03.12-.091.131l-.004.002a.113.113 0 0 1-.13-.092l-.321-1.812a.113.113 0 0 1 .092-.131l.005-.002zm-5.79.119a.11.11 0 0 1 .042.01l.004.002a.114.114 0 0 1 .055.15l-.393.834a.112.112 0 0 1-.148.055l-.004-.002a.112.112 0 0 1-.055-.149l.393-.836a.112.112 0 0 1 .105-.064zm8.458 0a.108.108 0 0 1 .104.062l.39.84a.11.11 0 0 1-.052.147l-.008.004a.11.11 0 0 1-.146-.055l-.391-.838a.11.11 0 0 1 .053-.146l.008-.004a.11.11 0 0 1 .042-.01zm-4.236.018H12c.063 0 .115.05.115.113l.002 1.84c0 .063-.05.113-.113.113h-.006a.113.113 0 0 1-.113-.113l-.004-1.838c0-.063.05-.115.113-.115zm-2.592.578c.011 0 .022 0 .034.004l.005.002c.06.016.095.077.079.136l-.24.893a.111.111 0 0 1-.137.078l-.006-.002a.111.111 0 0 1-.078-.137l.24-.89a.113.113 0 0 1 .103-.084zm5.196.002a.11.11 0 0 1 .103.082l.24.89a.11.11 0 0 1-.078.137l-.006.002a.11.11 0 0 1-.136-.078l-.24-.89a.11.11 0 0 1 .078-.138l.005-.002a.112.112 0 0 1 .034-.003zm-3.475.302h.01l.008.002c.061.006.107.06.101.121l-.08.92a.112.112 0 0 1-.121.102h-.008a.11.11 0 0 1-.1-.121l.08-.922a.111.111 0 0 1 .11-.102zm1.736 0h.02a.11.11 0 0 1 .107.102l.08.924a.11.11 0 0 1-.101.119l-.008.002a.11.11 0 0 1-.12-.102l-.08-.924a.112.112 0 0 1 .102-.12z',
25
+ },
26
+ }
27
+
28
+ /** A browser's logo in its brand colour. Decorative: pair it with a label. */
29
+ export function BrowserIcon({ browser, className }: { browser: BrowserName; className?: string }) {
30
+ const { color, path } = LOGOS[browser]
31
+ return (
32
+ <svg viewBox="0 0 24 24" fill={color} aria-hidden className={cn('size-4', className)}>
33
+ <path d={path} />
34
+ </svg>
35
+ )
36
+ }
@@ -0,0 +1,66 @@
1
+ import { type ReactNode, useState } from 'react'
2
+ import { cn } from '../lib/utils'
3
+ import { Badge } from '../ui/badge'
4
+ import { Card, CardContent, CardHeader } from '../ui/card'
5
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible'
6
+ import { ChevronRightIcon } from './icons'
7
+
8
+ interface CollapsibleCardProps {
9
+ title: ReactNode
10
+ icon?: ReactNode
11
+ /** Shown as a badge beside the title when above zero, so a closed card still says whether it holds anything. */
12
+ count?: number
13
+ /** Header controls kept outside the toggle, e.g. an Edit button. */
14
+ actions?: ReactNode
15
+ open?: boolean
16
+ defaultOpen?: boolean
17
+ onOpenChange?: (open: boolean) => void
18
+ children: ReactNode
19
+ }
20
+
21
+ /** A secondary card that starts folded to its header, for content most visits don't need. */
22
+ export function CollapsibleCard({
23
+ title,
24
+ icon,
25
+ count = 0,
26
+ actions,
27
+ open,
28
+ defaultOpen = false,
29
+ onOpenChange,
30
+ children,
31
+ }: CollapsibleCardProps) {
32
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen)
33
+ const isOpen = open ?? uncontrolledOpen
34
+
35
+ function setOpen(next: boolean) {
36
+ if (open === undefined) setUncontrolledOpen(next)
37
+ onOpenChange?.(next)
38
+ }
39
+
40
+ return (
41
+ <Card>
42
+ <Collapsible open={isOpen} onOpenChange={setOpen}>
43
+ <CardHeader
44
+ className={cn('flex flex-row items-center gap-2 py-1.5', !isOpen && 'border-b-0')}
45
+ >
46
+ <CollapsibleTrigger className="group -mx-1 flex min-w-0 flex-1 cursor-pointer items-center gap-2 rounded-md px-1 py-1 text-left hover:bg-accent/50">
47
+ <ChevronRightIcon className="size-4 shrink-0 text-muted-foreground transition-transform group-data-[panel-open]:rotate-90 motion-reduce:transition-none" />
48
+ {/* CardTitle's styling on a span: a button may only hold phrasing content. */}
49
+ <span
50
+ data-slot="card-title"
51
+ className="flex items-center gap-2 text-base font-semibold"
52
+ >
53
+ {icon}
54
+ {title}
55
+ </span>
56
+ {count > 0 && <Badge variant="secondary">{count}</Badge>}
57
+ </CollapsibleTrigger>
58
+ {actions}
59
+ </CardHeader>
60
+ <CollapsibleContent>
61
+ <CardContent>{children}</CardContent>
62
+ </CollapsibleContent>
63
+ </Collapsible>
64
+ </Card>
65
+ )
66
+ }
@@ -149,8 +149,10 @@ export {
149
149
  SunIcon,
150
150
  SwatchBookIcon,
151
151
  TableIcon,
152
+ TabletIcon,
152
153
  TagIcon,
153
154
  TagsIcon,
155
+ TerminalIcon,
154
156
  TicketPercentIcon,
155
157
  Trash2Icon,
156
158
  TrashIcon,
@@ -175,3 +177,4 @@ export {
175
177
  XIcon,
176
178
  ZapIcon,
177
179
  } from 'lucide-react'
180
+ export { BrowserIcon, type BrowserName } from './browser-icon'
@@ -1,46 +1,101 @@
1
1
  import { Fragment } from 'react'
2
- import { Card, CardContent, CardHeader, CardTitle } from '../../ui/card'
3
2
  import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '../../ui/empty'
3
+ import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '../../ui/sheet'
4
+ import { CollapsibleCard } from '../collapsible-card'
5
+ import { CopyToClipboardButton } from '../copy-to-clipboard-button'
4
6
  import { BracesIcon } from '../icons'
5
7
 
6
- interface MetadataCardProps {
7
- /**
8
- * Free-form metadata stored on the record. Read-only — apps and integrations
9
- * write to this; admins just see what's there. Always an object (possibly
10
- * empty); the API never returns `null`.
11
- */
12
- metadata: Record<string, unknown>
13
- /** Translated copy (passed by the app — this component stays headless). */
14
- title: string
8
+ /**
9
+ * Free-form metadata stored on a record. Read-only — apps and integrations
10
+ * write to it; admins just see what's there.
11
+ */
12
+ type Metadata = Record<string, unknown> | null | undefined
13
+
14
+ /** The keys external systems (ERP, PIM, CRM) know a record by, as `{ system => key }`. */
15
+ type ExternalReferences = Record<string, string> | null | undefined
16
+
17
+ interface MetadataListProps {
18
+ metadata: Metadata
19
+ externalReferences?: ExternalReferences
20
+ /** Translated copy (passed by the app — these components stay headless). */
15
21
  emptyTitle: string
16
22
  emptyDescription: string
23
+ /** Section headings, shown only when the record has both kinds of data. */
24
+ metadataTitle?: string
25
+ externalReferencesTitle?: string
26
+ /** Accessible label for the copy button next to a system's key. */
27
+ copyLabel?: (system: string) => string
28
+ }
29
+
30
+ export function hasMetadata(metadata: Metadata): metadata is Record<string, unknown> {
31
+ return !!metadata && Object.keys(metadata).length > 0
32
+ }
33
+
34
+ export function hasExternalReferences(
35
+ externalReferences: ExternalReferences,
36
+ ): externalReferences is Record<string, string> {
37
+ return !!externalReferences && Object.keys(externalReferences).length > 0
17
38
  }
18
39
 
19
- export function MetadataCard({ metadata, title, emptyTitle, emptyDescription }: MetadataCardProps) {
20
- const entries = Object.entries(metadata)
40
+ export function MetadataList({
41
+ metadata,
42
+ externalReferences,
43
+ emptyTitle,
44
+ emptyDescription,
45
+ metadataTitle,
46
+ externalReferencesTitle,
47
+ copyLabel,
48
+ }: MetadataListProps) {
49
+ const showMetadata = hasMetadata(metadata)
50
+ const showReferences = hasExternalReferences(externalReferences)
51
+
52
+ if (!showMetadata && !showReferences) {
53
+ return (
54
+ <Empty className="border-0 p-0">
55
+ <EmptyHeader>
56
+ <EmptyMedia variant="icon">
57
+ <BracesIcon />
58
+ </EmptyMedia>
59
+ <EmptyTitle>{emptyTitle}</EmptyTitle>
60
+ <EmptyDescription>{emptyDescription}</EmptyDescription>
61
+ </EmptyHeader>
62
+ </Empty>
63
+ )
64
+ }
65
+
66
+ const both = showMetadata && showReferences
21
67
 
22
68
  return (
23
- <Card>
24
- <CardHeader>
25
- <CardTitle>
26
- <BracesIcon className="size-4" />
27
- {title}
28
- </CardTitle>
29
- </CardHeader>
30
- <CardContent>
31
- {entries.length === 0 ? (
32
- <Empty className="border-0 p-0">
33
- <EmptyHeader>
34
- <EmptyMedia variant="icon">
35
- <BracesIcon />
36
- </EmptyMedia>
37
- <EmptyTitle>{emptyTitle}</EmptyTitle>
38
- <EmptyDescription>{emptyDescription}</EmptyDescription>
39
- </EmptyHeader>
40
- </Empty>
41
- ) : (
69
+ <div className="flex flex-col gap-4">
70
+ {showReferences && (
71
+ <section className="flex flex-col gap-2">
72
+ {externalReferencesTitle && (
73
+ <h4 className="text-xs font-medium text-muted-foreground">{externalReferencesTitle}</h4>
74
+ )}
75
+ <dl className="grid grid-cols-[minmax(160px,1fr)_2fr] items-center gap-x-4 gap-y-1 text-sm">
76
+ {Object.entries(externalReferences).map(([system, key]) => (
77
+ <Fragment key={system}>
78
+ <dt>
79
+ <code className="text-xs text-muted-foreground">{system}</code>
80
+ </dt>
81
+ <dd className="flex min-w-0 items-center gap-1 font-mono text-xs text-foreground/90">
82
+ <span className="break-all">{key}</span>
83
+ {copyLabel && (
84
+ <CopyToClipboardButton value={key} aria-label={copyLabel(system)} />
85
+ )}
86
+ </dd>
87
+ </Fragment>
88
+ ))}
89
+ </dl>
90
+ </section>
91
+ )}
92
+ {showMetadata && (
93
+ <section className="flex flex-col gap-2">
94
+ {both && metadataTitle && (
95
+ <h4 className="text-xs font-medium text-muted-foreground">{metadataTitle}</h4>
96
+ )}
42
97
  <dl className="grid grid-cols-[minmax(160px,1fr)_2fr] gap-x-4 gap-y-2 text-sm">
43
- {entries.map(([key, value]) => (
98
+ {Object.entries(metadata).map(([key, value]) => (
44
99
  <Fragment key={key}>
45
100
  <dt>
46
101
  <code className="text-xs text-muted-foreground">{key}</code>
@@ -51,9 +106,59 @@ export function MetadataCard({ metadata, title, emptyTitle, emptyDescription }:
51
106
  </Fragment>
52
107
  ))}
53
108
  </dl>
54
- )}
55
- </CardContent>
56
- </Card>
109
+ </section>
110
+ )}
111
+ </div>
112
+ )
113
+ }
114
+
115
+ interface MetadataCardProps extends MetadataListProps {
116
+ title: string
117
+ }
118
+
119
+ /** Folded by default: metadata is for integrations, and the entry count says whether there is any. */
120
+ export function MetadataCard({ title, ...listProps }: MetadataCardProps) {
121
+ return (
122
+ <CollapsibleCard
123
+ title={title}
124
+ icon={<BracesIcon className="size-4" />}
125
+ count={
126
+ Object.keys(listProps.metadata ?? {}).length +
127
+ Object.keys(listProps.externalReferences ?? {}).length
128
+ }
129
+ >
130
+ <MetadataList {...listProps} />
131
+ </CollapsibleCard>
132
+ )
133
+ }
134
+
135
+ interface MetadataSheetProps extends MetadataListProps {
136
+ open: boolean
137
+ onOpenChange: (open: boolean) => void
138
+ title: string
139
+ description?: string
140
+ }
141
+
142
+ /** Metadata of a record listed inside another page, opened from its row menu. */
143
+ export function MetadataSheet({
144
+ open,
145
+ onOpenChange,
146
+ title,
147
+ description,
148
+ ...listProps
149
+ }: MetadataSheetProps) {
150
+ return (
151
+ <Sheet open={open} onOpenChange={onOpenChange}>
152
+ <SheetContent>
153
+ <SheetHeader>
154
+ <SheetTitle>{title}</SheetTitle>
155
+ {description && <SheetDescription>{description}</SheetDescription>}
156
+ </SheetHeader>
157
+ <div className="flex-1 overflow-y-auto p-4">
158
+ <MetadataList {...listProps} />
159
+ </div>
160
+ </SheetContent>
161
+ </Sheet>
57
162
  )
58
163
  }
59
164
 
@@ -0,0 +1,74 @@
1
+ import { cn } from '../lib/utils'
2
+
3
+ const R = 6.5
4
+ const CIRCUMFERENCE = 2 * Math.PI * R
5
+
6
+ /** Progress through a checklist as a ring that fills green. */
7
+ export function StepProgressRing({
8
+ value,
9
+ max,
10
+ className,
11
+ }: {
12
+ value: number
13
+ max: number
14
+ className?: string
15
+ }) {
16
+ return (
17
+ <svg viewBox="0 0 16 16" className={cn('size-4 shrink-0 -rotate-90', className)} aria-hidden>
18
+ <circle cx="8" cy="8" r={R} fill="none" strokeWidth="1.5" className="stroke-border" />
19
+ <circle
20
+ cx="8"
21
+ cy="8"
22
+ r={R}
23
+ fill="none"
24
+ strokeWidth="1.5"
25
+ strokeLinecap="round"
26
+ strokeDasharray={CIRCUMFERENCE}
27
+ strokeDashoffset={CIRCUMFERENCE * (1 - value / (max || 1))}
28
+ className="stroke-success"
29
+ />
30
+ </svg>
31
+ )
32
+ }
33
+
34
+ export type StepStatus = 'done' | 'in_progress' | 'todo'
35
+
36
+ /**
37
+ * A checklist step's state: a green check once done, a dashed blue ring while
38
+ * it is started but waiting on someone else (such as a review), and a plain
39
+ * ring before it is started.
40
+ */
41
+ export function StepIcon({ status, className }: { status: StepStatus; className?: string }) {
42
+ const classes = cn('size-4 shrink-0', className)
43
+
44
+ if (status === 'done') {
45
+ return (
46
+ <svg viewBox="0 0 16 16" className={classes} aria-hidden>
47
+ <circle cx="8" cy="8" r="7.5" className="fill-success" />
48
+ <path
49
+ d="M5 8.2 7 10.2 11 6"
50
+ fill="none"
51
+ stroke="white"
52
+ strokeWidth="1.75"
53
+ strokeLinecap="round"
54
+ strokeLinejoin="round"
55
+ />
56
+ </svg>
57
+ )
58
+ }
59
+
60
+ return (
61
+ <svg viewBox="0 0 16 16" className={classes} aria-hidden>
62
+ <circle
63
+ cx="8"
64
+ cy="8"
65
+ r={R}
66
+ fill="none"
67
+ strokeWidth="1.5"
68
+ strokeLinecap="round"
69
+ strokeDasharray={status === 'in_progress' ? '2.5 2.6' : undefined}
70
+ className={status === 'in_progress' ? 'stroke-blue-500' : 'stroke-border'}
71
+ />
72
+ </svg>
73
+ )
74
+ }
package/src/styles.css CHANGED
@@ -1,6 +1,6 @@
1
1
  @import "tailwindcss";
2
2
  @import "tw-animate-css";
3
- @import "shadcn/tailwind.css";
3
+ @import "./shadcn.css";
4
4
  @import "@fontsource-variable/geist";
5
5
  @import "@fontsource-variable/geist-mono";
6
6
 
@@ -450,8 +450,8 @@
450
450
  --radius-sm: 0.4rem;
451
451
  --radius-md: 0.5rem;
452
452
  --radius-lg: 0.625rem;
453
- --radius-xl: 0.875rem;
454
- --radius-2xl: 1.125rem;
453
+ --radius-xl: 0.75rem;
454
+ --radius-2xl: 1rem;
455
455
 
456
456
  /* Sidebar + header tokens */
457
457
  --spacing-sidebar-width: 240px;
package/src/ui/badge.tsx CHANGED
@@ -78,6 +78,9 @@ type StatusTone = 'success' | 'warning' | 'destructive' | 'info' | 'neutral'
78
78
  */
79
79
  const statusToneMap: Record<string, StatusTone> = {
80
80
  active: 'success',
81
+ // Staff accounts: locked after too many failed sign-ins, deactivated by an admin.
82
+ locked: 'warning',
83
+ deactivated: 'neutral',
81
84
  authorized: 'info',
82
85
  complete: 'success',
83
86
  completed: 'success',