@creativecreate/react-footnotes 1.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/README.md ADDED
@@ -0,0 +1,426 @@
1
+ # @creativecreate/react-footnotes
2
+
3
+ A React component library for managing footnotes with citation and special symbol support.
4
+
5
+ Maintaining footnotes with proper numbering across different components and pages can be challenging — you need to manually track footnote order, handle duplicates, manage navigation between references and content, and ensure consistency throughout your application. Additionally, maintaining footnote content becomes difficult when footnotes are scattered across multiple files, making it hard to update or manage them centrally. This package tackles these challenges by providing a context-aware, automatic numbering system that handles everything for you. Use footnotes freely across any component without worrying about order or duplicates; the library automatically coordinates numbering, detects duplicates, and provides smart bidirectional navigation. With flexible content management through a centralized messages file, callback functions, or inline children, you can easily maintain and update footnote content regardless of where your components are located.
6
+
7
+ ## Features
8
+
9
+ - 📝 Support for both citation (numbered) and special (symbol) footnotes
10
+ - 🔄 Context-aware coordination and auto numbering: use footnotes freely across different components without manually tracking the order — the library handles everything automatically
11
+ - 🔁 Smart duplicate detection: multiple references to the same footnote automatically share the same number, preventing duplicate entries
12
+ - 🔗 Smart bidirectional navigation: navigate between footnote references and content, with intelligent tracking that traces back to the current reference location even when duplicate references exist
13
+ - 🎯 Flexible content sources: provide footnote content via messages file, callback function (`getContent`), or inline children — choose the approach that fits your needs
14
+ - 🌐 Framework-agnostic: Compatible with Next.js App Router, Remix, Vite, and other React frameworks
15
+ - ♿ Web accessibility compatible: built with ARIA attributes, semantic HTML, keyboard navigation support, and screen reader compatibility following WAI-ARIA best practices
16
+ - 🎨 Fully customizable with custom CSS to match your design style, with built-in responsive design support for all device sizes
17
+ - 📦 TypeScript support
18
+ - 🔄 Route-aware footnote reset for SPA navigation
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @creativecreate/react-footnotes
24
+ ```
25
+
26
+ ## Basic Usage
27
+
28
+ ### 1. Create your messages file
29
+
30
+ The package includes a `messages.json` file as a **guideline/template**. You should copy it to your project (e.g., to your `src` directory) and customize it with your own footnote content. You cannot import it directly from the package module.
31
+
32
+ **Step 1:** Copy `messages.json` from the package to your project (e.g., `src/messages.json`)
33
+
34
+ **Step 2:** Customize it with your footnote content:
35
+
36
+ ```json
37
+ {
38
+ "footnotes": {
39
+ "citation": {
40
+ "smith-2023-study": "Smith, J., et al. 'Climate Change Impacts on Agriculture.' Nature Climate Change, vol. 13, no. 4, 2023, pp. 245-260.",
41
+ "who-guidelines": "World Health Organization. 'Air Quality Guidelines: Global Update 2021.' WHO Press, 2021.",
42
+ "johnson-research": "Johnson, M. 'Sustainable Energy Solutions.' Renewable Energy Journal, vol. 45, 2023, pp. 112-128."
43
+ },
44
+ "special": {
45
+ "editor-note": "Editor's note: This data was updated on March 15, 2024 to reflect the latest findings.",
46
+ "methodology-note": "Methodology: Survey conducted across 50 countries with a sample size of 10,000 participants."
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ **Step 3:** Import it from your project:
53
+
54
+ ```tsx
55
+ import messagesData from './messages.json'; // Import from your project, not from the package
56
+
57
+ // Extract footnotes from the messages structure
58
+ const messages = messagesData.footnotes;
59
+ ```
60
+
61
+ ### 2. Wrap your app with FootnoteProvider and provide messages
62
+
63
+ ```tsx
64
+ import { FootnoteProvider } from '@creativecreate/react-footnotes';
65
+ import messagesData from './messages.json'; // Your custom messages file from your project
66
+
67
+ // Extract footnotes from the messages structure
68
+ const messages = messagesData.footnotes;
69
+
70
+ function App() {
71
+ return (
72
+ <FootnoteProvider
73
+ messages={messages}
74
+ // Alternative: see section - using React router
75
+ pathname={`${window.location.pathname}${window.location.search}`}
76
+ >
77
+ {/* Your app content */}
78
+ </FootnoteProvider>
79
+ );
80
+ }
81
+ ```
82
+
83
+ ### 3. Add footnote references in your content
84
+
85
+ ```tsx
86
+ import { Footnote } from '@creativecreate/react-footnotes';
87
+
88
+ function MyComponent() {
89
+ return (
90
+ <div>
91
+ <p>
92
+ Recent research has shown significant impacts of climate change on agricultural productivity<Footnote id="smith-2023-study" type="citation" />.
93
+ </p>
94
+ <p>
95
+ According to WHO guidelines<Footnote id="who-guidelines" type="citation" />, air quality standards have been updated<Footnote id="editor-note" type="special" />.
96
+ </p>
97
+ </div>
98
+ );
99
+ }
100
+ ```
101
+
102
+ ### 4. Render the footnote list
103
+
104
+ ```tsx
105
+ import { FootnoteList } from '@creativecreate/react-footnotes';
106
+
107
+ function Footer() {
108
+ return (
109
+ <footer>
110
+ {/* render footnote list */}
111
+ <FootnoteList />
112
+ </footer>
113
+ );
114
+ }
115
+ ```
116
+
117
+ **Example output:**
118
+
119
+ ![Footnotes rendered in footer](./example.png)
120
+
121
+ ## Advanced Usage
122
+
123
+ ### Using getContent prop for dynamic footnotes
124
+
125
+ The `getContent` prop allows you to provide footnote content programmatically, overriding messages from the provider. This is useful for dynamic content or when you want to generate footnotes based on component state or props:
126
+
127
+ ```tsx
128
+ import { Footnote } from '@creativecreate/react-footnotes';
129
+ import { useState, useEffect } from 'react';
130
+
131
+ function MyComponent() {
132
+ const [dynamicCitations, setDynamicCitations] = useState<{
133
+ citation?: { [key: string]: string };
134
+ special?: { [key: string]: string };
135
+ }>({});
136
+
137
+ useEffect(() => {
138
+ // Fetch citations asynchronously
139
+ fetchCitations().then(setDynamicCitations);
140
+ }, []);
141
+
142
+ // Custom function to generate footnote content
143
+ const getContent = (id: string, type: 'citation' | 'special') => {
144
+ if (type && id) {
145
+ return dynamicCitations[type]?.[id];
146
+ }
147
+ return null;
148
+ };
149
+
150
+ return (
151
+ <div>
152
+ <p>
153
+ This text has a dynamic footnote
154
+ <Footnote id="dynamic_citation_1" type="citation" getContent={getContent} />.
155
+ </p>
156
+ <p>
157
+ And another one
158
+ <Footnote id="dynamic_special_1" type="special" getContent={getContent} />.
159
+ </p>
160
+ </div>
161
+ );
162
+ }
163
+ ```
164
+
165
+ **When to use `getContent`:**
166
+ - When footnote content depends on component state or props
167
+ - When you need to fetch content from an API or external source
168
+ - When you want to override provider messages for specific footnotes
169
+ - When content needs to be computed dynamically
170
+
171
+ ### Using children prop for inline footnote content
172
+
173
+ The `children` prop provides the highest priority for footnote content and allows you to pass content directly, including formatted text or React components:
174
+
175
+ ```tsx
176
+ import { Footnote } from '@creativecreate/react-footnotes';
177
+ import {Link} from '~/components/Link';
178
+
179
+ function MyComponent() {
180
+ return (
181
+ <div>
182
+ <p>
183
+ You can also include formatted content
184
+ <Footnote id="inline-2" type="special">
185
+ <strong>Bold text</strong> and <em>italic text</em> work in children prop.
186
+ </Footnote>.
187
+ </p>
188
+ <p>
189
+ Or even React components
190
+ <Footnote id="inline-3" type="citation">
191
+ Visit <Link href="https://example.com">example.com</Link> for more info.
192
+ </Footnote>.
193
+ </p>
194
+ </div>
195
+ );
196
+ }
197
+ ```
198
+
199
+ **When to use `children`:**
200
+ - When you want to provide footnote content directly inline
201
+ - When you need to include formatted text or React components
202
+ - When content is specific to a single footnote instance
203
+ - When you want the highest priority (overrides both `getContent` and messages)
204
+
205
+ **Priority order:**
206
+ 1. `children` prop (highest priority)
207
+ 2. `getContent` prop
208
+ 3. Messages from `FootnoteProvider` (lowest priority)
209
+
210
+ ### Customizing FootnoteList styling
211
+
212
+ ```tsx
213
+ import { FootnoteList } from '@creativecreate/react-footnotes';
214
+
215
+ function MyPage() {
216
+ return (
217
+ <FootnoteList
218
+ className="my-custom-list-class" // Class for the <ol> wrapper
219
+ itemClassName="my-item-class" // Class for each <li> item
220
+ itemSupClassName="my-sup-class" // Class for the <sup> symbol in items
221
+ itemContentClassName="my-content-class" // Class for the content <span> in items
222
+ itemBackLinkClassName="my-back-link-class" // Class for the back link <button>
223
+ itemBackLinkIconClassName="my-icon-class" // Class for the back link icon <small>
224
+ order="citation-first" // Show citations before special footnotes
225
+ />
226
+ );
227
+ }
228
+ ```
229
+
230
+ ### Customizing Footnote styling
231
+
232
+ ```tsx
233
+ import { Footnote } from '@creativecreate/react-footnotes';
234
+
235
+ function MyComponent() {
236
+ return (
237
+ <div>
238
+ <p>
239
+ This text has a customized footnote
240
+ <Footnote
241
+ id="example1"
242
+ type="citation"
243
+ className="my-footnote-ref-class" // Class for the footnote reference <button>
244
+ supClassName="my-footnote-sup-class" // Class for the <sup> element
245
+ />.
246
+ </p>
247
+ </div>
248
+ );
249
+ }
250
+ ```
251
+
252
+ ### Using with React Router
253
+
254
+ When using React Router, pass the `pathname` prop using `useLocation()` to enable automatic footnote reset on route changes:
255
+
256
+ ```tsx
257
+ import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom';
258
+ import { FootnoteProvider } from '@creativecreate/react-footnotes';
259
+
260
+ function AppContent() {
261
+ const location = useLocation();
262
+
263
+ return (
264
+ <FootnoteProvider
265
+ messages={messages}
266
+ pathname={`${location.pathname}${location.search}`}
267
+ >
268
+ {/* Your routes */}
269
+ </FootnoteProvider>
270
+ );
271
+ }
272
+
273
+ function App() {
274
+ return (
275
+ <BrowserRouter>
276
+ <AppContent />
277
+ </BrowserRouter>
278
+ );
279
+ }
280
+ ```
281
+
282
+ **How it works:**
283
+ - The `pathname` prop tracks the current route
284
+ - When the route changes, footnotes are automatically reset and renumbered
285
+ - This ensures footnote numbering starts fresh on each page (1, 2, 3...)
286
+ - The `location.search` is included to handle query parameters
287
+
288
+ ## API Reference
289
+
290
+ ### FootnoteProvider
291
+
292
+ The context provider that manages footnote state.
293
+
294
+ **Props:**
295
+ - `children: ReactNode` - Your app content
296
+ - `messages?: FootnoteMessages` - Optional messages object containing footnote content
297
+ ```tsx
298
+ {
299
+ citation?: { [id: string]: ReactNode },
300
+ special?: { [id: string]: ReactNode }
301
+ }
302
+ ```
303
+ - `pathname: string` - **Required.** Current pathname string for route-based footnote reset. When the pathname changes, footnotes are automatically reset and renumbered.
304
+ - For React Router: `` `${location.pathname}${location.search}` ``
305
+ - For simple cases: `` `${window.location.pathname}${window.location.search}` ``
306
+
307
+ ### Footnote
308
+
309
+ Component for rendering a footnote reference.
310
+
311
+ **Props:**
312
+ - `id: string` - Unique identifier for the footnote (must exist in messages)
313
+ - `type: 'citation' | 'special'` - Type of footnote (required)
314
+ - `getContent?: (id: string, type: 'citation' | 'special') => ReactNode` - Optional function to override messages
315
+ - `children?: ReactNode` - Optional direct content (overrides messages and getContent)
316
+ - `className?: string` - Optional className to override default styles (default: `'footnote-ref'`)
317
+ - `supClassName?: string` - Optional className for the `<sup>` element (default: `'footnote-symbol'`)
318
+
319
+ ### FootnoteList
320
+
321
+ Component for rendering all registered footnotes.
322
+
323
+ **Props:**
324
+ - `className?: string` - ClassName for the ordered list (default: `'footnote-list'`)
325
+ - `itemClassName?: string` - Optional className for list items
326
+ - `itemSupClassName?: string` - Optional className for the `<sup>` element in items
327
+ - `itemContentClassName?: string` - Optional className for the content element in items
328
+ - `itemBackLinkClassName?: string` - Optional className for the back link button
329
+ - `itemBackLinkIconClassName?: string` - Optional className for the back link icon
330
+ - `order?: 'special-first' | 'citation-first'` - Order of footnotes (default: `'special-first'`)
331
+ - `'special-first'` - Special footnotes appear first, then citations
332
+ - `'citation-first'` - Citations appear first, then special footnotes
333
+
334
+ ### FootnoteListItem
335
+
336
+ Component for rendering an individual footnote list item (used internally by FootnoteList).
337
+
338
+ **Props:**
339
+ - `id: string` - Unique identifier for the footnote
340
+ - `type?: 'citation' | 'special'` - Type of footnote
341
+ - `symbol: string` - The symbol or number to display
342
+ - `children?: ReactNode` - The footnote content
343
+ - `className?: string` - Optional className for the list item (default: `'footnote-list-item'`)
344
+ - `supClassName?: string` - Optional className for the `<sup>` element (default: `'footnote-list-item__symbol'`)
345
+ - `contentClassName?: string` - Optional className for the content (default: `'footnote-list-item__content'`)
346
+ - `backLinkClassName?: string` - Optional className for the back link button (default: `'footnote-list-item__back-link'`)
347
+ - `backLinkIconClassName?: string` - Optional className for the back link icon (default: `'footnote-list-item__back-link-icon'`)
348
+
349
+ ### useFootnotes
350
+
351
+ Hook to access footnote context.
352
+
353
+ **Returns:**
354
+ - `registerFootnote: (footnote: FootnoteProps) => void` - Register a footnote
355
+ - `citationList: FootnoteProps[]` - List of citation footnotes
356
+ - `specialList: FootnoteProps[]` - List of special footnotes
357
+ - `clickHandler: (e: React.MouseEvent<HTMLButtonElement>) => void` - Click handler for navigation
358
+ - `getContent: (id: string, type: FootnotesCategory) => ReactNode | null` - Function to get content from messages
359
+
360
+ ## Styling
361
+
362
+ The components use generic CSS class names that you can style with any CSS framework or custom CSS. The package doesn't require Tailwind CSS or any other specific styling library.
363
+
364
+ ### Default Styles
365
+
366
+ Import the default stylesheet (optional):
367
+
368
+ ```tsx
369
+ import '@creativecreate/react-footnotes/footnotes.css';
370
+ ```
371
+
372
+ Or copy the styles from `footnotes.css` to your own CSS file.
373
+
374
+ ### CSS Class Names
375
+
376
+ The package uses the following BEM-style class names that you can override:
377
+
378
+ - `.footnote-ref` - The footnote reference button (clickable superscript)
379
+ - `.footnote-symbol` - The symbol/number inside the reference button
380
+ - `.footnote-list` - The footnote list ordered list element
381
+ - `.footnote-list-item` - Individual footnote list item
382
+ - `.footnote-list-item__symbol` - The symbol in the list item
383
+ - `.footnote-list-item__content` - The footnote content
384
+ - `.footnote-list-item__back-link` - The "back to reference" button
385
+ - `.footnote-list-item__back-link-icon` - The back link icon
386
+
387
+ ### Customizing Styles
388
+
389
+ You can customize styles in several ways:
390
+
391
+ 1. **Override with your own CSS:**
392
+ ```css
393
+ .footnote-ref {
394
+ color: #your-color;
395
+ margin-left: 0.5rem;
396
+ }
397
+ ```
398
+
399
+ 2. **Use className props:**
400
+ ```tsx
401
+ <Footnote
402
+ id="smith-2023-study"
403
+ type="citation"
404
+ className="my-custom-class"
405
+ supClassName="my-sup-class"
406
+ />
407
+ <FootnoteList
408
+ className="my-list-class"
409
+ itemClassName="my-item-class"
410
+ order="citation-first"
411
+ />
412
+ ```
413
+
414
+ 3. **Use CSS modules or styled-components:**
415
+ ```tsx
416
+ import styles from './footnotes.module.css';
417
+ <Footnote id="smith-2023-study" type="citation" className={styles.footnote} />
418
+ ```
419
+
420
+ ## TypeScript
421
+
422
+ The package is written in TypeScript and includes type definitions. All types are exported from the main entry point.
423
+
424
+ ## License
425
+
426
+ MIT
@@ -0,0 +1,155 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ type FootnotesCategory = 'citation' | 'special';
5
+ type FootnoteKeys = string;
6
+ type FootnoteProps = {
7
+ id: FootnoteKeys;
8
+ type?: FootnotesCategory;
9
+ children?: ReactNode;
10
+ };
11
+ type FootnoteMessages = {
12
+ citation?: Record<string, ReactNode>;
13
+ special?: Record<string, ReactNode>;
14
+ };
15
+ type FootnoteContextType = {
16
+ registerFootnote: (footnote: FootnoteProps) => void;
17
+ citationList: FootnoteProps[];
18
+ specialList: FootnoteProps[];
19
+ clickHandler: (e: React.MouseEvent<HTMLButtonElement>) => void;
20
+ getContent: (id: string, type: FootnotesCategory) => ReactNode | null;
21
+ };
22
+ declare const splitFootnotes: (footnotes: FootnoteProps[]) => {
23
+ specialList: FootnoteProps[];
24
+ citationList: FootnoteProps[];
25
+ };
26
+ declare const getSpecialChar: (footnoteList: FootnoteProps[], id: FootnoteKeys) => string;
27
+
28
+ type FootnoteProviderProps = {
29
+ children: ReactNode;
30
+ /**
31
+ * Messages object containing footnote content.
32
+ * Structure: { citation: { id: content }, special: { id: content } }
33
+ */
34
+ messages?: FootnoteMessages;
35
+ /**
36
+ * Current pathname string for route-based footnote reset.
37
+ * For React Router: `\`${location.pathname}${location.search}\``
38
+ * For simple cases: `\`${window.location.pathname}${window.location.search}\``
39
+ */
40
+ pathname: string;
41
+ };
42
+ declare const FootnoteProvider: ({ children, messages, pathname, }: FootnoteProviderProps) => react_jsx_runtime.JSX.Element;
43
+ declare const useFootnotes: () => FootnoteContextType;
44
+
45
+ type FootnoteListProps = {
46
+ className?: string;
47
+ /**
48
+ * Optional className for the list item
49
+ */
50
+ itemClassName?: string;
51
+ /**
52
+ * Optional className for the <sup> element
53
+ */
54
+ itemSupClassName?: string;
55
+ /**
56
+ * Optional className for the content element
57
+ */
58
+ itemContentClassName?: string;
59
+ /**
60
+ * Optional className for the back link button
61
+ */
62
+ itemBackLinkClassName?: string;
63
+ /**
64
+ * Optional className for the back link icon
65
+ */
66
+ itemBackLinkIconClassName?: string;
67
+ /**
68
+ * Order of footnotes in the list
69
+ * @default 'special-first' - Special footnotes appear first, then citations
70
+ * 'citation-first' - Citations appear first, then special footnotes
71
+ */
72
+ order?: 'special-first' | 'citation-first';
73
+ };
74
+ declare const FootnoteList: ({ className, itemClassName, itemSupClassName, itemContentClassName, itemBackLinkClassName, itemBackLinkIconClassName, order, }: FootnoteListProps) => react_jsx_runtime.JSX.Element | null;
75
+
76
+ interface FootnoteListItemProps {
77
+ id: FootnoteKeys;
78
+ type?: 'citation' | 'special';
79
+ symbol: string;
80
+ children?: React.ReactNode;
81
+ /**
82
+ * Optional className to override default styles
83
+ */
84
+ className?: string;
85
+ /**
86
+ * Optional className for the <sup> element
87
+ */
88
+ supClassName?: string;
89
+ /**
90
+ * Optional className for the content element
91
+ */
92
+ contentClassName?: string;
93
+ /**
94
+ * Optional className for the back link button
95
+ */
96
+ backLinkClassName?: string;
97
+ /**
98
+ * Optional className for the back link icon
99
+ */
100
+ backLinkIconClassName?: string;
101
+ }
102
+ declare const FootnoteListItem: ({ id, children, symbol, className, supClassName, contentClassName, backLinkClassName, backLinkIconClassName, }: FootnoteListItemProps) => react_jsx_runtime.JSX.Element;
103
+
104
+ type FootnoteComponentProps = {
105
+ id: FootnoteKeys;
106
+ /**
107
+ * Type of footnote: 'citation' or 'special'
108
+ * Required when using messages from FootnoteProvider
109
+ */
110
+ type: FootnotesCategory;
111
+ /**
112
+ * Optional function to get footnote content.
113
+ * If provided, will override messages from FootnoteProvider.
114
+ * Should return the content for the footnote with the given id and type.
115
+ */
116
+ getContent?: (id: string, type: FootnotesCategory) => React.ReactNode;
117
+ /**
118
+ * Optional children to use as footnote content.
119
+ * If provided, will be used instead of getContent or messages.
120
+ */
121
+ children?: React.ReactNode;
122
+ /**
123
+ * Optional className to override default styles
124
+ */
125
+ className?: string;
126
+ /**
127
+ * Optional className for the <sup> element
128
+ */
129
+ supClassName?: string;
130
+ };
131
+
132
+ /**
133
+ * Footnote component - renders an inline footnote reference.
134
+ *
135
+ * Priority for content:
136
+ * 1. If `children` is provided, it will be used for the footnote content (highest priority).
137
+ * 2. If `getContent` prop is passed, it is called with `id` and `type` and its result is used.
138
+ * 3. Otherwise, content is fetched from messages via FootnoteProvider context (`getContentFromContext`).
139
+ *
140
+ * Registers itself in the footnote context via `registerFootnote` when content exists.
141
+ *
142
+ * Displays a symbol (number for 'citation', special character for 'special').
143
+ * Clicking the reference will navigate to its corresponding item in the FootnoteList.
144
+ *
145
+ * Props:
146
+ * - id: Unique id for the footnote
147
+ * - type: "citation" | "special"
148
+ * - getContent?: (id, type) => content (overrides messages)
149
+ * - children?: ReactNode (overrides everything, highest priority)
150
+ * - className?: class for the button/footnote ref
151
+ * - supClassName?: class for the <sup> element
152
+ */
153
+ declare const Footnote: ({ id, type, getContent, children, className, supClassName, }: FootnoteComponentProps) => react_jsx_runtime.JSX.Element | null;
154
+
155
+ export { Footnote, type FootnoteKeys, FootnoteList, FootnoteListItem, type FootnoteMessages, type FootnoteProps, FootnoteProvider, type FootnotesCategory, getSpecialChar, splitFootnotes, useFootnotes };