Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/green-donuts-press.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/shared': patch
'@clerk/clerk-react': patch
'@clerk/types': patch
---

Improve JSDoc documentation
73 changes: 71 additions & 2 deletions .typedoc/custom-plugin.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
// @ts-check
import { MarkdownRendererEvent } from 'typedoc-plugin-markdown';
import { MarkdownPageEvent, MarkdownRendererEvent } from 'typedoc-plugin-markdown';

/**
* A list of files where we want to remove any headings
*/
const FILES_WITHOUT_HEADINGS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the files only contain the contents of a single interface.

For example:

# Parameters
Table goes here

So since I want to use the table contents as a partial/include somewhere else I need to get rid off the heading. But since I don't want to remove headings everywhere I'm removing them only from the files I want to use in such way.

'use-organization-return.mdx',
'use-organization-params.mdx',
'paginated-resources.mdx',
'pages-or-infinite-options.mdx',
'pages-or-infinite-options.mdx',
'paginated-hook-config.mdx',
'use-organization-list-return.mdx',
'use-organization-list-params.mdx',
];

/**
* An array of tuples where the first element is the file name and the second element is the new path.
*/
const LINK_REPLACEMENTS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes there are relative links like [PaginatedResponse](../types/paginated-response.mdx) in the files. The link to the also generated file through typedoc. But we already have that page manually created and want to link to it. So this replaces relative links with links to our existing docs.

['clerk-paginated-response', '/docs/references/javascript/types/clerk-paginated-response'],
['paginated-resources', '#paginated-resources'],
];

/**
* Inside the generated MDX files are links to other generated MDX files. These relative links need to be replaced with absolute links to pages that exist on clerk.com.
* For example, `[Foobar](../../foo/bar.mdx)` needs to be replaced with `[Foobar](/docs/foo/bar)`.
* It also shouldn't matter how level deep the relative link is.
*
* This function returns an array of `{ pattern: string, replace: string }` to pass into the `typedoc-plugin-replace-text` plugin.
*/
function getRelativeLinkReplacements() {
return LINK_REPLACEMENTS.map(([fileName, newPath]) => {
return {
pattern: new RegExp(`\\((?:\\.{1,2}\\/)+.*?${fileName}\\.mdx\\)`, 'g'),
replace: `(${newPath})`,
};
});
}

/**
* @param {string} str
Expand All@@ -13,8 +51,9 @@ function toKebabCase(str) {
*/
export function load(app) {
app.renderer.on(MarkdownRendererEvent.BEGIN, output => {
// Do not output README.mdx files
// Modify the output object
output.urls = output.urls
// Do not output README.mdx files
?.filter(e => !e.url.endsWith('README.mdx'))
.map(e => {
// Convert URLs (by default camelCase) to kebab-case
Expand All@@ -23,7 +62,37 @@ export function load(app) {
e.url = kebabUrl;
e.model.url = kebabUrl;

/**
* For the `@clerk/shared` package it outputs the hooks as for example: shared/react/hooks/use-clerk/functions/use-clerk.mdx.
* It also places the interfaces as shared/react/hooks/use-organization/interfaces/use-organization-return.mdx
* Group all those .mdx files under shared/react/hooks
*/
if (e.url.includes('shared/react/hooks')) {
e.url = e.url.replace(/\/[^/]+\/(functions|interfaces)\//, '/');
e.model.url = e.url;
}

return e;
});
});

app.renderer.on(MarkdownPageEvent.END, output => {
const fileName = output.url.split('/').pop();
const linkReplacements = getRelativeLinkReplacements();

for (const { pattern, replace } of linkReplacements) {
if (output.contents) {
output.contents = output.contents.replace(pattern, replace);
}
}

if (fileName) {
if (FILES_WITHOUT_HEADINGS.includes(fileName)) {
if (output.contents) {
// Remove any headings from the file, irrespective of the level
output.contents = output.contents.replace(/^#+\s.+/gm, '');
}
}
}
});
}
112 changes: 110 additions & 2 deletions .typedoc/custom-theme.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// @ts-check
import { ReflectionKind } from 'typedoc';
import { ArrayType, IntersectionType, ReflectionKind, ReflectionType, UnionType } from 'typedoc';
import { MarkdownTheme, MarkdownThemeContext } from 'typedoc-plugin-markdown';

/**
Expand DownExpand Up@@ -36,7 +36,7 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {
this.partials = {
...superPartials,
/**
* Copied from default theme / source code. This hides the return type from the output
* Copied from default theme / source code. This hides the return type heading over the table from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/179a54c502b318cd4f3951e5e8b90f7f7a4752d8/packages/typedoc-plugin-markdown/src/theme/context/partials/member.signatureReturns.ts
* @param {import('typedoc').SignatureReflection} model
* @param {{ headingLevel: number }} options
Expand DownExpand Up@@ -235,6 +235,114 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {

md.push(this.partials.body(model, { headingLevel: options.headingLevel }));

return md.join('\n\n');
},
/**
* Copied from default theme / source code. This hides the "Type parameters" section and the declaration title from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/e798507a3c04f9ddf7710baf4cc7836053e438ff/packages/typedoc-plugin-markdown/src/theme/context/partials/member.declaration.ts
* @param {import('typedoc').DeclarationReflection} model
* @param {{ headingLevel: number, nested?: boolean }} options
*/
declaration: (model, options = { headingLevel: 2, nested: false }) => {
const md = [];

const opts = {
nested: false,
...options,
};

if (!opts.nested && model.sources && !this.options.getValue('disableSources')) {
md.push(this.partials.sources(model));
}

if (model?.documents) {
md.push(
this.partials.documents(model, {
headingLevel: options.headingLevel,
}),
);
}

/**
* @type any
*/
const modelType = model.type;
/**
* @type {import('typedoc').DeclarationReflection}
*/
let typeDeclaration = modelType?.declaration;

if (model.type instanceof ArrayType && model.type?.elementType instanceof ReflectionType) {
typeDeclaration = model.type?.elementType?.declaration;
}

const hasTypeDeclaration =
Boolean(typeDeclaration) ||
(model.type instanceof UnionType && model.type?.types.some(type => type instanceof ReflectionType));

if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: true,
showTags: false,
}),
);
}

if (model.type instanceof IntersectionType) {
model.type?.types?.forEach(intersectionType => {
if (intersectionType instanceof ReflectionType && !intersectionType.declaration.signatures) {
if (intersectionType.declaration.children) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

md.push(
this.partials.typeDeclaration(intersectionType.declaration, {
headingLevel: opts.headingLevel,
}),
);
}
}
});
}

if (hasTypeDeclaration) {
if (model.type instanceof UnionType) {
if (this.helpers.hasUsefulTypeDetails(model.type)) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

model.type.types.forEach(type => {
if (type instanceof ReflectionType) {
md.push(this.partials.someType(type, { forceCollapse: true }));
md.push(this.partials.typeDeclarationContainer(model, type.declaration, options));
} else {
md.push(`${this.partials.someType(type)}`);
}
});
}
} else {
const useHeading =
typeDeclaration?.children?.length &&
(model.kind !== ReflectionKind.Property || this.helpers.useTableFormat('properties'));
if (useHeading) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));
}
md.push(this.partials.typeDeclarationContainer(model, typeDeclaration, options));
}
}
if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: false,
showTags: true,
showReturns: true,
}),
);
}

md.push(this.partials.inheritance(model, { headingLevel: opts.headingLevel }));

return md.join('\n\n');
},
};
Expand Down
8 changes: 8 additions & 0 deletions .typedoc/typedoc-prettier-config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
{
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 120,
"useTabs": false,
"bracketSpacing": true
}
2 changes: 1 addition & 1 deletion package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,7 +48,7 @@
"test:integration:tanstack-start": "E2E_APP_ID=tanstack.start pnpm test:integration:base --grep @tanstack-start",
"test:integration:vue": "E2E_APP_ID=vue.vite pnpm test:integration:base --grep @vue",
"turbo:clean": "turbo daemon clean",
"typedoc:generate": "typedoc --tsconfig tsconfig.typedoc.json",
"typedoc:generate": "pnpm build:declarations && typedoc --tsconfig tsconfig.typedoc.json",
"version-packages": "changeset version && pnpm install --lockfile-only --engine-strict=false",
"version-packages:canary": "./scripts/canary.mjs",
"version-packages:snapshot": "./scripts/snapshot.mjs",
Expand Down
1 change: 1 addition & 0 deletions packages/react/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
/*/
!/src/
!/docs/
43 changes: 43 additions & 0 deletions packages/react/docs/use-auth.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/external-data/page.tsx' }}
'use client';

import { useAuth } from '@clerk/nextjs';

export default function ExternalDataPage() {
const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth();

const fetchExternalData = async () => {
const token = await getToken();

// Fetch data from an external API
const response = await fetch('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});

return response.json();
};

if (!isLoaded) {
return <div>Loading...</div>;
}

if (!isSignedIn) {
return <div>Sign in to view this page</div>;
}

return (
<div>
<p>
Hello, {userId}! Your current active session is {sessionId}.
</p>
<button onClick={fetchExternalData}>Fetch Data</button>
</div>
);
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-in.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-in/page.tsx' }}
'use client';

import { useSignIn } from '@clerk/nextjs';

export default function SignInPage() {
const { isLoaded, signIn } = useSignIn();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-in attempt status is {signIn?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-up.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-up/page.tsx' }}
'use client';

import { useSignUp } from '@clerk/nextjs';

export default function SignUpPage() {
const { isLoaded, signUp } = useSignUp();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-up attempt status is {signUp?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useAuth.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,9 @@ import { createGetToken, createSignOut } from './utils';
*
* The following example demonstrates how to use the `useAuth()` hook to access the current auth state, like whether the user is signed in or not. It also includes a basic example for using the `getToken()` method to retrieve a session token for fetching data from an external resource.
*
* <Tabs items='React,Next.js'>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use these MDX components (they are not resolved, just passed along) in such way, they won't be rendered in IntelliSense

* <Tab>
*
* ```tsx {{ filename: 'src/pages/ExternalDataPage.tsx' }}
* import { useAuth } from '@clerk/clerk-react'
*
Expand DownExpand Up@@ -58,6 +61,14 @@ import { createGetToken, createSignOut } from './utils';
* )
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-auth.md#nextjs-01}
*
* </Tab>
* </Tabs>
*/
export const useAuth = (initialAuthState: any = {}): UseAuthReturn => {
useAssertWrappedByClerkProvider('useAuth');
Expand Down
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useSignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,9 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
*
* The following example uses the `useSignIn()` hook to access the [`SignIn`](https://clerk.com/docs/references/javascript/sign-in/sign-in) object, which contains the current sign-in attempt status and methods to create a new sign-in attempt. The `isLoaded` property is used to handle the loading state.
*
* <Tabs items='React,Next.js'>
* <Tab>
*
* ```tsx {{ filename: 'src/pages/SignInPage.tsx' }}
* import { useSignIn } from '@clerk/clerk-react'
*
Expand All@@ -28,6 +31,14 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-sign-in.md#nextjs-01}
*
* </Tab>
* </Tabs>
*
* @example
* ### Create a custom sign-in flow with `useSignIn()`
*
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/green-donuts-press.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/shared': patch
'@clerk/clerk-react': patch
'@clerk/types': patch
---

Improve JSDoc documentation
73 changes: 71 additions & 2 deletions .typedoc/custom-plugin.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
// @ts-check
import { MarkdownRendererEvent } from 'typedoc-plugin-markdown';
import { MarkdownPageEvent, MarkdownRendererEvent } from 'typedoc-plugin-markdown';

/**
* A list of files where we want to remove any headings
*/
const FILES_WITHOUT_HEADINGS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the files only contain the contents of a single interface.

For example:

# Parameters
Table goes here

So since I want to use the table contents as a partial/include somewhere else I need to get rid off the heading. But since I don't want to remove headings everywhere I'm removing them only from the files I want to use in such way.

'use-organization-return.mdx',
'use-organization-params.mdx',
'paginated-resources.mdx',
'pages-or-infinite-options.mdx',
'pages-or-infinite-options.mdx',
'paginated-hook-config.mdx',
'use-organization-list-return.mdx',
'use-organization-list-params.mdx',
];

/**
* An array of tuples where the first element is the file name and the second element is the new path.
*/
const LINK_REPLACEMENTS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes there are relative links like [PaginatedResponse](../types/paginated-response.mdx) in the files. The link to the also generated file through typedoc. But we already have that page manually created and want to link to it. So this replaces relative links with links to our existing docs.

['clerk-paginated-response', '/docs/references/javascript/types/clerk-paginated-response'],
['paginated-resources', '#paginated-resources'],
];

/**
* Inside the generated MDX files are links to other generated MDX files. These relative links need to be replaced with absolute links to pages that exist on clerk.com.
* For example, `[Foobar](../../foo/bar.mdx)` needs to be replaced with `[Foobar](/docs/foo/bar)`.
* It also shouldn't matter how level deep the relative link is.
*
* This function returns an array of `{ pattern: string, replace: string }` to pass into the `typedoc-plugin-replace-text` plugin.
*/
function getRelativeLinkReplacements() {
return LINK_REPLACEMENTS.map(([fileName, newPath]) => {
return {
pattern: new RegExp(`\\((?:\\.{1,2}\\/)+.*?${fileName}\\.mdx\\)`, 'g'),
replace: `(${newPath})`,
};
});
}

/**
* @param {string} str
Expand All@@ -13,8 +51,9 @@ function toKebabCase(str) {
*/
export function load(app) {
app.renderer.on(MarkdownRendererEvent.BEGIN, output => {
// Do not output README.mdx files
// Modify the output object
output.urls = output.urls
// Do not output README.mdx files
?.filter(e => !e.url.endsWith('README.mdx'))
.map(e => {
// Convert URLs (by default camelCase) to kebab-case
Expand All@@ -23,7 +62,37 @@ export function load(app) {
e.url = kebabUrl;
e.model.url = kebabUrl;

/**
* For the `@clerk/shared` package it outputs the hooks as for example: shared/react/hooks/use-clerk/functions/use-clerk.mdx.
* It also places the interfaces as shared/react/hooks/use-organization/interfaces/use-organization-return.mdx
* Group all those .mdx files under shared/react/hooks
*/
if (e.url.includes('shared/react/hooks')) {
e.url = e.url.replace(/\/[^/]+\/(functions|interfaces)\//, '/');
e.model.url = e.url;
}

return e;
});
});

app.renderer.on(MarkdownPageEvent.END, output => {
const fileName = output.url.split('/').pop();
const linkReplacements = getRelativeLinkReplacements();

for (const { pattern, replace } of linkReplacements) {
if (output.contents) {
output.contents = output.contents.replace(pattern, replace);
}
}

if (fileName) {
if (FILES_WITHOUT_HEADINGS.includes(fileName)) {
if (output.contents) {
// Remove any headings from the file, irrespective of the level
output.contents = output.contents.replace(/^#+\s.+/gm, '');
}
}
}
});
}
112 changes: 110 additions & 2 deletions .typedoc/custom-theme.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// @ts-check
import { ReflectionKind } from 'typedoc';
import { ArrayType, IntersectionType, ReflectionKind, ReflectionType, UnionType } from 'typedoc';
import { MarkdownTheme, MarkdownThemeContext } from 'typedoc-plugin-markdown';

/**
Expand DownExpand Up@@ -36,7 +36,7 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {
this.partials = {
...superPartials,
/**
* Copied from default theme / source code. This hides the return type from the output
* Copied from default theme / source code. This hides the return type heading over the table from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/179a54c502b318cd4f3951e5e8b90f7f7a4752d8/packages/typedoc-plugin-markdown/src/theme/context/partials/member.signatureReturns.ts
* @param {import('typedoc').SignatureReflection} model
* @param {{ headingLevel: number }} options
Expand DownExpand Up@@ -235,6 +235,114 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {

md.push(this.partials.body(model, { headingLevel: options.headingLevel }));

return md.join('\n\n');
},
/**
* Copied from default theme / source code. This hides the "Type parameters" section and the declaration title from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/e798507a3c04f9ddf7710baf4cc7836053e438ff/packages/typedoc-plugin-markdown/src/theme/context/partials/member.declaration.ts
* @param {import('typedoc').DeclarationReflection} model
* @param {{ headingLevel: number, nested?: boolean }} options
*/
declaration: (model, options = { headingLevel: 2, nested: false }) => {
const md = [];

const opts = {
nested: false,
...options,
};

if (!opts.nested && model.sources && !this.options.getValue('disableSources')) {
md.push(this.partials.sources(model));
}

if (model?.documents) {
md.push(
this.partials.documents(model, {
headingLevel: options.headingLevel,
}),
);
}

/**
* @type any
*/
const modelType = model.type;
/**
* @type {import('typedoc').DeclarationReflection}
*/
let typeDeclaration = modelType?.declaration;

if (model.type instanceof ArrayType && model.type?.elementType instanceof ReflectionType) {
typeDeclaration = model.type?.elementType?.declaration;
}

const hasTypeDeclaration =
Boolean(typeDeclaration) ||
(model.type instanceof UnionType && model.type?.types.some(type => type instanceof ReflectionType));

if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: true,
showTags: false,
}),
);
}

if (model.type instanceof IntersectionType) {
model.type?.types?.forEach(intersectionType => {
if (intersectionType instanceof ReflectionType && !intersectionType.declaration.signatures) {
if (intersectionType.declaration.children) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

md.push(
this.partials.typeDeclaration(intersectionType.declaration, {
headingLevel: opts.headingLevel,
}),
);
}
}
});
}

if (hasTypeDeclaration) {
if (model.type instanceof UnionType) {
if (this.helpers.hasUsefulTypeDetails(model.type)) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

model.type.types.forEach(type => {
if (type instanceof ReflectionType) {
md.push(this.partials.someType(type, { forceCollapse: true }));
md.push(this.partials.typeDeclarationContainer(model, type.declaration, options));
} else {
md.push(`${this.partials.someType(type)}`);
}
});
}
} else {
const useHeading =
typeDeclaration?.children?.length &&
(model.kind !== ReflectionKind.Property || this.helpers.useTableFormat('properties'));
if (useHeading) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));
}
md.push(this.partials.typeDeclarationContainer(model, typeDeclaration, options));
}
}
if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: false,
showTags: true,
showReturns: true,
}),
);
}

md.push(this.partials.inheritance(model, { headingLevel: opts.headingLevel }));

return md.join('\n\n');
},
};
Expand Down
8 changes: 8 additions & 0 deletions .typedoc/typedoc-prettier-config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
{
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 120,
"useTabs": false,
"bracketSpacing": true
}
2 changes: 1 addition & 1 deletion package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,7 +48,7 @@
"test:integration:tanstack-start": "E2E_APP_ID=tanstack.start pnpm test:integration:base --grep @tanstack-start",
"test:integration:vue": "E2E_APP_ID=vue.vite pnpm test:integration:base --grep @vue",
"turbo:clean": "turbo daemon clean",
"typedoc:generate": "typedoc --tsconfig tsconfig.typedoc.json",
"typedoc:generate": "pnpm build:declarations && typedoc --tsconfig tsconfig.typedoc.json",
"version-packages": "changeset version && pnpm install --lockfile-only --engine-strict=false",
"version-packages:canary": "./scripts/canary.mjs",
"version-packages:snapshot": "./scripts/snapshot.mjs",
Expand Down
1 change: 1 addition & 0 deletions packages/react/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
/*/
!/src/
!/docs/
43 changes: 43 additions & 0 deletions packages/react/docs/use-auth.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/external-data/page.tsx' }}
'use client';

import { useAuth } from '@clerk/nextjs';

export default function ExternalDataPage() {
const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth();

const fetchExternalData = async () => {
const token = await getToken();

// Fetch data from an external API
const response = await fetch('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});

return response.json();
};

if (!isLoaded) {
return <div>Loading...</div>;
}

if (!isSignedIn) {
return <div>Sign in to view this page</div>;
}

return (
<div>
<p>
Hello, {userId}! Your current active session is {sessionId}.
</p>
<button onClick={fetchExternalData}>Fetch Data</button>
</div>
);
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-in.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-in/page.tsx' }}
'use client';

import { useSignIn } from '@clerk/nextjs';

export default function SignInPage() {
const { isLoaded, signIn } = useSignIn();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-in attempt status is {signIn?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-up.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-up/page.tsx' }}
'use client';

import { useSignUp } from '@clerk/nextjs';

export default function SignUpPage() {
const { isLoaded, signUp } = useSignUp();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-up attempt status is {signUp?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useAuth.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,9 @@ import { createGetToken, createSignOut } from './utils';
*
* The following example demonstrates how to use the `useAuth()` hook to access the current auth state, like whether the user is signed in or not. It also includes a basic example for using the `getToken()` method to retrieve a session token for fetching data from an external resource.
*
* <Tabs items='React,Next.js'>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use these MDX components (they are not resolved, just passed along) in such way, they won't be rendered in IntelliSense

* <Tab>
*
* ```tsx {{ filename: 'src/pages/ExternalDataPage.tsx' }}
* import { useAuth } from '@clerk/clerk-react'
*
Expand DownExpand Up@@ -58,6 +61,14 @@ import { createGetToken, createSignOut } from './utils';
* )
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-auth.md#nextjs-01}
*
* </Tab>
* </Tabs>
*/
export const useAuth = (initialAuthState: any = {}): UseAuthReturn => {
useAssertWrappedByClerkProvider('useAuth');
Expand Down
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useSignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,9 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
*
* The following example uses the `useSignIn()` hook to access the [`SignIn`](https://clerk.com/docs/references/javascript/sign-in/sign-in) object, which contains the current sign-in attempt status and methods to create a new sign-in attempt. The `isLoaded` property is used to handle the loading state.
*
* <Tabs items='React,Next.js'>
* <Tab>
*
* ```tsx {{ filename: 'src/pages/SignInPage.tsx' }}
* import { useSignIn } from '@clerk/clerk-react'
*
Expand All@@ -28,6 +31,14 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-sign-in.md#nextjs-01}
*
* </Tab>
* </Tabs>
*
* @example
* ### Create a custom sign-in flow with `useSignIn()`
*
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/green-donuts-press.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/shared': patch
'@clerk/clerk-react': patch
'@clerk/types': patch
---

Improve JSDoc documentation
73 changes: 71 additions & 2 deletions .typedoc/custom-plugin.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
// @ts-check
import { MarkdownRendererEvent } from 'typedoc-plugin-markdown';
import { MarkdownPageEvent, MarkdownRendererEvent } from 'typedoc-plugin-markdown';

/**
* A list of files where we want to remove any headings
*/
const FILES_WITHOUT_HEADINGS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the files only contain the contents of a single interface.

For example:

# Parameters
Table goes here

So since I want to use the table contents as a partial/include somewhere else I need to get rid off the heading. But since I don't want to remove headings everywhere I'm removing them only from the files I want to use in such way.

'use-organization-return.mdx',
'use-organization-params.mdx',
'paginated-resources.mdx',
'pages-or-infinite-options.mdx',
'pages-or-infinite-options.mdx',
'paginated-hook-config.mdx',
'use-organization-list-return.mdx',
'use-organization-list-params.mdx',
];

/**
* An array of tuples where the first element is the file name and the second element is the new path.
*/
const LINK_REPLACEMENTS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes there are relative links like [PaginatedResponse](../types/paginated-response.mdx) in the files. The link to the also generated file through typedoc. But we already have that page manually created and want to link to it. So this replaces relative links with links to our existing docs.

['clerk-paginated-response', '/docs/references/javascript/types/clerk-paginated-response'],
['paginated-resources', '#paginated-resources'],
];

/**
* Inside the generated MDX files are links to other generated MDX files. These relative links need to be replaced with absolute links to pages that exist on clerk.com.
* For example, `[Foobar](../../foo/bar.mdx)` needs to be replaced with `[Foobar](/docs/foo/bar)`.
* It also shouldn't matter how level deep the relative link is.
*
* This function returns an array of `{ pattern: string, replace: string }` to pass into the `typedoc-plugin-replace-text` plugin.
*/
function getRelativeLinkReplacements() {
return LINK_REPLACEMENTS.map(([fileName, newPath]) => {
return {
pattern: new RegExp(`\\((?:\\.{1,2}\\/)+.*?${fileName}\\.mdx\\)`, 'g'),
replace: `(${newPath})`,
};
});
}

/**
* @param {string} str
Expand All@@ -13,8 +51,9 @@ function toKebabCase(str) {
*/
export function load(app) {
app.renderer.on(MarkdownRendererEvent.BEGIN, output => {
// Do not output README.mdx files
// Modify the output object
output.urls = output.urls
// Do not output README.mdx files
?.filter(e => !e.url.endsWith('README.mdx'))
.map(e => {
// Convert URLs (by default camelCase) to kebab-case
Expand All@@ -23,7 +62,37 @@ export function load(app) {
e.url = kebabUrl;
e.model.url = kebabUrl;

/**
* For the `@clerk/shared` package it outputs the hooks as for example: shared/react/hooks/use-clerk/functions/use-clerk.mdx.
* It also places the interfaces as shared/react/hooks/use-organization/interfaces/use-organization-return.mdx
* Group all those .mdx files under shared/react/hooks
*/
if (e.url.includes('shared/react/hooks')) {
e.url = e.url.replace(/\/[^/]+\/(functions|interfaces)\//, '/');
e.model.url = e.url;
}

return e;
});
});

app.renderer.on(MarkdownPageEvent.END, output => {
const fileName = output.url.split('/').pop();
const linkReplacements = getRelativeLinkReplacements();

for (const { pattern, replace } of linkReplacements) {
if (output.contents) {
output.contents = output.contents.replace(pattern, replace);
}
}

if (fileName) {
if (FILES_WITHOUT_HEADINGS.includes(fileName)) {
if (output.contents) {
// Remove any headings from the file, irrespective of the level
output.contents = output.contents.replace(/^#+\s.+/gm, '');
}
}
}
});
}
112 changes: 110 additions & 2 deletions .typedoc/custom-theme.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// @ts-check
import { ReflectionKind } from 'typedoc';
import { ArrayType, IntersectionType, ReflectionKind, ReflectionType, UnionType } from 'typedoc';
import { MarkdownTheme, MarkdownThemeContext } from 'typedoc-plugin-markdown';

/**
Expand DownExpand Up@@ -36,7 +36,7 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {
this.partials = {
...superPartials,
/**
* Copied from default theme / source code. This hides the return type from the output
* Copied from default theme / source code. This hides the return type heading over the table from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/179a54c502b318cd4f3951e5e8b90f7f7a4752d8/packages/typedoc-plugin-markdown/src/theme/context/partials/member.signatureReturns.ts
* @param {import('typedoc').SignatureReflection} model
* @param {{ headingLevel: number }} options
Expand DownExpand Up@@ -235,6 +235,114 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {

md.push(this.partials.body(model, { headingLevel: options.headingLevel }));

return md.join('\n\n');
},
/**
* Copied from default theme / source code. This hides the "Type parameters" section and the declaration title from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/e798507a3c04f9ddf7710baf4cc7836053e438ff/packages/typedoc-plugin-markdown/src/theme/context/partials/member.declaration.ts
* @param {import('typedoc').DeclarationReflection} model
* @param {{ headingLevel: number, nested?: boolean }} options
*/
declaration: (model, options = { headingLevel: 2, nested: false }) => {
const md = [];

const opts = {
nested: false,
...options,
};

if (!opts.nested && model.sources && !this.options.getValue('disableSources')) {
md.push(this.partials.sources(model));
}

if (model?.documents) {
md.push(
this.partials.documents(model, {
headingLevel: options.headingLevel,
}),
);
}

/**
* @type any
*/
const modelType = model.type;
/**
* @type {import('typedoc').DeclarationReflection}
*/
let typeDeclaration = modelType?.declaration;

if (model.type instanceof ArrayType && model.type?.elementType instanceof ReflectionType) {
typeDeclaration = model.type?.elementType?.declaration;
}

const hasTypeDeclaration =
Boolean(typeDeclaration) ||
(model.type instanceof UnionType && model.type?.types.some(type => type instanceof ReflectionType));

if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: true,
showTags: false,
}),
);
}

if (model.type instanceof IntersectionType) {
model.type?.types?.forEach(intersectionType => {
if (intersectionType instanceof ReflectionType && !intersectionType.declaration.signatures) {
if (intersectionType.declaration.children) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

md.push(
this.partials.typeDeclaration(intersectionType.declaration, {
headingLevel: opts.headingLevel,
}),
);
}
}
});
}

if (hasTypeDeclaration) {
if (model.type instanceof UnionType) {
if (this.helpers.hasUsefulTypeDetails(model.type)) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

model.type.types.forEach(type => {
if (type instanceof ReflectionType) {
md.push(this.partials.someType(type, { forceCollapse: true }));
md.push(this.partials.typeDeclarationContainer(model, type.declaration, options));
} else {
md.push(`${this.partials.someType(type)}`);
}
});
}
} else {
const useHeading =
typeDeclaration?.children?.length &&
(model.kind !== ReflectionKind.Property || this.helpers.useTableFormat('properties'));
if (useHeading) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));
}
md.push(this.partials.typeDeclarationContainer(model, typeDeclaration, options));
}
}
if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: false,
showTags: true,
showReturns: true,
}),
);
}

md.push(this.partials.inheritance(model, { headingLevel: opts.headingLevel }));

return md.join('\n\n');
},
};
Expand Down
8 changes: 8 additions & 0 deletions .typedoc/typedoc-prettier-config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
{
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 120,
"useTabs": false,
"bracketSpacing": true
}
2 changes: 1 addition & 1 deletion package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,7 +48,7 @@
"test:integration:tanstack-start": "E2E_APP_ID=tanstack.start pnpm test:integration:base --grep @tanstack-start",
"test:integration:vue": "E2E_APP_ID=vue.vite pnpm test:integration:base --grep @vue",
"turbo:clean": "turbo daemon clean",
"typedoc:generate": "typedoc --tsconfig tsconfig.typedoc.json",
"typedoc:generate": "pnpm build:declarations && typedoc --tsconfig tsconfig.typedoc.json",
"version-packages": "changeset version && pnpm install --lockfile-only --engine-strict=false",
"version-packages:canary": "./scripts/canary.mjs",
"version-packages:snapshot": "./scripts/snapshot.mjs",
Expand Down
1 change: 1 addition & 0 deletions packages/react/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
/*/
!/src/
!/docs/
43 changes: 43 additions & 0 deletions packages/react/docs/use-auth.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/external-data/page.tsx' }}
'use client';

import { useAuth } from '@clerk/nextjs';

export default function ExternalDataPage() {
const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth();

const fetchExternalData = async () => {
const token = await getToken();

// Fetch data from an external API
const response = await fetch('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});

return response.json();
};

if (!isLoaded) {
return <div>Loading...</div>;
}

if (!isSignedIn) {
return <div>Sign in to view this page</div>;
}

return (
<div>
<p>
Hello, {userId}! Your current active session is {sessionId}.
</p>
<button onClick={fetchExternalData}>Fetch Data</button>
</div>
);
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-in.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-in/page.tsx' }}
'use client';

import { useSignIn } from '@clerk/nextjs';

export default function SignInPage() {
const { isLoaded, signIn } = useSignIn();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-in attempt status is {signIn?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-up.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-up/page.tsx' }}
'use client';

import { useSignUp } from '@clerk/nextjs';

export default function SignUpPage() {
const { isLoaded, signUp } = useSignUp();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-up attempt status is {signUp?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useAuth.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,9 @@ import { createGetToken, createSignOut } from './utils';
*
* The following example demonstrates how to use the `useAuth()` hook to access the current auth state, like whether the user is signed in or not. It also includes a basic example for using the `getToken()` method to retrieve a session token for fetching data from an external resource.
*
* <Tabs items='React,Next.js'>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use these MDX components (they are not resolved, just passed along) in such way, they won't be rendered in IntelliSense

* <Tab>
*
* ```tsx {{ filename: 'src/pages/ExternalDataPage.tsx' }}
* import { useAuth } from '@clerk/clerk-react'
*
Expand DownExpand Up@@ -58,6 +61,14 @@ import { createGetToken, createSignOut } from './utils';
* )
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-auth.md#nextjs-01}
*
* </Tab>
* </Tabs>
*/
export const useAuth = (initialAuthState: any = {}): UseAuthReturn => {
useAssertWrappedByClerkProvider('useAuth');
Expand Down
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useSignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,9 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
*
* The following example uses the `useSignIn()` hook to access the [`SignIn`](https://clerk.com/docs/references/javascript/sign-in/sign-in) object, which contains the current sign-in attempt status and methods to create a new sign-in attempt. The `isLoaded` property is used to handle the loading state.
*
* <Tabs items='React,Next.js'>
* <Tab>
*
* ```tsx {{ filename: 'src/pages/SignInPage.tsx' }}
* import { useSignIn } from '@clerk/clerk-react'
*
Expand All@@ -28,6 +31,14 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-sign-in.md#nextjs-01}
*
* </Tab>
* </Tabs>
*
* @example
* ### Create a custom sign-in flow with `useSignIn()`
*
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/green-donuts-press.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/shared': patch
'@clerk/clerk-react': patch
'@clerk/types': patch
---

Improve JSDoc documentation
73 changes: 71 additions & 2 deletions .typedoc/custom-plugin.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
// @ts-check
import { MarkdownRendererEvent } from 'typedoc-plugin-markdown';
import { MarkdownPageEvent, MarkdownRendererEvent } from 'typedoc-plugin-markdown';

/**
* A list of files where we want to remove any headings
*/
const FILES_WITHOUT_HEADINGS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the files only contain the contents of a single interface.

For example:

# Parameters
Table goes here

So since I want to use the table contents as a partial/include somewhere else I need to get rid off the heading. But since I don't want to remove headings everywhere I'm removing them only from the files I want to use in such way.

'use-organization-return.mdx',
'use-organization-params.mdx',
'paginated-resources.mdx',
'pages-or-infinite-options.mdx',
'pages-or-infinite-options.mdx',
'paginated-hook-config.mdx',
'use-organization-list-return.mdx',
'use-organization-list-params.mdx',
];

/**
* An array of tuples where the first element is the file name and the second element is the new path.
*/
const LINK_REPLACEMENTS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes there are relative links like [PaginatedResponse](../types/paginated-response.mdx) in the files. The link to the also generated file through typedoc. But we already have that page manually created and want to link to it. So this replaces relative links with links to our existing docs.

['clerk-paginated-response', '/docs/references/javascript/types/clerk-paginated-response'],
['paginated-resources', '#paginated-resources'],
];

/**
* Inside the generated MDX files are links to other generated MDX files. These relative links need to be replaced with absolute links to pages that exist on clerk.com.
* For example, `[Foobar](../../foo/bar.mdx)` needs to be replaced with `[Foobar](/docs/foo/bar)`.
* It also shouldn't matter how level deep the relative link is.
*
* This function returns an array of `{ pattern: string, replace: string }` to pass into the `typedoc-plugin-replace-text` plugin.
*/
function getRelativeLinkReplacements() {
return LINK_REPLACEMENTS.map(([fileName, newPath]) => {
return {
pattern: new RegExp(`\\((?:\\.{1,2}\\/)+.*?${fileName}\\.mdx\\)`, 'g'),
replace: `(${newPath})`,
};
});
}

/**
* @param {string} str
Expand All@@ -13,8 +51,9 @@ function toKebabCase(str) {
*/
export function load(app) {
app.renderer.on(MarkdownRendererEvent.BEGIN, output => {
// Do not output README.mdx files
// Modify the output object
output.urls = output.urls
// Do not output README.mdx files
?.filter(e => !e.url.endsWith('README.mdx'))
.map(e => {
// Convert URLs (by default camelCase) to kebab-case
Expand All@@ -23,7 +62,37 @@ export function load(app) {
e.url = kebabUrl;
e.model.url = kebabUrl;

/**
* For the `@clerk/shared` package it outputs the hooks as for example: shared/react/hooks/use-clerk/functions/use-clerk.mdx.
* It also places the interfaces as shared/react/hooks/use-organization/interfaces/use-organization-return.mdx
* Group all those .mdx files under shared/react/hooks
*/
if (e.url.includes('shared/react/hooks')) {
e.url = e.url.replace(/\/[^/]+\/(functions|interfaces)\//, '/');
e.model.url = e.url;
}

return e;
});
});

app.renderer.on(MarkdownPageEvent.END, output => {
const fileName = output.url.split('/').pop();
const linkReplacements = getRelativeLinkReplacements();

for (const { pattern, replace } of linkReplacements) {
if (output.contents) {
output.contents = output.contents.replace(pattern, replace);
}
}

if (fileName) {
if (FILES_WITHOUT_HEADINGS.includes(fileName)) {
if (output.contents) {
// Remove any headings from the file, irrespective of the level
output.contents = output.contents.replace(/^#+\s.+/gm, '');
}
}
}
});
}
112 changes: 110 additions & 2 deletions .typedoc/custom-theme.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// @ts-check
import { ReflectionKind } from 'typedoc';
import { ArrayType, IntersectionType, ReflectionKind, ReflectionType, UnionType } from 'typedoc';
import { MarkdownTheme, MarkdownThemeContext } from 'typedoc-plugin-markdown';

/**
Expand DownExpand Up@@ -36,7 +36,7 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {
this.partials = {
...superPartials,
/**
* Copied from default theme / source code. This hides the return type from the output
* Copied from default theme / source code. This hides the return type heading over the table from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/179a54c502b318cd4f3951e5e8b90f7f7a4752d8/packages/typedoc-plugin-markdown/src/theme/context/partials/member.signatureReturns.ts
* @param {import('typedoc').SignatureReflection} model
* @param {{ headingLevel: number }} options
Expand DownExpand Up@@ -235,6 +235,114 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {

md.push(this.partials.body(model, { headingLevel: options.headingLevel }));

return md.join('\n\n');
},
/**
* Copied from default theme / source code. This hides the "Type parameters" section and the declaration title from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/e798507a3c04f9ddf7710baf4cc7836053e438ff/packages/typedoc-plugin-markdown/src/theme/context/partials/member.declaration.ts
* @param {import('typedoc').DeclarationReflection} model
* @param {{ headingLevel: number, nested?: boolean }} options
*/
declaration: (model, options = { headingLevel: 2, nested: false }) => {
const md = [];

const opts = {
nested: false,
...options,
};

if (!opts.nested && model.sources && !this.options.getValue('disableSources')) {
md.push(this.partials.sources(model));
}

if (model?.documents) {
md.push(
this.partials.documents(model, {
headingLevel: options.headingLevel,
}),
);
}

/**
* @type any
*/
const modelType = model.type;
/**
* @type {import('typedoc').DeclarationReflection}
*/
let typeDeclaration = modelType?.declaration;

if (model.type instanceof ArrayType && model.type?.elementType instanceof ReflectionType) {
typeDeclaration = model.type?.elementType?.declaration;
}

const hasTypeDeclaration =
Boolean(typeDeclaration) ||
(model.type instanceof UnionType && model.type?.types.some(type => type instanceof ReflectionType));

if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: true,
showTags: false,
}),
);
}

if (model.type instanceof IntersectionType) {
model.type?.types?.forEach(intersectionType => {
if (intersectionType instanceof ReflectionType && !intersectionType.declaration.signatures) {
if (intersectionType.declaration.children) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

md.push(
this.partials.typeDeclaration(intersectionType.declaration, {
headingLevel: opts.headingLevel,
}),
);
}
}
});
}

if (hasTypeDeclaration) {
if (model.type instanceof UnionType) {
if (this.helpers.hasUsefulTypeDetails(model.type)) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

model.type.types.forEach(type => {
if (type instanceof ReflectionType) {
md.push(this.partials.someType(type, { forceCollapse: true }));
md.push(this.partials.typeDeclarationContainer(model, type.declaration, options));
} else {
md.push(`${this.partials.someType(type)}`);
}
});
}
} else {
const useHeading =
typeDeclaration?.children?.length &&
(model.kind !== ReflectionKind.Property || this.helpers.useTableFormat('properties'));
if (useHeading) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));
}
md.push(this.partials.typeDeclarationContainer(model, typeDeclaration, options));
}
}
if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: false,
showTags: true,
showReturns: true,
}),
);
}

md.push(this.partials.inheritance(model, { headingLevel: opts.headingLevel }));

return md.join('\n\n');
},
};
Expand Down
8 changes: 8 additions & 0 deletions .typedoc/typedoc-prettier-config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
{
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 120,
"useTabs": false,
"bracketSpacing": true
}
2 changes: 1 addition & 1 deletion package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,7 +48,7 @@
"test:integration:tanstack-start": "E2E_APP_ID=tanstack.start pnpm test:integration:base --grep @tanstack-start",
"test:integration:vue": "E2E_APP_ID=vue.vite pnpm test:integration:base --grep @vue",
"turbo:clean": "turbo daemon clean",
"typedoc:generate": "typedoc --tsconfig tsconfig.typedoc.json",
"typedoc:generate": "pnpm build:declarations && typedoc --tsconfig tsconfig.typedoc.json",
"version-packages": "changeset version && pnpm install --lockfile-only --engine-strict=false",
"version-packages:canary": "./scripts/canary.mjs",
"version-packages:snapshot": "./scripts/snapshot.mjs",
Expand Down
1 change: 1 addition & 0 deletions packages/react/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
/*/
!/src/
!/docs/
43 changes: 43 additions & 0 deletions packages/react/docs/use-auth.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/external-data/page.tsx' }}
'use client';

import { useAuth } from '@clerk/nextjs';

export default function ExternalDataPage() {
const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth();

const fetchExternalData = async () => {
const token = await getToken();

// Fetch data from an external API
const response = await fetch('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});

return response.json();
};

if (!isLoaded) {
return <div>Loading...</div>;
}

if (!isSignedIn) {
return <div>Sign in to view this page</div>;
}

return (
<div>
<p>
Hello, {userId}! Your current active session is {sessionId}.
</p>
<button onClick={fetchExternalData}>Fetch Data</button>
</div>
);
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-in.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-in/page.tsx' }}
'use client';

import { useSignIn } from '@clerk/nextjs';

export default function SignInPage() {
const { isLoaded, signIn } = useSignIn();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-in attempt status is {signIn?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-up.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-up/page.tsx' }}
'use client';

import { useSignUp } from '@clerk/nextjs';

export default function SignUpPage() {
const { isLoaded, signUp } = useSignUp();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-up attempt status is {signUp?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useAuth.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,9 @@ import { createGetToken, createSignOut } from './utils';
*
* The following example demonstrates how to use the `useAuth()` hook to access the current auth state, like whether the user is signed in or not. It also includes a basic example for using the `getToken()` method to retrieve a session token for fetching data from an external resource.
*
* <Tabs items='React,Next.js'>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use these MDX components (they are not resolved, just passed along) in such way, they won't be rendered in IntelliSense

* <Tab>
*
* ```tsx {{ filename: 'src/pages/ExternalDataPage.tsx' }}
* import { useAuth } from '@clerk/clerk-react'
*
Expand DownExpand Up@@ -58,6 +61,14 @@ import { createGetToken, createSignOut } from './utils';
* )
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-auth.md#nextjs-01}
*
* </Tab>
* </Tabs>
*/
export const useAuth = (initialAuthState: any = {}): UseAuthReturn => {
useAssertWrappedByClerkProvider('useAuth');
Expand Down
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useSignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,9 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
*
* The following example uses the `useSignIn()` hook to access the [`SignIn`](https://clerk.com/docs/references/javascript/sign-in/sign-in) object, which contains the current sign-in attempt status and methods to create a new sign-in attempt. The `isLoaded` property is used to handle the loading state.
*
* <Tabs items='React,Next.js'>
* <Tab>
*
* ```tsx {{ filename: 'src/pages/SignInPage.tsx' }}
* import { useSignIn } from '@clerk/clerk-react'
*
Expand All@@ -28,6 +31,14 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-sign-in.md#nextjs-01}
*
* </Tab>
* </Tabs>
*
* @example
* ### Create a custom sign-in flow with `useSignIn()`
*
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/green-donuts-press.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/shared': patch
'@clerk/clerk-react': patch
'@clerk/types': patch
---

Improve JSDoc documentation
73 changes: 71 additions & 2 deletions .typedoc/custom-plugin.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
// @ts-check
import { MarkdownRendererEvent } from 'typedoc-plugin-markdown';
import { MarkdownPageEvent, MarkdownRendererEvent } from 'typedoc-plugin-markdown';

/**
* A list of files where we want to remove any headings
*/
const FILES_WITHOUT_HEADINGS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the files only contain the contents of a single interface.

For example:

# Parameters
Table goes here

So since I want to use the table contents as a partial/include somewhere else I need to get rid off the heading. But since I don't want to remove headings everywhere I'm removing them only from the files I want to use in such way.

'use-organization-return.mdx',
'use-organization-params.mdx',
'paginated-resources.mdx',
'pages-or-infinite-options.mdx',
'pages-or-infinite-options.mdx',
'paginated-hook-config.mdx',
'use-organization-list-return.mdx',
'use-organization-list-params.mdx',
];

/**
* An array of tuples where the first element is the file name and the second element is the new path.
*/
const LINK_REPLACEMENTS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes there are relative links like [PaginatedResponse](../types/paginated-response.mdx) in the files. The link to the also generated file through typedoc. But we already have that page manually created and want to link to it. So this replaces relative links with links to our existing docs.

['clerk-paginated-response', '/docs/references/javascript/types/clerk-paginated-response'],
['paginated-resources', '#paginated-resources'],
];

/**
* Inside the generated MDX files are links to other generated MDX files. These relative links need to be replaced with absolute links to pages that exist on clerk.com.
* For example, `[Foobar](../../foo/bar.mdx)` needs to be replaced with `[Foobar](/docs/foo/bar)`.
* It also shouldn't matter how level deep the relative link is.
*
* This function returns an array of `{ pattern: string, replace: string }` to pass into the `typedoc-plugin-replace-text` plugin.
*/
function getRelativeLinkReplacements() {
return LINK_REPLACEMENTS.map(([fileName, newPath]) => {
return {
pattern: new RegExp(`\\((?:\\.{1,2}\\/)+.*?${fileName}\\.mdx\\)`, 'g'),
replace: `(${newPath})`,
};
});
}

/**
* @param {string} str
Expand All@@ -13,8 +51,9 @@ function toKebabCase(str) {
*/
export function load(app) {
app.renderer.on(MarkdownRendererEvent.BEGIN, output => {
// Do not output README.mdx files
// Modify the output object
output.urls = output.urls
// Do not output README.mdx files
?.filter(e => !e.url.endsWith('README.mdx'))
.map(e => {
// Convert URLs (by default camelCase) to kebab-case
Expand All@@ -23,7 +62,37 @@ export function load(app) {
e.url = kebabUrl;
e.model.url = kebabUrl;

/**
* For the `@clerk/shared` package it outputs the hooks as for example: shared/react/hooks/use-clerk/functions/use-clerk.mdx.
* It also places the interfaces as shared/react/hooks/use-organization/interfaces/use-organization-return.mdx
* Group all those .mdx files under shared/react/hooks
*/
if (e.url.includes('shared/react/hooks')) {
e.url = e.url.replace(/\/[^/]+\/(functions|interfaces)\//, '/');
e.model.url = e.url;
}

return e;
});
});

app.renderer.on(MarkdownPageEvent.END, output => {
const fileName = output.url.split('/').pop();
const linkReplacements = getRelativeLinkReplacements();

for (const { pattern, replace } of linkReplacements) {
if (output.contents) {
output.contents = output.contents.replace(pattern, replace);
}
}

if (fileName) {
if (FILES_WITHOUT_HEADINGS.includes(fileName)) {
if (output.contents) {
// Remove any headings from the file, irrespective of the level
output.contents = output.contents.replace(/^#+\s.+/gm, '');
}
}
}
});
}
112 changes: 110 additions & 2 deletions .typedoc/custom-theme.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// @ts-check
import { ReflectionKind } from 'typedoc';
import { ArrayType, IntersectionType, ReflectionKind, ReflectionType, UnionType } from 'typedoc';
import { MarkdownTheme, MarkdownThemeContext } from 'typedoc-plugin-markdown';

/**
Expand DownExpand Up@@ -36,7 +36,7 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {
this.partials = {
...superPartials,
/**
* Copied from default theme / source code. This hides the return type from the output
* Copied from default theme / source code. This hides the return type heading over the table from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/179a54c502b318cd4f3951e5e8b90f7f7a4752d8/packages/typedoc-plugin-markdown/src/theme/context/partials/member.signatureReturns.ts
* @param {import('typedoc').SignatureReflection} model
* @param {{ headingLevel: number }} options
Expand DownExpand Up@@ -235,6 +235,114 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {

md.push(this.partials.body(model, { headingLevel: options.headingLevel }));

return md.join('\n\n');
},
/**
* Copied from default theme / source code. This hides the "Type parameters" section and the declaration title from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/e798507a3c04f9ddf7710baf4cc7836053e438ff/packages/typedoc-plugin-markdown/src/theme/context/partials/member.declaration.ts
* @param {import('typedoc').DeclarationReflection} model
* @param {{ headingLevel: number, nested?: boolean }} options
*/
declaration: (model, options = { headingLevel: 2, nested: false }) => {
const md = [];

const opts = {
nested: false,
...options,
};

if (!opts.nested && model.sources && !this.options.getValue('disableSources')) {
md.push(this.partials.sources(model));
}

if (model?.documents) {
md.push(
this.partials.documents(model, {
headingLevel: options.headingLevel,
}),
);
}

/**
* @type any
*/
const modelType = model.type;
/**
* @type {import('typedoc').DeclarationReflection}
*/
let typeDeclaration = modelType?.declaration;

if (model.type instanceof ArrayType && model.type?.elementType instanceof ReflectionType) {
typeDeclaration = model.type?.elementType?.declaration;
}

const hasTypeDeclaration =
Boolean(typeDeclaration) ||
(model.type instanceof UnionType && model.type?.types.some(type => type instanceof ReflectionType));

if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: true,
showTags: false,
}),
);
}

if (model.type instanceof IntersectionType) {
model.type?.types?.forEach(intersectionType => {
if (intersectionType instanceof ReflectionType && !intersectionType.declaration.signatures) {
if (intersectionType.declaration.children) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

md.push(
this.partials.typeDeclaration(intersectionType.declaration, {
headingLevel: opts.headingLevel,
}),
);
}
}
});
}

if (hasTypeDeclaration) {
if (model.type instanceof UnionType) {
if (this.helpers.hasUsefulTypeDetails(model.type)) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

model.type.types.forEach(type => {
if (type instanceof ReflectionType) {
md.push(this.partials.someType(type, { forceCollapse: true }));
md.push(this.partials.typeDeclarationContainer(model, type.declaration, options));
} else {
md.push(`${this.partials.someType(type)}`);
}
});
}
} else {
const useHeading =
typeDeclaration?.children?.length &&
(model.kind !== ReflectionKind.Property || this.helpers.useTableFormat('properties'));
if (useHeading) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));
}
md.push(this.partials.typeDeclarationContainer(model, typeDeclaration, options));
}
}
if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: false,
showTags: true,
showReturns: true,
}),
);
}

md.push(this.partials.inheritance(model, { headingLevel: opts.headingLevel }));

return md.join('\n\n');
},
};
Expand Down
8 changes: 8 additions & 0 deletions .typedoc/typedoc-prettier-config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
{
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 120,
"useTabs": false,
"bracketSpacing": true
}
2 changes: 1 addition & 1 deletion package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,7 +48,7 @@
"test:integration:tanstack-start": "E2E_APP_ID=tanstack.start pnpm test:integration:base --grep @tanstack-start",
"test:integration:vue": "E2E_APP_ID=vue.vite pnpm test:integration:base --grep @vue",
"turbo:clean": "turbo daemon clean",
"typedoc:generate": "typedoc --tsconfig tsconfig.typedoc.json",
"typedoc:generate": "pnpm build:declarations && typedoc --tsconfig tsconfig.typedoc.json",
"version-packages": "changeset version && pnpm install --lockfile-only --engine-strict=false",
"version-packages:canary": "./scripts/canary.mjs",
"version-packages:snapshot": "./scripts/snapshot.mjs",
Expand Down
1 change: 1 addition & 0 deletions packages/react/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
/*/
!/src/
!/docs/
43 changes: 43 additions & 0 deletions packages/react/docs/use-auth.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/external-data/page.tsx' }}
'use client';

import { useAuth } from '@clerk/nextjs';

export default function ExternalDataPage() {
const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth();

const fetchExternalData = async () => {
const token = await getToken();

// Fetch data from an external API
const response = await fetch('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});

return response.json();
};

if (!isLoaded) {
return <div>Loading...</div>;
}

if (!isSignedIn) {
return <div>Sign in to view this page</div>;
}

return (
<div>
<p>
Hello, {userId}! Your current active session is {sessionId}.
</p>
<button onClick={fetchExternalData}>Fetch Data</button>
</div>
);
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-in.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-in/page.tsx' }}
'use client';

import { useSignIn } from '@clerk/nextjs';

export default function SignInPage() {
const { isLoaded, signIn } = useSignIn();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-in attempt status is {signIn?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-up.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-up/page.tsx' }}
'use client';

import { useSignUp } from '@clerk/nextjs';

export default function SignUpPage() {
const { isLoaded, signUp } = useSignUp();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-up attempt status is {signUp?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useAuth.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,9 @@ import { createGetToken, createSignOut } from './utils';
*
* The following example demonstrates how to use the `useAuth()` hook to access the current auth state, like whether the user is signed in or not. It also includes a basic example for using the `getToken()` method to retrieve a session token for fetching data from an external resource.
*
* <Tabs items='React,Next.js'>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use these MDX components (they are not resolved, just passed along) in such way, they won't be rendered in IntelliSense

* <Tab>
*
* ```tsx {{ filename: 'src/pages/ExternalDataPage.tsx' }}
* import { useAuth } from '@clerk/clerk-react'
*
Expand DownExpand Up@@ -58,6 +61,14 @@ import { createGetToken, createSignOut } from './utils';
* )
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-auth.md#nextjs-01}
*
* </Tab>
* </Tabs>
*/
export const useAuth = (initialAuthState: any = {}): UseAuthReturn => {
useAssertWrappedByClerkProvider('useAuth');
Expand Down
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useSignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,9 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
*
* The following example uses the `useSignIn()` hook to access the [`SignIn`](https://clerk.com/docs/references/javascript/sign-in/sign-in) object, which contains the current sign-in attempt status and methods to create a new sign-in attempt. The `isLoaded` property is used to handle the loading state.
*
* <Tabs items='React,Next.js'>
* <Tab>
*
* ```tsx {{ filename: 'src/pages/SignInPage.tsx' }}
* import { useSignIn } from '@clerk/clerk-react'
*
Expand All@@ -28,6 +31,14 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-sign-in.md#nextjs-01}
*
* </Tab>
* </Tabs>
*
* @example
* ### Create a custom sign-in flow with `useSignIn()`
*
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/green-donuts-press.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/shared': patch
'@clerk/clerk-react': patch
'@clerk/types': patch
---

Improve JSDoc documentation
73 changes: 71 additions & 2 deletions .typedoc/custom-plugin.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
// @ts-check
import { MarkdownRendererEvent } from 'typedoc-plugin-markdown';
import { MarkdownPageEvent, MarkdownRendererEvent } from 'typedoc-plugin-markdown';

/**
* A list of files where we want to remove any headings
*/
const FILES_WITHOUT_HEADINGS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the files only contain the contents of a single interface.

For example:

# Parameters
Table goes here

So since I want to use the table contents as a partial/include somewhere else I need to get rid off the heading. But since I don't want to remove headings everywhere I'm removing them only from the files I want to use in such way.

'use-organization-return.mdx',
'use-organization-params.mdx',
'paginated-resources.mdx',
'pages-or-infinite-options.mdx',
'pages-or-infinite-options.mdx',
'paginated-hook-config.mdx',
'use-organization-list-return.mdx',
'use-organization-list-params.mdx',
];

/**
* An array of tuples where the first element is the file name and the second element is the new path.
*/
const LINK_REPLACEMENTS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes there are relative links like [PaginatedResponse](../types/paginated-response.mdx) in the files. The link to the also generated file through typedoc. But we already have that page manually created and want to link to it. So this replaces relative links with links to our existing docs.

['clerk-paginated-response', '/docs/references/javascript/types/clerk-paginated-response'],
['paginated-resources', '#paginated-resources'],
];

/**
* Inside the generated MDX files are links to other generated MDX files. These relative links need to be replaced with absolute links to pages that exist on clerk.com.
* For example, `[Foobar](../../foo/bar.mdx)` needs to be replaced with `[Foobar](/docs/foo/bar)`.
* It also shouldn't matter how level deep the relative link is.
*
* This function returns an array of `{ pattern: string, replace: string }` to pass into the `typedoc-plugin-replace-text` plugin.
*/
function getRelativeLinkReplacements() {
return LINK_REPLACEMENTS.map(([fileName, newPath]) => {
return {
pattern: new RegExp(`\\((?:\\.{1,2}\\/)+.*?${fileName}\\.mdx\\)`, 'g'),
replace: `(${newPath})`,
};
});
}

/**
* @param {string} str
Expand All@@ -13,8 +51,9 @@ function toKebabCase(str) {
*/
export function load(app) {
app.renderer.on(MarkdownRendererEvent.BEGIN, output => {
// Do not output README.mdx files
// Modify the output object
output.urls = output.urls
// Do not output README.mdx files
?.filter(e => !e.url.endsWith('README.mdx'))
.map(e => {
// Convert URLs (by default camelCase) to kebab-case
Expand All@@ -23,7 +62,37 @@ export function load(app) {
e.url = kebabUrl;
e.model.url = kebabUrl;

/**
* For the `@clerk/shared` package it outputs the hooks as for example: shared/react/hooks/use-clerk/functions/use-clerk.mdx.
* It also places the interfaces as shared/react/hooks/use-organization/interfaces/use-organization-return.mdx
* Group all those .mdx files under shared/react/hooks
*/
if (e.url.includes('shared/react/hooks')) {
e.url = e.url.replace(/\/[^/]+\/(functions|interfaces)\//, '/');
e.model.url = e.url;
}

return e;
});
});

app.renderer.on(MarkdownPageEvent.END, output => {
const fileName = output.url.split('/').pop();
const linkReplacements = getRelativeLinkReplacements();

for (const { pattern, replace } of linkReplacements) {
if (output.contents) {
output.contents = output.contents.replace(pattern, replace);
}
}

if (fileName) {
if (FILES_WITHOUT_HEADINGS.includes(fileName)) {
if (output.contents) {
// Remove any headings from the file, irrespective of the level
output.contents = output.contents.replace(/^#+\s.+/gm, '');
}
}
}
});
}
112 changes: 110 additions & 2 deletions .typedoc/custom-theme.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// @ts-check
import { ReflectionKind } from 'typedoc';
import { ArrayType, IntersectionType, ReflectionKind, ReflectionType, UnionType } from 'typedoc';
import { MarkdownTheme, MarkdownThemeContext } from 'typedoc-plugin-markdown';

/**
Expand DownExpand Up@@ -36,7 +36,7 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {
this.partials = {
...superPartials,
/**
* Copied from default theme / source code. This hides the return type from the output
* Copied from default theme / source code. This hides the return type heading over the table from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/179a54c502b318cd4f3951e5e8b90f7f7a4752d8/packages/typedoc-plugin-markdown/src/theme/context/partials/member.signatureReturns.ts
* @param {import('typedoc').SignatureReflection} model
* @param {{ headingLevel: number }} options
Expand DownExpand Up@@ -235,6 +235,114 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {

md.push(this.partials.body(model, { headingLevel: options.headingLevel }));

return md.join('\n\n');
},
/**
* Copied from default theme / source code. This hides the "Type parameters" section and the declaration title from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/e798507a3c04f9ddf7710baf4cc7836053e438ff/packages/typedoc-plugin-markdown/src/theme/context/partials/member.declaration.ts
* @param {import('typedoc').DeclarationReflection} model
* @param {{ headingLevel: number, nested?: boolean }} options
*/
declaration: (model, options = { headingLevel: 2, nested: false }) => {
const md = [];

const opts = {
nested: false,
...options,
};

if (!opts.nested && model.sources && !this.options.getValue('disableSources')) {
md.push(this.partials.sources(model));
}

if (model?.documents) {
md.push(
this.partials.documents(model, {
headingLevel: options.headingLevel,
}),
);
}

/**
* @type any
*/
const modelType = model.type;
/**
* @type {import('typedoc').DeclarationReflection}
*/
let typeDeclaration = modelType?.declaration;

if (model.type instanceof ArrayType && model.type?.elementType instanceof ReflectionType) {
typeDeclaration = model.type?.elementType?.declaration;
}

const hasTypeDeclaration =
Boolean(typeDeclaration) ||
(model.type instanceof UnionType && model.type?.types.some(type => type instanceof ReflectionType));

if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: true,
showTags: false,
}),
);
}

if (model.type instanceof IntersectionType) {
model.type?.types?.forEach(intersectionType => {
if (intersectionType instanceof ReflectionType && !intersectionType.declaration.signatures) {
if (intersectionType.declaration.children) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

md.push(
this.partials.typeDeclaration(intersectionType.declaration, {
headingLevel: opts.headingLevel,
}),
);
}
}
});
}

if (hasTypeDeclaration) {
if (model.type instanceof UnionType) {
if (this.helpers.hasUsefulTypeDetails(model.type)) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

model.type.types.forEach(type => {
if (type instanceof ReflectionType) {
md.push(this.partials.someType(type, { forceCollapse: true }));
md.push(this.partials.typeDeclarationContainer(model, type.declaration, options));
} else {
md.push(`${this.partials.someType(type)}`);
}
});
}
} else {
const useHeading =
typeDeclaration?.children?.length &&
(model.kind !== ReflectionKind.Property || this.helpers.useTableFormat('properties'));
if (useHeading) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));
}
md.push(this.partials.typeDeclarationContainer(model, typeDeclaration, options));
}
}
if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: false,
showTags: true,
showReturns: true,
}),
);
}

md.push(this.partials.inheritance(model, { headingLevel: opts.headingLevel }));

return md.join('\n\n');
},
};
Expand Down
8 changes: 8 additions & 0 deletions .typedoc/typedoc-prettier-config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
{
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 120,
"useTabs": false,
"bracketSpacing": true
}
2 changes: 1 addition & 1 deletion package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,7 +48,7 @@
"test:integration:tanstack-start": "E2E_APP_ID=tanstack.start pnpm test:integration:base --grep @tanstack-start",
"test:integration:vue": "E2E_APP_ID=vue.vite pnpm test:integration:base --grep @vue",
"turbo:clean": "turbo daemon clean",
"typedoc:generate": "typedoc --tsconfig tsconfig.typedoc.json",
"typedoc:generate": "pnpm build:declarations && typedoc --tsconfig tsconfig.typedoc.json",
"version-packages": "changeset version && pnpm install --lockfile-only --engine-strict=false",
"version-packages:canary": "./scripts/canary.mjs",
"version-packages:snapshot": "./scripts/snapshot.mjs",
Expand Down
1 change: 1 addition & 0 deletions packages/react/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
/*/
!/src/
!/docs/
43 changes: 43 additions & 0 deletions packages/react/docs/use-auth.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/external-data/page.tsx' }}
'use client';

import { useAuth } from '@clerk/nextjs';

export default function ExternalDataPage() {
const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth();

const fetchExternalData = async () => {
const token = await getToken();

// Fetch data from an external API
const response = await fetch('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});

return response.json();
};

if (!isLoaded) {
return <div>Loading...</div>;
}

if (!isSignedIn) {
return <div>Sign in to view this page</div>;
}

return (
<div>
<p>
Hello, {userId}! Your current active session is {sessionId}.
</p>
<button onClick={fetchExternalData}>Fetch Data</button>
</div>
);
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-in.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-in/page.tsx' }}
'use client';

import { useSignIn } from '@clerk/nextjs';

export default function SignInPage() {
const { isLoaded, signIn } = useSignIn();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-in attempt status is {signIn?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-up.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-up/page.tsx' }}
'use client';

import { useSignUp } from '@clerk/nextjs';

export default function SignUpPage() {
const { isLoaded, signUp } = useSignUp();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-up attempt status is {signUp?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useAuth.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,9 @@ import { createGetToken, createSignOut } from './utils';
*
* The following example demonstrates how to use the `useAuth()` hook to access the current auth state, like whether the user is signed in or not. It also includes a basic example for using the `getToken()` method to retrieve a session token for fetching data from an external resource.
*
* <Tabs items='React,Next.js'>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use these MDX components (they are not resolved, just passed along) in such way, they won't be rendered in IntelliSense

* <Tab>
*
* ```tsx {{ filename: 'src/pages/ExternalDataPage.tsx' }}
* import { useAuth } from '@clerk/clerk-react'
*
Expand DownExpand Up@@ -58,6 +61,14 @@ import { createGetToken, createSignOut } from './utils';
* )
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-auth.md#nextjs-01}
*
* </Tab>
* </Tabs>
*/
export const useAuth = (initialAuthState: any = {}): UseAuthReturn => {
useAssertWrappedByClerkProvider('useAuth');
Expand Down
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useSignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,9 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
*
* The following example uses the `useSignIn()` hook to access the [`SignIn`](https://clerk.com/docs/references/javascript/sign-in/sign-in) object, which contains the current sign-in attempt status and methods to create a new sign-in attempt. The `isLoaded` property is used to handle the loading state.
*
* <Tabs items='React,Next.js'>
* <Tab>
*
* ```tsx {{ filename: 'src/pages/SignInPage.tsx' }}
* import { useSignIn } from '@clerk/clerk-react'
*
Expand All@@ -28,6 +31,14 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-sign-in.md#nextjs-01}
*
* </Tab>
* </Tabs>
*
* @example
* ### Create a custom sign-in flow with `useSignIn()`
*
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/green-donuts-press.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/shared': patch
'@clerk/clerk-react': patch
'@clerk/types': patch
---

Improve JSDoc documentation
73 changes: 71 additions & 2 deletions .typedoc/custom-plugin.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
// @ts-check
import { MarkdownRendererEvent } from 'typedoc-plugin-markdown';
import { MarkdownPageEvent, MarkdownRendererEvent } from 'typedoc-plugin-markdown';

/**
* A list of files where we want to remove any headings
*/
const FILES_WITHOUT_HEADINGS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the files only contain the contents of a single interface.

For example:

# Parameters
Table goes here

So since I want to use the table contents as a partial/include somewhere else I need to get rid off the heading. But since I don't want to remove headings everywhere I'm removing them only from the files I want to use in such way.

'use-organization-return.mdx',
'use-organization-params.mdx',
'paginated-resources.mdx',
'pages-or-infinite-options.mdx',
'pages-or-infinite-options.mdx',
'paginated-hook-config.mdx',
'use-organization-list-return.mdx',
'use-organization-list-params.mdx',
];

/**
* An array of tuples where the first element is the file name and the second element is the new path.
*/
const LINK_REPLACEMENTS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes there are relative links like [PaginatedResponse](../types/paginated-response.mdx) in the files. The link to the also generated file through typedoc. But we already have that page manually created and want to link to it. So this replaces relative links with links to our existing docs.

['clerk-paginated-response', '/docs/references/javascript/types/clerk-paginated-response'],
['paginated-resources', '#paginated-resources'],
];

/**
* Inside the generated MDX files are links to other generated MDX files. These relative links need to be replaced with absolute links to pages that exist on clerk.com.
* For example, `[Foobar](../../foo/bar.mdx)` needs to be replaced with `[Foobar](/docs/foo/bar)`.
* It also shouldn't matter how level deep the relative link is.
*
* This function returns an array of `{ pattern: string, replace: string }` to pass into the `typedoc-plugin-replace-text` plugin.
*/
function getRelativeLinkReplacements() {
return LINK_REPLACEMENTS.map(([fileName, newPath]) => {
return {
pattern: new RegExp(`\\((?:\\.{1,2}\\/)+.*?${fileName}\\.mdx\\)`, 'g'),
replace: `(${newPath})`,
};
});
}

/**
* @param {string} str
Expand All@@ -13,8 +51,9 @@ function toKebabCase(str) {
*/
export function load(app) {
app.renderer.on(MarkdownRendererEvent.BEGIN, output => {
// Do not output README.mdx files
// Modify the output object
output.urls = output.urls
// Do not output README.mdx files
?.filter(e => !e.url.endsWith('README.mdx'))
.map(e => {
// Convert URLs (by default camelCase) to kebab-case
Expand All@@ -23,7 +62,37 @@ export function load(app) {
e.url = kebabUrl;
e.model.url = kebabUrl;

/**
* For the `@clerk/shared` package it outputs the hooks as for example: shared/react/hooks/use-clerk/functions/use-clerk.mdx.
* It also places the interfaces as shared/react/hooks/use-organization/interfaces/use-organization-return.mdx
* Group all those .mdx files under shared/react/hooks
*/
if (e.url.includes('shared/react/hooks')) {
e.url = e.url.replace(/\/[^/]+\/(functions|interfaces)\//, '/');
e.model.url = e.url;
}

return e;
});
});

app.renderer.on(MarkdownPageEvent.END, output => {
const fileName = output.url.split('/').pop();
const linkReplacements = getRelativeLinkReplacements();

for (const { pattern, replace } of linkReplacements) {
if (output.contents) {
output.contents = output.contents.replace(pattern, replace);
}
}

if (fileName) {
if (FILES_WITHOUT_HEADINGS.includes(fileName)) {
if (output.contents) {
// Remove any headings from the file, irrespective of the level
output.contents = output.contents.replace(/^#+\s.+/gm, '');
}
}
}
});
}
112 changes: 110 additions & 2 deletions .typedoc/custom-theme.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// @ts-check
import { ReflectionKind } from 'typedoc';
import { ArrayType, IntersectionType, ReflectionKind, ReflectionType, UnionType } from 'typedoc';
import { MarkdownTheme, MarkdownThemeContext } from 'typedoc-plugin-markdown';

/**
Expand DownExpand Up@@ -36,7 +36,7 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {
this.partials = {
...superPartials,
/**
* Copied from default theme / source code. This hides the return type from the output
* Copied from default theme / source code. This hides the return type heading over the table from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/179a54c502b318cd4f3951e5e8b90f7f7a4752d8/packages/typedoc-plugin-markdown/src/theme/context/partials/member.signatureReturns.ts
* @param {import('typedoc').SignatureReflection} model
* @param {{ headingLevel: number }} options
Expand DownExpand Up@@ -235,6 +235,114 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {

md.push(this.partials.body(model, { headingLevel: options.headingLevel }));

return md.join('\n\n');
},
/**
* Copied from default theme / source code. This hides the "Type parameters" section and the declaration title from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/e798507a3c04f9ddf7710baf4cc7836053e438ff/packages/typedoc-plugin-markdown/src/theme/context/partials/member.declaration.ts
* @param {import('typedoc').DeclarationReflection} model
* @param {{ headingLevel: number, nested?: boolean }} options
*/
declaration: (model, options = { headingLevel: 2, nested: false }) => {
const md = [];

const opts = {
nested: false,
...options,
};

if (!opts.nested && model.sources && !this.options.getValue('disableSources')) {
md.push(this.partials.sources(model));
}

if (model?.documents) {
md.push(
this.partials.documents(model, {
headingLevel: options.headingLevel,
}),
);
}

/**
* @type any
*/
const modelType = model.type;
/**
* @type {import('typedoc').DeclarationReflection}
*/
let typeDeclaration = modelType?.declaration;

if (model.type instanceof ArrayType && model.type?.elementType instanceof ReflectionType) {
typeDeclaration = model.type?.elementType?.declaration;
}

const hasTypeDeclaration =
Boolean(typeDeclaration) ||
(model.type instanceof UnionType && model.type?.types.some(type => type instanceof ReflectionType));

if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: true,
showTags: false,
}),
);
}

if (model.type instanceof IntersectionType) {
model.type?.types?.forEach(intersectionType => {
if (intersectionType instanceof ReflectionType && !intersectionType.declaration.signatures) {
if (intersectionType.declaration.children) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

md.push(
this.partials.typeDeclaration(intersectionType.declaration, {
headingLevel: opts.headingLevel,
}),
);
}
}
});
}

if (hasTypeDeclaration) {
if (model.type instanceof UnionType) {
if (this.helpers.hasUsefulTypeDetails(model.type)) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

model.type.types.forEach(type => {
if (type instanceof ReflectionType) {
md.push(this.partials.someType(type, { forceCollapse: true }));
md.push(this.partials.typeDeclarationContainer(model, type.declaration, options));
} else {
md.push(`${this.partials.someType(type)}`);
}
});
}
} else {
const useHeading =
typeDeclaration?.children?.length &&
(model.kind !== ReflectionKind.Property || this.helpers.useTableFormat('properties'));
if (useHeading) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));
}
md.push(this.partials.typeDeclarationContainer(model, typeDeclaration, options));
}
}
if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: false,
showTags: true,
showReturns: true,
}),
);
}

md.push(this.partials.inheritance(model, { headingLevel: opts.headingLevel }));

return md.join('\n\n');
},
};
Expand Down
8 changes: 8 additions & 0 deletions .typedoc/typedoc-prettier-config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
{
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 120,
"useTabs": false,
"bracketSpacing": true
}
2 changes: 1 addition & 1 deletion package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,7 +48,7 @@
"test:integration:tanstack-start": "E2E_APP_ID=tanstack.start pnpm test:integration:base --grep @tanstack-start",
"test:integration:vue": "E2E_APP_ID=vue.vite pnpm test:integration:base --grep @vue",
"turbo:clean": "turbo daemon clean",
"typedoc:generate": "typedoc --tsconfig tsconfig.typedoc.json",
"typedoc:generate": "pnpm build:declarations && typedoc --tsconfig tsconfig.typedoc.json",
"version-packages": "changeset version && pnpm install --lockfile-only --engine-strict=false",
"version-packages:canary": "./scripts/canary.mjs",
"version-packages:snapshot": "./scripts/snapshot.mjs",
Expand Down
1 change: 1 addition & 0 deletions packages/react/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
/*/
!/src/
!/docs/
43 changes: 43 additions & 0 deletions packages/react/docs/use-auth.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/external-data/page.tsx' }}
'use client';

import { useAuth } from '@clerk/nextjs';

export default function ExternalDataPage() {
const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth();

const fetchExternalData = async () => {
const token = await getToken();

// Fetch data from an external API
const response = await fetch('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});

return response.json();
};

if (!isLoaded) {
return <div>Loading...</div>;
}

if (!isSignedIn) {
return <div>Sign in to view this page</div>;
}

return (
<div>
<p>
Hello, {userId}! Your current active session is {sessionId}.
</p>
<button onClick={fetchExternalData}>Fetch Data</button>
</div>
);
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-in.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-in/page.tsx' }}
'use client';

import { useSignIn } from '@clerk/nextjs';

export default function SignInPage() {
const { isLoaded, signIn } = useSignIn();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-in attempt status is {signIn?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-up.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-up/page.tsx' }}
'use client';

import { useSignUp } from '@clerk/nextjs';

export default function SignUpPage() {
const { isLoaded, signUp } = useSignUp();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-up attempt status is {signUp?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useAuth.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,9 @@ import { createGetToken, createSignOut } from './utils';
*
* The following example demonstrates how to use the `useAuth()` hook to access the current auth state, like whether the user is signed in or not. It also includes a basic example for using the `getToken()` method to retrieve a session token for fetching data from an external resource.
*
* <Tabs items='React,Next.js'>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use these MDX components (they are not resolved, just passed along) in such way, they won't be rendered in IntelliSense

* <Tab>
*
* ```tsx {{ filename: 'src/pages/ExternalDataPage.tsx' }}
* import { useAuth } from '@clerk/clerk-react'
*
Expand DownExpand Up@@ -58,6 +61,14 @@ import { createGetToken, createSignOut } from './utils';
* )
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-auth.md#nextjs-01}
*
* </Tab>
* </Tabs>
*/
export const useAuth = (initialAuthState: any = {}): UseAuthReturn => {
useAssertWrappedByClerkProvider('useAuth');
Expand Down
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useSignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,9 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
*
* The following example uses the `useSignIn()` hook to access the [`SignIn`](https://clerk.com/docs/references/javascript/sign-in/sign-in) object, which contains the current sign-in attempt status and methods to create a new sign-in attempt. The `isLoaded` property is used to handle the loading state.
*
* <Tabs items='React,Next.js'>
* <Tab>
*
* ```tsx {{ filename: 'src/pages/SignInPage.tsx' }}
* import { useSignIn } from '@clerk/clerk-react'
*
Expand All@@ -28,6 +31,14 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-sign-in.md#nextjs-01}
*
* </Tab>
* </Tabs>
*
* @example
* ### Create a custom sign-in flow with `useSignIn()`
*
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/green-donuts-press.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/shared': patch
'@clerk/clerk-react': patch
'@clerk/types': patch
---

Improve JSDoc documentation
73 changes: 71 additions & 2 deletions .typedoc/custom-plugin.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
// @ts-check
import { MarkdownRendererEvent } from 'typedoc-plugin-markdown';
import { MarkdownPageEvent, MarkdownRendererEvent } from 'typedoc-plugin-markdown';

/**
* A list of files where we want to remove any headings
*/
const FILES_WITHOUT_HEADINGS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the files only contain the contents of a single interface.

For example:

# Parameters
Table goes here

So since I want to use the table contents as a partial/include somewhere else I need to get rid off the heading. But since I don't want to remove headings everywhere I'm removing them only from the files I want to use in such way.

'use-organization-return.mdx',
'use-organization-params.mdx',
'paginated-resources.mdx',
'pages-or-infinite-options.mdx',
'pages-or-infinite-options.mdx',
'paginated-hook-config.mdx',
'use-organization-list-return.mdx',
'use-organization-list-params.mdx',
];

/**
* An array of tuples where the first element is the file name and the second element is the new path.
*/
const LINK_REPLACEMENTS = [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes there are relative links like [PaginatedResponse](../types/paginated-response.mdx) in the files. The link to the also generated file through typedoc. But we already have that page manually created and want to link to it. So this replaces relative links with links to our existing docs.

['clerk-paginated-response', '/docs/references/javascript/types/clerk-paginated-response'],
['paginated-resources', '#paginated-resources'],
];

/**
* Inside the generated MDX files are links to other generated MDX files. These relative links need to be replaced with absolute links to pages that exist on clerk.com.
* For example, `[Foobar](../../foo/bar.mdx)` needs to be replaced with `[Foobar](/docs/foo/bar)`.
* It also shouldn't matter how level deep the relative link is.
*
* This function returns an array of `{ pattern: string, replace: string }` to pass into the `typedoc-plugin-replace-text` plugin.
*/
function getRelativeLinkReplacements() {
return LINK_REPLACEMENTS.map(([fileName, newPath]) => {
return {
pattern: new RegExp(`\\((?:\\.{1,2}\\/)+.*?${fileName}\\.mdx\\)`, 'g'),
replace: `(${newPath})`,
};
});
}

/**
* @param {string} str
Expand All@@ -13,8 +51,9 @@ function toKebabCase(str) {
*/
export function load(app) {
app.renderer.on(MarkdownRendererEvent.BEGIN, output => {
// Do not output README.mdx files
// Modify the output object
output.urls = output.urls
// Do not output README.mdx files
?.filter(e => !e.url.endsWith('README.mdx'))
.map(e => {
// Convert URLs (by default camelCase) to kebab-case
Expand All@@ -23,7 +62,37 @@ export function load(app) {
e.url = kebabUrl;
e.model.url = kebabUrl;

/**
* For the `@clerk/shared` package it outputs the hooks as for example: shared/react/hooks/use-clerk/functions/use-clerk.mdx.
* It also places the interfaces as shared/react/hooks/use-organization/interfaces/use-organization-return.mdx
* Group all those .mdx files under shared/react/hooks
*/
if (e.url.includes('shared/react/hooks')) {
e.url = e.url.replace(/\/[^/]+\/(functions|interfaces)\//, '/');
e.model.url = e.url;
}

return e;
});
});

app.renderer.on(MarkdownPageEvent.END, output => {
const fileName = output.url.split('/').pop();
const linkReplacements = getRelativeLinkReplacements();

for (const { pattern, replace } of linkReplacements) {
if (output.contents) {
output.contents = output.contents.replace(pattern, replace);
}
}

if (fileName) {
if (FILES_WITHOUT_HEADINGS.includes(fileName)) {
if (output.contents) {
// Remove any headings from the file, irrespective of the level
output.contents = output.contents.replace(/^#+\s.+/gm, '');
}
}
}
});
}
112 changes: 110 additions & 2 deletions .typedoc/custom-theme.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// @ts-check
import { ReflectionKind } from 'typedoc';
import { ArrayType, IntersectionType, ReflectionKind, ReflectionType, UnionType } from 'typedoc';
import { MarkdownTheme, MarkdownThemeContext } from 'typedoc-plugin-markdown';

/**
Expand DownExpand Up@@ -36,7 +36,7 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {
this.partials = {
...superPartials,
/**
* Copied from default theme / source code. This hides the return type from the output
* Copied from default theme / source code. This hides the return type heading over the table from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/179a54c502b318cd4f3951e5e8b90f7f7a4752d8/packages/typedoc-plugin-markdown/src/theme/context/partials/member.signatureReturns.ts
* @param {import('typedoc').SignatureReflection} model
* @param {{ headingLevel: number }} options
Expand DownExpand Up@@ -235,6 +235,114 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {

md.push(this.partials.body(model, { headingLevel: options.headingLevel }));

return md.join('\n\n');
},
/**
* Copied from default theme / source code. This hides the "Type parameters" section and the declaration title from the output
* https://github.com/typedoc2md/typedoc-plugin-markdown/blob/e798507a3c04f9ddf7710baf4cc7836053e438ff/packages/typedoc-plugin-markdown/src/theme/context/partials/member.declaration.ts
* @param {import('typedoc').DeclarationReflection} model
* @param {{ headingLevel: number, nested?: boolean }} options
*/
declaration: (model, options = { headingLevel: 2, nested: false }) => {
const md = [];

const opts = {
nested: false,
...options,
};

if (!opts.nested && model.sources && !this.options.getValue('disableSources')) {
md.push(this.partials.sources(model));
}

if (model?.documents) {
md.push(
this.partials.documents(model, {
headingLevel: options.headingLevel,
}),
);
}

/**
* @type any
*/
const modelType = model.type;
/**
* @type {import('typedoc').DeclarationReflection}
*/
let typeDeclaration = modelType?.declaration;

if (model.type instanceof ArrayType && model.type?.elementType instanceof ReflectionType) {
typeDeclaration = model.type?.elementType?.declaration;
}

const hasTypeDeclaration =
Boolean(typeDeclaration) ||
(model.type instanceof UnionType && model.type?.types.some(type => type instanceof ReflectionType));

if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: true,
showTags: false,
}),
);
}

if (model.type instanceof IntersectionType) {
model.type?.types?.forEach(intersectionType => {
if (intersectionType instanceof ReflectionType && !intersectionType.declaration.signatures) {
if (intersectionType.declaration.children) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

md.push(
this.partials.typeDeclaration(intersectionType.declaration, {
headingLevel: opts.headingLevel,
}),
);
}
}
});
}

if (hasTypeDeclaration) {
if (model.type instanceof UnionType) {
if (this.helpers.hasUsefulTypeDetails(model.type)) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));

model.type.types.forEach(type => {
if (type instanceof ReflectionType) {
md.push(this.partials.someType(type, { forceCollapse: true }));
md.push(this.partials.typeDeclarationContainer(model, type.declaration, options));
} else {
md.push(`${this.partials.someType(type)}`);
}
});
}
} else {
const useHeading =
typeDeclaration?.children?.length &&
(model.kind !== ReflectionKind.Property || this.helpers.useTableFormat('properties'));
if (useHeading) {
md.push(heading(opts.headingLevel, this.i18n.theme_type_declaration()));
}
md.push(this.partials.typeDeclarationContainer(model, typeDeclaration, options));
}
}
if (model.comment) {
md.push(
this.partials.comment(model.comment, {
headingLevel: opts.headingLevel,
showSummary: false,
showTags: true,
showReturns: true,
}),
);
}

md.push(this.partials.inheritance(model, { headingLevel: opts.headingLevel }));

return md.join('\n\n');
},
};
Expand Down
8 changes: 8 additions & 0 deletions .typedoc/typedoc-prettier-config.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
{
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 120,
"useTabs": false,
"bracketSpacing": true
}
2 changes: 1 addition & 1 deletion package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,7 +48,7 @@
"test:integration:tanstack-start": "E2E_APP_ID=tanstack.start pnpm test:integration:base --grep @tanstack-start",
"test:integration:vue": "E2E_APP_ID=vue.vite pnpm test:integration:base --grep @vue",
"turbo:clean": "turbo daemon clean",
"typedoc:generate": "typedoc --tsconfig tsconfig.typedoc.json",
"typedoc:generate": "pnpm build:declarations && typedoc --tsconfig tsconfig.typedoc.json",
"version-packages": "changeset version && pnpm install --lockfile-only --engine-strict=false",
"version-packages:canary": "./scripts/canary.mjs",
"version-packages:snapshot": "./scripts/snapshot.mjs",
Expand Down
1 change: 1 addition & 0 deletions packages/react/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
/*/
!/src/
!/docs/
43 changes: 43 additions & 0 deletions packages/react/docs/use-auth.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/external-data/page.tsx' }}
'use client';

import { useAuth } from '@clerk/nextjs';

export default function ExternalDataPage() {
const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth();

const fetchExternalData = async () => {
const token = await getToken();

// Fetch data from an external API
const response = await fetch('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});

return response.json();
};

if (!isLoaded) {
return <div>Loading...</div>;
}

if (!isSignedIn) {
return <div>Sign in to view this page</div>;
}

return (
<div>
<p>
Hello, {userId}! Your current active session is {sessionId}.
</p>
<button onClick={fetchExternalData}>Fetch Data</button>
</div>
);
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-in.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-in/page.tsx' }}
'use client';

import { useSignIn } from '@clerk/nextjs';

export default function SignInPage() {
const { isLoaded, signIn } = useSignIn();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-in attempt status is {signIn?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
20 changes: 20 additions & 0 deletions packages/react/docs/use-sign-up.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
<!-- #region nextjs-01 -->

```tsx {{ filename: 'app/sign-up/page.tsx' }}
'use client';

import { useSignUp } from '@clerk/nextjs';

export default function SignUpPage() {
const { isLoaded, signUp } = useSignUp();

if (!isLoaded) {
// Handle loading state
return null;
}

return <div>The current sign-up attempt status is {signUp?.status}.</div>;
}
```

<!-- #endregion nextjs-01 -->
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useAuth.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,9 @@ import { createGetToken, createSignOut } from './utils';
*
* The following example demonstrates how to use the `useAuth()` hook to access the current auth state, like whether the user is signed in or not. It also includes a basic example for using the `getToken()` method to retrieve a session token for fetching data from an external resource.
*
* <Tabs items='React,Next.js'>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use these MDX components (they are not resolved, just passed along) in such way, they won't be rendered in IntelliSense

* <Tab>
*
* ```tsx {{ filename: 'src/pages/ExternalDataPage.tsx' }}
* import { useAuth } from '@clerk/clerk-react'
*
Expand DownExpand Up@@ -58,6 +61,14 @@ import { createGetToken, createSignOut } from './utils';
* )
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-auth.md#nextjs-01}
*
* </Tab>
* </Tabs>
*/
export const useAuth = (initialAuthState: any = {}): UseAuthReturn => {
useAssertWrappedByClerkProvider('useAuth');
Expand Down
11 changes: 11 additions & 0 deletions packages/react/src/hooks/useSignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,9 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
*
* The following example uses the `useSignIn()` hook to access the [`SignIn`](https://clerk.com/docs/references/javascript/sign-in/sign-in) object, which contains the current sign-in attempt status and methods to create a new sign-in attempt. The `isLoaded` property is used to handle the loading state.
*
* <Tabs items='React,Next.js'>
* <Tab>
*
* ```tsx {{ filename: 'src/pages/SignInPage.tsx' }}
* import { useSignIn } from '@clerk/clerk-react'
*
Expand All@@ -28,6 +31,14 @@ import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvid
* }
* ```
*
* </Tab>
* <Tab>
*
* {@include ../../docs/use-sign-in.md#nextjs-01}
*
* </Tab>
* </Tabs>
*
* @example
* ### Create a custom sign-in flow with `useSignIn()`
*
Expand Down
Loading