react-cheminfo 0.1.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.
Files changed (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/lib/citation/core/clipboard.d.ts +14 -0
  4. package/lib/citation/core/clipboard.d.ts.map +1 -0
  5. package/lib/citation/core/clipboard.js +25 -0
  6. package/lib/citation/core/clipboard.js.map +1 -0
  7. package/lib/citation/core/download.d.ts +11 -0
  8. package/lib/citation/core/download.d.ts.map +1 -0
  9. package/lib/citation/core/download.js +20 -0
  10. package/lib/citation/core/download.js.map +1 -0
  11. package/lib/citation/core/formats.d.ts +91 -0
  12. package/lib/citation/core/formats.d.ts.map +1 -0
  13. package/lib/citation/core/formats.js +125 -0
  14. package/lib/citation/core/formats.js.map +1 -0
  15. package/lib/citation/core/index.d.ts +10 -0
  16. package/lib/citation/core/index.d.ts.map +1 -0
  17. package/lib/citation/core/index.js +7 -0
  18. package/lib/citation/core/index.js.map +1 -0
  19. package/lib/citation/core/reference.d.ts +30 -0
  20. package/lib/citation/core/reference.d.ts.map +1 -0
  21. package/lib/citation/core/reference.js +9 -0
  22. package/lib/citation/core/reference.js.map +1 -0
  23. package/lib/citation/core/render.d.ts +21 -0
  24. package/lib/citation/core/render.d.ts.map +1 -0
  25. package/lib/citation/core/render.js +74 -0
  26. package/lib/citation/core/render.js.map +1 -0
  27. package/lib/citation/core/segments.d.ts +47 -0
  28. package/lib/citation/core/segments.d.ts.map +1 -0
  29. package/lib/citation/core/segments.js +134 -0
  30. package/lib/citation/core/segments.js.map +1 -0
  31. package/lib/citation/ui/CitationMenu.d.ts +14 -0
  32. package/lib/citation/ui/CitationMenu.d.ts.map +1 -0
  33. package/lib/citation/ui/CitationMenu.js +63 -0
  34. package/lib/citation/ui/CitationMenu.js.map +1 -0
  35. package/lib/citation/ui/CitationPreview.d.ts +18 -0
  36. package/lib/citation/ui/CitationPreview.d.ts.map +1 -0
  37. package/lib/citation/ui/CitationPreview.js +55 -0
  38. package/lib/citation/ui/CitationPreview.js.map +1 -0
  39. package/lib/citation/ui/CiteButton.d.ts +22 -0
  40. package/lib/citation/ui/CiteButton.d.ts.map +1 -0
  41. package/lib/citation/ui/CiteButton.js +15 -0
  42. package/lib/citation/ui/CiteButton.js.map +1 -0
  43. package/lib/citation/ui/entries.d.ts +45 -0
  44. package/lib/citation/ui/entries.d.ts.map +1 -0
  45. package/lib/citation/ui/entries.js +52 -0
  46. package/lib/citation/ui/entries.js.map +1 -0
  47. package/lib/citation/ui/index.d.ts +7 -0
  48. package/lib/citation/ui/index.d.ts.map +1 -0
  49. package/lib/citation/ui/index.js +4 -0
  50. package/lib/citation/ui/index.js.map +1 -0
  51. package/lib/core.d.ts +3 -0
  52. package/lib/core.d.ts.map +1 -0
  53. package/lib/core.js +3 -0
  54. package/lib/core.js.map +1 -0
  55. package/lib/ecosystem/core/index.d.ts +3 -0
  56. package/lib/ecosystem/core/index.d.ts.map +1 -0
  57. package/lib/ecosystem/core/index.js +2 -0
  58. package/lib/ecosystem/core/index.js.map +1 -0
  59. package/lib/ecosystem/core/sites.d.ts +57 -0
  60. package/lib/ecosystem/core/sites.d.ts.map +1 -0
  61. package/lib/ecosystem/core/sites.js +105 -0
  62. package/lib/ecosystem/core/sites.js.map +1 -0
  63. package/lib/ecosystem/ui/EcosystemButton.d.ts +24 -0
  64. package/lib/ecosystem/ui/EcosystemButton.d.ts.map +1 -0
  65. package/lib/ecosystem/ui/EcosystemButton.js +14 -0
  66. package/lib/ecosystem/ui/EcosystemButton.js.map +1 -0
  67. package/lib/ecosystem/ui/EcosystemMenu.d.ts +17 -0
  68. package/lib/ecosystem/ui/EcosystemMenu.d.ts.map +1 -0
  69. package/lib/ecosystem/ui/EcosystemMenu.js +112 -0
  70. package/lib/ecosystem/ui/EcosystemMenu.js.map +1 -0
  71. package/lib/ecosystem/ui/index.d.ts +7 -0
  72. package/lib/ecosystem/ui/index.d.ts.map +1 -0
  73. package/lib/ecosystem/ui/index.js +4 -0
  74. package/lib/ecosystem/ui/index.js.map +1 -0
  75. package/lib/ecosystem/ui/marks.d.ts +18 -0
  76. package/lib/ecosystem/ui/marks.d.ts.map +1 -0
  77. package/lib/ecosystem/ui/marks.js +43 -0
  78. package/lib/ecosystem/ui/marks.js.map +1 -0
  79. package/lib/shared/ui/MenuButton.d.ts +34 -0
  80. package/lib/shared/ui/MenuButton.d.ts.map +1 -0
  81. package/lib/shared/ui/MenuButton.js +18 -0
  82. package/lib/shared/ui/MenuButton.js.map +1 -0
  83. package/lib/shared/ui/index.d.ts +2 -0
  84. package/lib/shared/ui/index.d.ts.map +1 -0
  85. package/lib/shared/ui/index.js +2 -0
  86. package/lib/shared/ui/index.js.map +1 -0
  87. package/lib/ui.d.ts +4 -0
  88. package/lib/ui.d.ts.map +1 -0
  89. package/lib/ui.js +4 -0
  90. package/lib/ui.js.map +1 -0
  91. package/package.json +79 -0
  92. package/src/citation/core/clipboard.ts +32 -0
  93. package/src/citation/core/download.ts +27 -0
  94. package/src/citation/core/formats.ts +169 -0
  95. package/src/citation/core/index.ts +23 -0
  96. package/src/citation/core/reference.ts +33 -0
  97. package/src/citation/core/render.ts +78 -0
  98. package/src/citation/core/segments.ts +170 -0
  99. package/src/citation/ui/CitationMenu.tsx +132 -0
  100. package/src/citation/ui/CitationPreview.tsx +84 -0
  101. package/src/citation/ui/CiteButton.tsx +39 -0
  102. package/src/citation/ui/entries.tsx +144 -0
  103. package/src/citation/ui/index.ts +6 -0
  104. package/src/core.ts +2 -0
  105. package/src/ecosystem/core/index.ts +7 -0
  106. package/src/ecosystem/core/sites.ts +164 -0
  107. package/src/ecosystem/ui/EcosystemButton.tsx +41 -0
  108. package/src/ecosystem/ui/EcosystemMenu.tsx +191 -0
  109. package/src/ecosystem/ui/index.ts +6 -0
  110. package/src/ecosystem/ui/marks.tsx +207 -0
  111. package/src/shared/ui/MenuButton.tsx +67 -0
  112. package/src/shared/ui/index.ts +1 -0
  113. package/src/ui.ts +3 -0
@@ -0,0 +1,191 @@
1
+ import type { CSSProperties, ReactElement } from 'react';
2
+ import { useState } from 'react';
3
+
4
+ import type { EcosystemSite, SiteId } from '../core/sites.ts';
5
+ import { ECOSYSTEM_SITES, siteUrl } from '../core/sites.ts';
6
+
7
+ import { SiteMark } from './marks.tsx';
8
+
9
+ const PANEL_STYLE: CSSProperties = {
10
+ display: 'flex',
11
+ width: 'min(34rem, 90vw)',
12
+ flexDirection: 'column',
13
+ padding: 12,
14
+ gap: 8,
15
+ };
16
+ const HEADING_STYLE: CSSProperties = {
17
+ color: '#5b6875',
18
+ fontSize: '0.75rem',
19
+ fontWeight: 600,
20
+ };
21
+ const GRID_STYLE: CSSProperties = {
22
+ display: 'grid',
23
+ gridTemplateColumns: 'repeat(auto-fit, minmax(15rem, 1fr))',
24
+ gap: 2,
25
+ };
26
+ const TILE_STYLE: CSSProperties = {
27
+ display: 'flex',
28
+ alignItems: 'center',
29
+ padding: '7px 8px',
30
+ border: '1px solid transparent',
31
+ borderRadius: 10,
32
+ color: '#16202c',
33
+ gap: 10,
34
+ textDecoration: 'none',
35
+ transition: 'background 120ms, border-color 120ms, transform 120ms',
36
+ };
37
+ const MARK_HOLDER_STYLE: CSSProperties = {
38
+ display: 'flex',
39
+ transition: 'transform 160ms',
40
+ };
41
+ const TEXT_STYLE: CSSProperties = { minWidth: 0 };
42
+ const NAME_STYLE: CSSProperties = {
43
+ display: 'flex',
44
+ alignItems: 'baseline',
45
+ fontSize: '0.9375rem',
46
+ fontWeight: 700,
47
+ letterSpacing: '-0.01em',
48
+ gap: 6,
49
+ whiteSpace: 'nowrap',
50
+ };
51
+ const HERE_STYLE: CSSProperties = {
52
+ fontSize: '0.625rem',
53
+ fontWeight: 700,
54
+ letterSpacing: '0.04em',
55
+ textTransform: 'uppercase',
56
+ };
57
+ const TAGLINE_STYLE: CSSProperties = {
58
+ color: '#5b6875',
59
+ fontSize: '0.75rem',
60
+ lineHeight: 1.35,
61
+ };
62
+ export interface EcosystemMenuProps {
63
+ /**
64
+ * The site the visitor is already on, which is shown but never linked.
65
+ * @default undefined
66
+ */
67
+ currentSiteId?: SiteId;
68
+ }
69
+
70
+ /**
71
+ * What the ecosystem button opens: every site of the family, each behind its
72
+ * own little logo and the two colours it owns.
73
+ * @param props - The site the visitor is already on.
74
+ * @returns The grid of sites.
75
+ */
76
+ export function EcosystemMenu(props: EcosystemMenuProps): ReactElement {
77
+ const { currentSiteId } = props;
78
+ const [hovered, setHovered] = useState<SiteId | null>(null);
79
+
80
+ return (
81
+ <div className="ecosystem-menu" style={PANEL_STYLE}>
82
+ <div style={HEADING_STYLE}>Our other tools, all in the browser</div>
83
+ <div style={GRID_STYLE}>
84
+ {ECOSYSTEM_SITES.map((site) => (
85
+ <SiteTile
86
+ key={site.id}
87
+ site={site}
88
+ isCurrent={site.id === currentSiteId}
89
+ isHovered={hovered === site.id}
90
+ onHover={setHovered}
91
+ />
92
+ ))}
93
+ </div>
94
+ </div>
95
+ );
96
+ }
97
+
98
+ interface SiteTileProps {
99
+ site: EcosystemSite;
100
+ isCurrent: boolean;
101
+ isHovered: boolean;
102
+ onHover: (id: SiteId | null) => void;
103
+ }
104
+
105
+ /**
106
+ * One site: its mark, its name in its own colours, and what it does.
107
+ * @param props - The site, and whether it is the current or the hovered one.
108
+ * @returns The tile.
109
+ */
110
+ function SiteTile(props: SiteTileProps): ReactElement {
111
+ const { site, isCurrent, isHovered, onHover } = props;
112
+ const lit = isHovered && !isCurrent;
113
+
114
+ const body = (
115
+ <>
116
+ <div
117
+ style={{
118
+ ...MARK_HOLDER_STYLE,
119
+ transform: lit ? 'scale(1.08) rotate(-6deg)' : 'none',
120
+ }}
121
+ >
122
+ <SiteMark site={site} />
123
+ </div>
124
+ <div style={TEXT_STYLE}>
125
+ <div style={NAME_STYLE}>
126
+ <span>
127
+ <span style={{ color: site.brand }}>{site.name.lead}</span>
128
+ {site.name.dot ? <span style={{ color: '#8a96a3' }}>.</span> : null}
129
+ <span style={{ color: site.brandAlt }}>{site.name.alt}</span>
130
+ </span>
131
+ {isCurrent ? (
132
+ <span style={{ ...HERE_STYLE, color: site.brandAlt }}>
133
+ you are here
134
+ </span>
135
+ ) : null}
136
+ </div>
137
+ <div style={TAGLINE_STYLE}>{site.tagline}</div>
138
+ </div>
139
+ </>
140
+ );
141
+
142
+ const style: CSSProperties = {
143
+ ...TILE_STYLE,
144
+ background: tint(site, isCurrent, lit),
145
+ borderColor: borderOf(site, isCurrent, lit),
146
+ transform: lit ? 'translateY(-1px)' : 'none',
147
+ cursor: isCurrent ? 'default' : 'pointer',
148
+ };
149
+
150
+ if (isCurrent) return <div style={style}>{body}</div>;
151
+
152
+ return (
153
+ <a
154
+ style={style}
155
+ href={siteUrl(site)}
156
+ title={site.host}
157
+ target="_blank"
158
+ rel="noreferrer"
159
+ onMouseEnter={() => {
160
+ onHover(site.id);
161
+ }}
162
+ onMouseLeave={() => {
163
+ onHover(null);
164
+ }}
165
+ onFocus={() => {
166
+ onHover(site.id);
167
+ }}
168
+ onBlur={() => {
169
+ onHover(null);
170
+ }}
171
+ >
172
+ {body}
173
+ </a>
174
+ );
175
+ }
176
+
177
+ // A tile lights up in the colour of the site it opens, never in ours: the
178
+ // pointer moving down the grid is what makes the ten pairs of colours read.
179
+ function tint(site: EcosystemSite, isCurrent: boolean, lit: boolean): string {
180
+ if (isCurrent) return '#f5f7fa';
181
+ return lit ? `color-mix(in oklab, ${site.brand} 9%, white)` : 'transparent';
182
+ }
183
+
184
+ function borderOf(
185
+ site: EcosystemSite,
186
+ isCurrent: boolean,
187
+ lit: boolean,
188
+ ): string {
189
+ if (isCurrent) return '#dfe3e8';
190
+ return lit ? `color-mix(in oklab, ${site.brand} 32%, white)` : 'transparent';
191
+ }
@@ -0,0 +1,6 @@
1
+ export type { EcosystemButtonProps } from './EcosystemButton.tsx';
2
+ export { EcosystemButton } from './EcosystemButton.tsx';
3
+ export type { EcosystemMenuProps } from './EcosystemMenu.tsx';
4
+ export { EcosystemMenu } from './EcosystemMenu.tsx';
5
+ export type { SiteMarkProps } from './marks.tsx';
6
+ export { SiteMark } from './marks.tsx';
@@ -0,0 +1,207 @@
1
+ import type { CSSProperties, ReactElement, ReactNode } from 'react';
2
+
3
+ import type { EcosystemSite, SiteId } from '../core/sites.ts';
4
+
5
+ // Every mark is drawn in the same 32×32 box, on a plate of the site's own
6
+ // colour, so ten marks from ten sites still read as one row. Each keeps the
7
+ // geometry of that site's own logo where it has one, and carries the site's
8
+ // answering colour on exactly one element — which is what stops it collapsing
9
+ // into a flat shape at 16 px.
10
+ const MARK_STYLE: CSSProperties = { display: 'block', flex: 'none' };
11
+
12
+ export interface SiteMarkProps {
13
+ /** The site whose mark is drawn, with the two colours it owns. */
14
+ site: EcosystemSite;
15
+ /**
16
+ * Edge of the square the mark is drawn in, in pixels.
17
+ * @default 28
18
+ */
19
+ size?: number;
20
+ }
21
+
22
+ /**
23
+ * The little logo of one site of the family.
24
+ * @param props - The site and the size of its mark.
25
+ * @returns The mark, as an inline SVG.
26
+ */
27
+ export function SiteMark(props: SiteMarkProps): ReactElement {
28
+ const { site, size = 28 } = props;
29
+
30
+ return (
31
+ <svg
32
+ style={MARK_STYLE}
33
+ width={size}
34
+ height={size}
35
+ viewBox="0 0 32 32"
36
+ aria-hidden="true"
37
+ focusable="false"
38
+ >
39
+ <rect
40
+ x={site.mark.edge ? 0.5 : 0}
41
+ y={site.mark.edge ? 0.5 : 0}
42
+ width={site.mark.edge ? 31 : 32}
43
+ height={site.mark.edge ? 31 : 32}
44
+ rx={site.mark.edge ? 6.5 : 7}
45
+ fill={site.mark.plate}
46
+ stroke={site.mark.edge}
47
+ />
48
+ {GLYPHS[site.id](site.mark.accent)}
49
+ </svg>
50
+ );
51
+ }
52
+
53
+ /** Every mark, keyed by site, drawn on top of its plate. */
54
+ const GLYPHS: Record<SiteId, (alt: string) => ReactNode> = {
55
+ // A hexagon with a tail: the structure, and the string it hashes into.
56
+ inchi: (alt) => (
57
+ <g
58
+ fill="none"
59
+ stroke={alt}
60
+ strokeWidth="2.4"
61
+ strokeLinecap="round"
62
+ strokeLinejoin="round"
63
+ >
64
+ <polygon points="13,6.5 7.7,9.6 7.7,15.7 13,18.8 18.3,15.7 18.3,9.6" />
65
+ <path d="M16.4 16.2 25.2 25" />
66
+ </g>
67
+ ),
68
+ // The core carrying three R groups, which is what this tool combines.
69
+ vcl: (alt) => (
70
+ <>
71
+ <g
72
+ fill="none"
73
+ stroke="#ffffff"
74
+ strokeWidth="2"
75
+ strokeLinecap="round"
76
+ strokeLinejoin="round"
77
+ >
78
+ <path d="M16 9.5 10.8 12.5 10.8 18.5 16 21.5 21.2 18.5 21.2 12.5Z" />
79
+ <path d="M16 9.5 16 5.5M21.2 18.5 24.8 20.6M10.8 18.5 7.2 20.6" />
80
+ </g>
81
+ <circle cx="16" cy="4.6" r="2.6" fill={alt} />
82
+ <circle cx="25.6" cy="21.2" r="2.6" fill={alt} />
83
+ <circle cx="6.4" cy="21.2" r="2.6" fill={alt} />
84
+ </>
85
+ ),
86
+ // A ring over the line of characters that writes it down.
87
+ smiles: (alt) => (
88
+ <>
89
+ <path
90
+ d="M16 7.5 22.1 11.1 22.1 18.3 16 21.9 9.9 18.3 9.9 11.1Z"
91
+ fill="none"
92
+ stroke="#ffffff"
93
+ strokeWidth="2.2"
94
+ strokeLinejoin="round"
95
+ />
96
+ <path
97
+ d="M5.5 25.5h21"
98
+ fill="none"
99
+ stroke={alt}
100
+ strokeWidth="3"
101
+ strokeLinecap="round"
102
+ />
103
+ </>
104
+ ),
105
+ // An isotopic pattern: the answer this one is asked for most.
106
+ chemcalc: (alt) => (
107
+ <g strokeLinecap="round" strokeWidth="3.4" fill="none">
108
+ <path d="M9 24V15" stroke="#ffffff" />
109
+ <path d="M16 24V7" stroke={alt} />
110
+ <path d="M23 24V18" stroke="#ffffff" />
111
+ </g>
112
+ ),
113
+ // The official NMRium symbol, from `Logo.tsx` of the NMRium sources: the
114
+ // orange half is theirs, and the half their logo sets in plum is set in white
115
+ // here because the plate already is that plum.
116
+ nmrium: (accent) => (
117
+ <g fillRule="evenodd" transform="translate(5.25 6.25) scale(0.088)">
118
+ <path
119
+ fill={accent}
120
+ d="M.64,31.7A31.67,31.67,0,0,1,54.72,9.29l0,0A31.66,31.66,0,0,1,64,31.7V190a13.39,13.39,0,0,0,26.77,0V54a31.7,31.7,0,0,1,31.7-31.7V40.58A13.38,13.38,0,0,0,109.13,54V190a31.71,31.71,0,0,1-63.41,0V31.7a13.29,13.29,0,0,0-3.88-9.46l0,0A13.41,13.41,0,0,0,19,31.7V164.82H.64Z"
121
+ />
122
+ <path
123
+ fill="#ffffff"
124
+ d="M154.21,54v68.17a13.34,13.34,0,0,0,3.89,9.47h0A13.39,13.39,0,0,0,181,122.14V99.32h-.06a31.63,31.63,0,0,1,9.29-22.38l0,0a31.65,31.65,0,0,1,44.8,0l0,0a31.58,31.58,0,0,1,9.3,22.39V118H226V99.32a13.3,13.3,0,0,0-3.89-9.46h0a13.44,13.44,0,0,0-18.93,0l0,0a13.3,13.3,0,0,0-3.83,8.19v24.09a31.7,31.7,0,0,1-54.11,22.42v0a31.66,31.66,0,0,1-9.3-22.4V54Z"
125
+ />
126
+ </g>
127
+ ),
128
+ // One formula branching into every structure it can be.
129
+ surge: (alt) => (
130
+ <>
131
+ <g
132
+ fill="none"
133
+ stroke="#ffffff"
134
+ strokeWidth="2"
135
+ strokeLinecap="round"
136
+ strokeLinejoin="round"
137
+ >
138
+ <path d="M7 16h6" />
139
+ <path d="M13 16 25 8.5M13 16h12M13 16 25 23.5" />
140
+ </g>
141
+ <circle cx="25" cy="8.5" r="3" fill={alt} />
142
+ <circle cx="25" cy="16" r="3" fill={alt} />
143
+ <circle cx="25" cy="23.5" r="3" fill={alt} />
144
+ </>
145
+ ),
146
+ // A radical over its radicand: a formula being set.
147
+ tex: (alt) => (
148
+ <>
149
+ <rect x="18.5" y="12" width="8.5" height="8.5" rx="1.6" fill={alt} />
150
+ <path
151
+ d="M4.5 15.5h3l3.6 8.2L16.8 7.5h10.4"
152
+ fill="none"
153
+ stroke="#ffffff"
154
+ strokeWidth="2.4"
155
+ strokeLinecap="round"
156
+ strokeLinejoin="round"
157
+ />
158
+ </>
159
+ ),
160
+ // The site's own favicon: two lobes of opposite phase overlapping end-on,
161
+ // the simplest sigma bond, in the phase palette its viewer renders with.
162
+ lcao: (alt) => (
163
+ <>
164
+ <path
165
+ d="M16 16C16 10.2 13.3 5 9 5S2 10.2 2 16s2.7 11 7 11 7-5.2 7-11z"
166
+ fill="#1565c0"
167
+ />
168
+ <path
169
+ d="M16 16c0 5.8 2.7 11 7 11s7-5.2 7-11-2.7-11-7-11-7 5.2-7 11z"
170
+ fill={alt}
171
+ />
172
+ <circle cx="16" cy="16" r="2.25" fill="#37474f" />
173
+ </>
174
+ ),
175
+ // The pattern that matches anything, which is where everyone starts.
176
+ regexp: (alt) => (
177
+ <text
178
+ x="50%"
179
+ y="53%"
180
+ dominantBaseline="central"
181
+ textAnchor="middle"
182
+ fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace"
183
+ fontSize="18"
184
+ fontWeight="700"
185
+ fill={alt}
186
+ >
187
+ .*
188
+ </text>
189
+ ),
190
+ // Three residues of a chain, one of them the ligand you came for.
191
+ pdb: (alt) => (
192
+ <>
193
+ <g
194
+ stroke="#ffffff"
195
+ strokeWidth="1.9"
196
+ strokeLinecap="round"
197
+ fill="none"
198
+ opacity="0.85"
199
+ >
200
+ <path d="M9.2 12.4 22 15.4 15.6 23Z" />
201
+ </g>
202
+ <circle cx="9.2" cy="12.4" r="3.1" fill="#ffffff" />
203
+ <circle cx="22" cy="15.4" r="3.1" fill="#ffffff" />
204
+ <circle cx="15.6" cy="23" r="3.1" fill={alt} />
205
+ </>
206
+ ),
207
+ };
@@ -0,0 +1,67 @@
1
+ import type { IconName, PopoverNextProps } from '@blueprintjs/core';
2
+ import { PopoverNext } from '@blueprintjs/core';
3
+ import type { CSSProperties, ReactElement } from 'react';
4
+ import { Button } from 'react-science/ui';
5
+
6
+ const HOLDER_STYLE: CSSProperties = {
7
+ display: 'inline-flex',
8
+ alignItems: 'center',
9
+ };
10
+
11
+ /** What every button of a site header takes, whatever its menu holds. */
12
+ export interface HeaderButtonProps {
13
+ /**
14
+ * Whether the button is reduced to its icon — no text, no caret — for a
15
+ * header that has run out of room. The icon still opens the same menu.
16
+ * @default false
17
+ */
18
+ compact?: boolean;
19
+ /**
20
+ * Side the menu opens on.
21
+ * @default 'bottom-end'
22
+ */
23
+ placement?: PopoverNextProps['placement'];
24
+ }
25
+
26
+ export interface MenuButtonProps extends HeaderButtonProps {
27
+ /** Class the holder carries, so a site can reach the button from its bar. */
28
+ className: string;
29
+ /** Glyph of the button. */
30
+ icon: IconName;
31
+ /** Text of the button, and what the pointer and a screen reader are told. */
32
+ label: string;
33
+ /** What the button opens. */
34
+ menu: ReactElement;
35
+ }
36
+
37
+ /**
38
+ * One entry of a site header: a button opening a menu, in the shape every such
39
+ * button of the package takes, so two of them differ only in glyph and menu.
40
+ * @param props - The glyph, the label, the menu, and how it opens.
41
+ * @returns The button and its menu.
42
+ */
43
+ export function MenuButton(props: MenuButtonProps): ReactElement {
44
+ const {
45
+ className,
46
+ icon,
47
+ label,
48
+ menu,
49
+ compact = false,
50
+ placement = 'bottom-end',
51
+ } = props;
52
+
53
+ return (
54
+ <span className={className} style={HOLDER_STYLE}>
55
+ <PopoverNext placement={placement} content={menu}>
56
+ <Button
57
+ variant="minimal"
58
+ icon={icon}
59
+ endIcon={compact ? undefined : 'caret-down'}
60
+ text={compact ? undefined : label}
61
+ aria-label={label}
62
+ title={compact ? label : undefined}
63
+ />
64
+ </PopoverNext>
65
+ </span>
66
+ );
67
+ }
@@ -0,0 +1 @@
1
+ export type { HeaderButtonProps } from './MenuButton.tsx';
package/src/ui.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './citation/ui/index.ts';
2
+ export * from './ecosystem/ui/index.ts';
3
+ export * from './shared/ui/index.ts';