diff --git a/apps/docs/content/docs/components/actions/button/index.mdx b/apps/docs/content/docs/components/actions/button/index.mdx index 25862530..5e54a293 100644 --- a/apps/docs/content/docs/components/actions/button/index.mdx +++ b/apps/docs/content/docs/components/actions/button/index.mdx @@ -1,6 +1,8 @@ --- title: Button description: A labelled control for actions in an interface. +source: packages/@luke-ui/react/src/button +reactAria: https://react-spectrum.adobe.com/react-aria/Button.html --- `Button` triggers an action, such as saving a form, opening a dialog, or deleting an item. Use diff --git a/apps/docs/content/docs/components/actions/button/props.mdx b/apps/docs/content/docs/components/actions/button/props.mdx index e377d6bd..dd1d8805 100644 --- a/apps/docs/content/docs/components/actions/button/props.mdx +++ b/apps/docs/content/docs/components/actions/button/props.mdx @@ -1,6 +1,8 @@ --- title: Button description: A labelled control for actions in an interface. +source: packages/@luke-ui/react/src/button +reactAria: https://react-spectrum.adobe.com/react-aria/Button.html --- ## Props diff --git a/apps/docs/content/docs/components/actions/icon-button/index.mdx b/apps/docs/content/docs/components/actions/icon-button/index.mdx index 4a929591..0f386719 100644 --- a/apps/docs/content/docs/components/actions/icon-button/index.mdx +++ b/apps/docs/content/docs/components/actions/icon-button/index.mdx @@ -1,6 +1,8 @@ --- title: Icon Button description: A compact icon-only control for an action. +source: packages/@luke-ui/react/src/icon-button +reactAria: https://react-spectrum.adobe.com/react-aria/Button.html --- `IconButton` triggers a familiar compact action, such as closing a panel, deleting a row, or adding diff --git a/apps/docs/content/docs/components/actions/icon-button/props.mdx b/apps/docs/content/docs/components/actions/icon-button/props.mdx index fc5dfd9d..3b1672c0 100644 --- a/apps/docs/content/docs/components/actions/icon-button/props.mdx +++ b/apps/docs/content/docs/components/actions/icon-button/props.mdx @@ -1,6 +1,8 @@ --- title: Icon Button description: A compact icon-only control for an action. +source: packages/@luke-ui/react/src/icon-button +reactAria: https://react-spectrum.adobe.com/react-aria/Button.html --- ## Props diff --git a/apps/docs/content/docs/components/actions/link/index.mdx b/apps/docs/content/docs/components/actions/link/index.mdx index 75a55591..54f454b9 100644 --- a/apps/docs/content/docs/components/actions/link/index.mdx +++ b/apps/docs/content/docs/components/actions/link/index.mdx @@ -1,6 +1,8 @@ --- title: Link description: A styled link for inline and standalone navigation. +source: packages/@luke-ui/react/src/link +reactAria: https://react-spectrum.adobe.com/react-aria/Link.html --- `Link` navigates to another URL or route. Use [`Button`](/components/actions/button) for an action diff --git a/apps/docs/content/docs/components/actions/link/props.mdx b/apps/docs/content/docs/components/actions/link/props.mdx index 7793ea81..1d9cb66b 100644 --- a/apps/docs/content/docs/components/actions/link/props.mdx +++ b/apps/docs/content/docs/components/actions/link/props.mdx @@ -1,6 +1,8 @@ --- title: Link description: A styled link for inline and standalone navigation. +source: packages/@luke-ui/react/src/link +reactAria: https://react-spectrum.adobe.com/react-aria/Link.html --- ## Props diff --git a/apps/docs/content/docs/components/feedback/loading-skeleton/index.mdx b/apps/docs/content/docs/components/feedback/loading-skeleton/index.mdx index bfdee044..0650c84a 100644 --- a/apps/docs/content/docs/components/feedback/loading-skeleton/index.mdx +++ b/apps/docs/content/docs/components/feedback/loading-skeleton/index.mdx @@ -1,6 +1,7 @@ --- title: Loading Skeleton description: Replaces content with a placeholder that keeps its shape while it loads. +source: packages/@luke-ui/react/src/loading-skeleton --- Use `LoadingSkeleton` when content is loading but its layout is known. Wrap the content that will diff --git a/apps/docs/content/docs/components/feedback/loading-skeleton/props.mdx b/apps/docs/content/docs/components/feedback/loading-skeleton/props.mdx index 79d044d7..a0cf384a 100644 --- a/apps/docs/content/docs/components/feedback/loading-skeleton/props.mdx +++ b/apps/docs/content/docs/components/feedback/loading-skeleton/props.mdx @@ -1,6 +1,7 @@ --- title: Loading Skeleton description: Replaces content with a placeholder that keeps its shape while it loads. +source: packages/@luke-ui/react/src/loading-skeleton --- ## Props diff --git a/apps/docs/content/docs/components/feedback/loading-spinner/index.mdx b/apps/docs/content/docs/components/feedback/loading-spinner/index.mdx index 55aedb26..c01e4607 100644 --- a/apps/docs/content/docs/components/feedback/loading-spinner/index.mdx +++ b/apps/docs/content/docs/components/feedback/loading-spinner/index.mdx @@ -1,6 +1,7 @@ --- title: Loading Spinner description: Animated indicator for work that is still in progress. +source: packages/@luke-ui/react/src/loading-spinner --- `LoadingSpinner` shows that work is in progress. Use it on its own for a small loading region. Wrap diff --git a/apps/docs/content/docs/components/feedback/loading-spinner/props.mdx b/apps/docs/content/docs/components/feedback/loading-spinner/props.mdx index 89418f22..76f5bf9e 100644 --- a/apps/docs/content/docs/components/feedback/loading-spinner/props.mdx +++ b/apps/docs/content/docs/components/feedback/loading-spinner/props.mdx @@ -1,6 +1,7 @@ --- title: Loading Spinner description: Animated indicator for work that is still in progress. +source: packages/@luke-ui/react/src/loading-spinner --- ## Props diff --git a/apps/docs/content/docs/components/forms/checkbox/index.mdx b/apps/docs/content/docs/components/forms/checkbox/index.mdx index dc9048df..f3d767a3 100644 --- a/apps/docs/content/docs/components/forms/checkbox/index.mdx +++ b/apps/docs/content/docs/components/forms/checkbox/index.mdx @@ -2,6 +2,8 @@ title: Checkbox description: Lets someone select an independent option, with optional supporting and validation text. +source: packages/@luke-ui/react/src/checkbox +reactAria: https://react-spectrum.adobe.com/react-aria/Checkbox.html --- Use `Checkbox` when someone can choose an option independently of nearby controls. Pass diff --git a/apps/docs/content/docs/components/forms/checkbox/props.mdx b/apps/docs/content/docs/components/forms/checkbox/props.mdx index c361a413..fc54ff96 100644 --- a/apps/docs/content/docs/components/forms/checkbox/props.mdx +++ b/apps/docs/content/docs/components/forms/checkbox/props.mdx @@ -2,6 +2,8 @@ title: Checkbox description: Lets someone select an independent option, with optional supporting and validation text. +source: packages/@luke-ui/react/src/checkbox +reactAria: https://react-spectrum.adobe.com/react-aria/Checkbox.html --- ## Props diff --git a/apps/docs/content/docs/components/forms/combobox-field/index.mdx b/apps/docs/content/docs/components/forms/combobox-field/index.mdx index adb9c5cb..f14422de 100644 --- a/apps/docs/content/docs/components/forms/combobox-field/index.mdx +++ b/apps/docs/content/docs/components/forms/combobox-field/index.mdx @@ -1,6 +1,8 @@ --- title: Combobox Field description: Searchable single-select field with label, validation, and option list. +source: packages/@luke-ui/react/src/combobox-field +reactAria: https://react-spectrum.adobe.com/react-aria/ComboBox.html --- Use `ComboboxField` when someone needs to search for or choose one option from a list. It combines diff --git a/apps/docs/content/docs/components/forms/combobox-field/props.mdx b/apps/docs/content/docs/components/forms/combobox-field/props.mdx index 4b71c092..495d6038 100644 --- a/apps/docs/content/docs/components/forms/combobox-field/props.mdx +++ b/apps/docs/content/docs/components/forms/combobox-field/props.mdx @@ -1,6 +1,8 @@ --- title: Combobox Field description: Searchable single-select field with label, validation, and option list. +source: packages/@luke-ui/react/src/combobox-field +reactAria: https://react-spectrum.adobe.com/react-aria/ComboBox.html --- ## Props diff --git a/apps/docs/content/docs/components/forms/text-field/index.mdx b/apps/docs/content/docs/components/forms/text-field/index.mdx index c30ec6ff..7b07f685 100644 --- a/apps/docs/content/docs/components/forms/text-field/index.mdx +++ b/apps/docs/content/docs/components/forms/text-field/index.mdx @@ -2,6 +2,8 @@ title: Text Field description: Captures a single line of text with a label, validation, and an optional prefix or suffix. +source: packages/@luke-ui/react/src/text-field +reactAria: https://react-spectrum.adobe.com/react-aria/TextField.html --- Use `TextField` to collect a single line of text. It combines an input with a label, description, diff --git a/apps/docs/content/docs/components/forms/text-field/props.mdx b/apps/docs/content/docs/components/forms/text-field/props.mdx index 31387ed6..167b263f 100644 --- a/apps/docs/content/docs/components/forms/text-field/props.mdx +++ b/apps/docs/content/docs/components/forms/text-field/props.mdx @@ -2,6 +2,8 @@ title: Text Field description: Captures a single line of text with a label, validation, and an optional prefix or suffix. +source: packages/@luke-ui/react/src/text-field +reactAria: https://react-spectrum.adobe.com/react-aria/TextField.html --- ## Props diff --git a/apps/docs/content/docs/components/layout/box/index.mdx b/apps/docs/content/docs/components/layout/box/index.mdx index 3f60b946..386eec41 100644 --- a/apps/docs/content/docs/components/layout/box/index.mdx +++ b/apps/docs/content/docs/components/layout/box/index.mdx @@ -1,6 +1,7 @@ --- title: Box description: A div with responsive Luke UI layout properties. +source: packages/@luke-ui/react/src/box --- `Box` is a `div` that accepts Luke UI layout properties. It places content in a layout, adds token diff --git a/apps/docs/content/docs/components/layout/box/props.mdx b/apps/docs/content/docs/components/layout/box/props.mdx index 8f3a716b..5c4d0cfb 100644 --- a/apps/docs/content/docs/components/layout/box/props.mdx +++ b/apps/docs/content/docs/components/layout/box/props.mdx @@ -1,6 +1,7 @@ --- title: Box description: A div with responsive Luke UI layout properties. +source: packages/@luke-ui/react/src/box --- ## Props diff --git a/apps/docs/content/docs/components/primitives/button/index.mdx b/apps/docs/content/docs/components/primitives/button/index.mdx index a5c97e58..7e43ed5b 100644 --- a/apps/docs/content/docs/components/primitives/button/index.mdx +++ b/apps/docs/content/docs/components/primitives/button/index.mdx @@ -1,6 +1,8 @@ --- title: Button primitive description: Lower-level button styles and behaviour for custom composed controls. +source: packages/@luke-ui/react/src/button/primitive +reactAria: https://react-spectrum.adobe.com/react-aria/Button.html --- Use the button primitive to build a custom button pattern. It provides Luke UI button styles and diff --git a/apps/docs/content/docs/components/primitives/button/props.mdx b/apps/docs/content/docs/components/primitives/button/props.mdx index d5244d3a..3925b105 100644 --- a/apps/docs/content/docs/components/primitives/button/props.mdx +++ b/apps/docs/content/docs/components/primitives/button/props.mdx @@ -1,6 +1,8 @@ --- title: Button primitive description: Lower-level button styles and behaviour for custom composed controls. +source: packages/@luke-ui/react/src/button/primitive +reactAria: https://react-spectrum.adobe.com/react-aria/Button.html --- ## Props diff --git a/apps/docs/content/docs/components/primitives/checkbox/index.mdx b/apps/docs/content/docs/components/primitives/checkbox/index.mdx index da1488eb..4219bcc2 100644 --- a/apps/docs/content/docs/components/primitives/checkbox/index.mdx +++ b/apps/docs/content/docs/components/primitives/checkbox/index.mdx @@ -1,6 +1,8 @@ --- title: Checkbox primitive description: Lower-level checkbox anatomy for custom composed form controls. +source: packages/@luke-ui/react/src/checkbox/primitive +reactAria: https://react-spectrum.adobe.com/react-aria/Checkbox.html --- Use the Checkbox primitive when a composed control needs a custom label layout or extra content. For diff --git a/apps/docs/content/docs/components/primitives/checkbox/props.mdx b/apps/docs/content/docs/components/primitives/checkbox/props.mdx index 9678bcc0..00c63f6d 100644 --- a/apps/docs/content/docs/components/primitives/checkbox/props.mdx +++ b/apps/docs/content/docs/components/primitives/checkbox/props.mdx @@ -1,6 +1,8 @@ --- title: Checkbox primitive description: Lower-level checkbox anatomy for custom composed form controls. +source: packages/@luke-ui/react/src/checkbox/primitive +reactAria: https://react-spectrum.adobe.com/react-aria/Checkbox.html --- ## Props diff --git a/apps/docs/content/docs/components/primitives/combobox/index.mdx b/apps/docs/content/docs/components/primitives/combobox/index.mdx index 07f7666a..730a9ba0 100644 --- a/apps/docs/content/docs/components/primitives/combobox/index.mdx +++ b/apps/docs/content/docs/components/primitives/combobox/index.mdx @@ -1,6 +1,8 @@ --- title: Combobox primitives description: Lower-level parts for composing a searchable single-select combobox. +source: packages/@luke-ui/react/src/combobox-field/primitive +reactAria: https://react-spectrum.adobe.com/react-aria/ComboBox.html --- Use these primitives when [`ComboboxField`](/components/forms/combobox-field) does not fit your diff --git a/apps/docs/content/docs/components/primitives/combobox/props.mdx b/apps/docs/content/docs/components/primitives/combobox/props.mdx index 0974eac9..e9620833 100644 --- a/apps/docs/content/docs/components/primitives/combobox/props.mdx +++ b/apps/docs/content/docs/components/primitives/combobox/props.mdx @@ -1,6 +1,8 @@ --- title: Combobox primitives description: Lower-level parts for composing a searchable single-select combobox. +source: packages/@luke-ui/react/src/combobox-field/primitive +reactAria: https://react-spectrum.adobe.com/react-aria/ComboBox.html --- ## Props diff --git a/apps/docs/content/docs/components/primitives/field/index.mdx b/apps/docs/content/docs/components/primitives/field/index.mdx index c5cda7cc..dc1f35c3 100644 --- a/apps/docs/content/docs/components/primitives/field/index.mdx +++ b/apps/docs/content/docs/components/primitives/field/index.mdx @@ -1,6 +1,7 @@ --- title: Field primitive description: Shared label, description, and validation parts for custom fields. +source: packages/@luke-ui/react/src/field/primitive --- Use the field primitives to build a custom field with Luke UI labels, descriptions, and validation diff --git a/apps/docs/content/docs/components/primitives/field/props.mdx b/apps/docs/content/docs/components/primitives/field/props.mdx index bb5b7953..8f667897 100644 --- a/apps/docs/content/docs/components/primitives/field/props.mdx +++ b/apps/docs/content/docs/components/primitives/field/props.mdx @@ -1,6 +1,7 @@ --- title: Field primitive description: Shared label, description, and validation parts for custom fields. +source: packages/@luke-ui/react/src/field/primitive --- ## Props diff --git a/apps/docs/content/docs/components/primitives/input-group/index.mdx b/apps/docs/content/docs/components/primitives/input-group/index.mdx index 34b347fd..6ec50f6f 100644 --- a/apps/docs/content/docs/components/primitives/input-group/index.mdx +++ b/apps/docs/content/docs/components/primitives/input-group/index.mdx @@ -1,6 +1,7 @@ --- title: InputGroup primitives description: Lower-level parts for composing a text input with content at either end. +source: packages/@luke-ui/react/src/text-field/primitive --- Use these primitives to build a custom text control. Reach for them when you need the styled input diff --git a/apps/docs/content/docs/components/primitives/input-group/props.mdx b/apps/docs/content/docs/components/primitives/input-group/props.mdx index 72d9e155..2de144fd 100644 --- a/apps/docs/content/docs/components/primitives/input-group/props.mdx +++ b/apps/docs/content/docs/components/primitives/input-group/props.mdx @@ -1,6 +1,7 @@ --- title: InputGroup primitives description: Lower-level parts for composing a text input with content at either end. +source: packages/@luke-ui/react/src/text-field/primitive --- ## Props diff --git a/apps/docs/content/docs/components/primitives/visually-hidden/index.mdx b/apps/docs/content/docs/components/primitives/visually-hidden/index.mdx index 8cc60d76..5fb4dc1f 100644 --- a/apps/docs/content/docs/components/primitives/visually-hidden/index.mdx +++ b/apps/docs/content/docs/components/primitives/visually-hidden/index.mdx @@ -1,6 +1,7 @@ --- title: Visually Hidden description: Hide content visually and keep it available to assistive technology. +source: packages/@luke-ui/react/src/visually-hidden --- Use `VisuallyHidden` to hide content from sight. The content stays in the accessibility tree and the diff --git a/apps/docs/content/docs/components/primitives/visually-hidden/props.mdx b/apps/docs/content/docs/components/primitives/visually-hidden/props.mdx index 371504f4..a5eb92fc 100644 --- a/apps/docs/content/docs/components/primitives/visually-hidden/props.mdx +++ b/apps/docs/content/docs/components/primitives/visually-hidden/props.mdx @@ -1,6 +1,7 @@ --- title: Visually Hidden description: Hide content visually and keep it available to assistive technology. +source: packages/@luke-ui/react/src/visually-hidden --- ## Props diff --git a/apps/docs/content/docs/components/typography/blockquote/index.mdx b/apps/docs/content/docs/components/typography/blockquote/index.mdx index afd2645a..636a2fa1 100644 --- a/apps/docs/content/docs/components/typography/blockquote/index.mdx +++ b/apps/docs/content/docs/components/typography/blockquote/index.mdx @@ -1,6 +1,7 @@ --- title: Blockquote description: Block-level quotation from another source. +source: packages/@luke-ui/react/src/blockquote --- `Blockquote` quotes another source as a block. It renders a native `blockquote` element, which diff --git a/apps/docs/content/docs/components/typography/blockquote/props.mdx b/apps/docs/content/docs/components/typography/blockquote/props.mdx index f90dfb1f..56992e8e 100644 --- a/apps/docs/content/docs/components/typography/blockquote/props.mdx +++ b/apps/docs/content/docs/components/typography/blockquote/props.mdx @@ -1,6 +1,7 @@ --- title: Blockquote description: Block-level quotation from another source. +source: packages/@luke-ui/react/src/blockquote --- ## Props diff --git a/apps/docs/content/docs/components/typography/code/index.mdx b/apps/docs/content/docs/components/typography/code/index.mdx index 6f69f503..b133d6ef 100644 --- a/apps/docs/content/docs/components/typography/code/index.mdx +++ b/apps/docs/content/docs/components/typography/code/index.mdx @@ -1,6 +1,7 @@ --- title: Code description: Marks text to signify a short fragment of computer code. +source: packages/@luke-ui/react/src/code --- `Code` marks a short fragment of computer code. It renders a native `code` element, which conveys diff --git a/apps/docs/content/docs/components/typography/code/props.mdx b/apps/docs/content/docs/components/typography/code/props.mdx index 66927a4a..54c206ef 100644 --- a/apps/docs/content/docs/components/typography/code/props.mdx +++ b/apps/docs/content/docs/components/typography/code/props.mdx @@ -1,6 +1,7 @@ --- title: Code description: Marks text to signify a short fragment of computer code. +source: packages/@luke-ui/react/src/code --- ## Props diff --git a/apps/docs/content/docs/components/typography/em/index.mdx b/apps/docs/content/docs/components/typography/em/index.mdx index af63c9e6..c477dc9e 100644 --- a/apps/docs/content/docs/components/typography/em/index.mdx +++ b/apps/docs/content/docs/components/typography/em/index.mdx @@ -1,6 +1,7 @@ --- title: Em description: Marks text to stress emphasis. +source: packages/@luke-ui/react/src/em --- `Em` stresses a word or phrase, changing how the sentence reads. It renders a native `em` element, diff --git a/apps/docs/content/docs/components/typography/em/props.mdx b/apps/docs/content/docs/components/typography/em/props.mdx index 2c7bee43..6418b389 100644 --- a/apps/docs/content/docs/components/typography/em/props.mdx +++ b/apps/docs/content/docs/components/typography/em/props.mdx @@ -1,6 +1,7 @@ --- title: Em description: Marks text to stress emphasis. +source: packages/@luke-ui/react/src/em --- ## Props diff --git a/apps/docs/content/docs/components/typography/emoji/index.mdx b/apps/docs/content/docs/components/typography/emoji/index.mdx index 4253371e..a7547f81 100644 --- a/apps/docs/content/docs/components/typography/emoji/index.mdx +++ b/apps/docs/content/docs/components/typography/emoji/index.mdx @@ -1,6 +1,7 @@ --- title: Emoji description: Emoji rendering with a reliable screen reader label. +source: packages/@luke-ui/react/src/emoji --- `Emoji` renders an emoji with a reliable accessible name. It accepts the same typography props as diff --git a/apps/docs/content/docs/components/typography/emoji/props.mdx b/apps/docs/content/docs/components/typography/emoji/props.mdx index 624fe35a..dc5ddd71 100644 --- a/apps/docs/content/docs/components/typography/emoji/props.mdx +++ b/apps/docs/content/docs/components/typography/emoji/props.mdx @@ -1,6 +1,7 @@ --- title: Emoji description: Emoji rendering with a reliable screen reader label. +source: packages/@luke-ui/react/src/emoji --- ## Props diff --git a/apps/docs/content/docs/components/typography/heading/index.mdx b/apps/docs/content/docs/components/typography/heading/index.mdx index d79c0c03..bef09142 100644 --- a/apps/docs/content/docs/components/typography/heading/index.mdx +++ b/apps/docs/content/docs/components/typography/heading/index.mdx @@ -1,6 +1,7 @@ --- title: Heading description: Semantic heading with automatic level management. +source: packages/@luke-ui/react/src/heading --- `Heading` renders a semantic section heading. It reads its level from `HeadingLevels` context. Set diff --git a/apps/docs/content/docs/components/typography/heading/props.mdx b/apps/docs/content/docs/components/typography/heading/props.mdx index d98d540f..37ecffc1 100644 --- a/apps/docs/content/docs/components/typography/heading/props.mdx +++ b/apps/docs/content/docs/components/typography/heading/props.mdx @@ -1,6 +1,7 @@ --- title: Heading description: Semantic heading with automatic level management. +source: packages/@luke-ui/react/src/heading --- ## Props diff --git a/apps/docs/content/docs/components/typography/kbd/index.mdx b/apps/docs/content/docs/components/typography/kbd/index.mdx index 18bbd8b0..500bbdc1 100644 --- a/apps/docs/content/docs/components/typography/kbd/index.mdx +++ b/apps/docs/content/docs/components/typography/kbd/index.mdx @@ -1,6 +1,7 @@ --- title: Kbd description: Represents keyboard input or a hotkey. +source: packages/@luke-ui/react/src/kbd --- `Kbd` marks keyboard input, such as a shortcut or a key name. It renders a native `kbd` element, diff --git a/apps/docs/content/docs/components/typography/kbd/props.mdx b/apps/docs/content/docs/components/typography/kbd/props.mdx index 602001dd..07847d79 100644 --- a/apps/docs/content/docs/components/typography/kbd/props.mdx +++ b/apps/docs/content/docs/components/typography/kbd/props.mdx @@ -1,6 +1,7 @@ --- title: Kbd description: Represents keyboard input or a hotkey. +source: packages/@luke-ui/react/src/kbd --- ## Props diff --git a/apps/docs/content/docs/components/typography/numeral/index.mdx b/apps/docs/content/docs/components/typography/numeral/index.mdx index 00e8eae0..2610abdb 100644 --- a/apps/docs/content/docs/components/typography/numeral/index.mdx +++ b/apps/docs/content/docs/components/typography/numeral/index.mdx @@ -1,6 +1,7 @@ --- title: Numeral description: Locale-aware number formatting powered by Intl.NumberFormat. +source: packages/@luke-ui/react/src/numeral --- `Numeral` formats a number with `Intl.NumberFormat`. It reads the active locale from React Aria's diff --git a/apps/docs/content/docs/components/typography/numeral/props.mdx b/apps/docs/content/docs/components/typography/numeral/props.mdx index 406f4920..129dba98 100644 --- a/apps/docs/content/docs/components/typography/numeral/props.mdx +++ b/apps/docs/content/docs/components/typography/numeral/props.mdx @@ -1,6 +1,7 @@ --- title: Numeral description: Locale-aware number formatting powered by Intl.NumberFormat. +source: packages/@luke-ui/react/src/numeral --- ## Props diff --git a/apps/docs/content/docs/components/typography/quote/index.mdx b/apps/docs/content/docs/components/typography/quote/index.mdx index 70e15094..a90ea217 100644 --- a/apps/docs/content/docs/components/typography/quote/index.mdx +++ b/apps/docs/content/docs/components/typography/quote/index.mdx @@ -1,6 +1,7 @@ --- title: Quote description: Short inline quotation. +source: packages/@luke-ui/react/src/quote --- `Quote` marks a short quotation inside a sentence. It renders a native `q` element, which conveys diff --git a/apps/docs/content/docs/components/typography/quote/props.mdx b/apps/docs/content/docs/components/typography/quote/props.mdx index eb16dd24..64efe306 100644 --- a/apps/docs/content/docs/components/typography/quote/props.mdx +++ b/apps/docs/content/docs/components/typography/quote/props.mdx @@ -1,6 +1,7 @@ --- title: Quote description: Short inline quotation. +source: packages/@luke-ui/react/src/quote --- ## Props diff --git a/apps/docs/content/docs/components/typography/strong/index.mdx b/apps/docs/content/docs/components/typography/strong/index.mdx index 5373ae9f..43f8ebb2 100644 --- a/apps/docs/content/docs/components/typography/strong/index.mdx +++ b/apps/docs/content/docs/components/typography/strong/index.mdx @@ -1,6 +1,7 @@ --- title: Strong description: Marks text to signify strong importance. +source: packages/@luke-ui/react/src/strong --- `Strong` marks text as important. It renders a native `strong` element, which conveys importance to diff --git a/apps/docs/content/docs/components/typography/strong/props.mdx b/apps/docs/content/docs/components/typography/strong/props.mdx index 7851a266..f10e1968 100644 --- a/apps/docs/content/docs/components/typography/strong/props.mdx +++ b/apps/docs/content/docs/components/typography/strong/props.mdx @@ -1,6 +1,7 @@ --- title: Strong description: Marks text to signify strong importance. +source: packages/@luke-ui/react/src/strong --- ## Props diff --git a/apps/docs/content/docs/components/typography/text/index.mdx b/apps/docs/content/docs/components/typography/text/index.mdx index 24b7b53c..e749ae29 100644 --- a/apps/docs/content/docs/components/typography/text/index.mdx +++ b/apps/docs/content/docs/components/typography/text/index.mdx @@ -1,6 +1,7 @@ --- title: Text description: Styled text with a coordinated type scale and semantic colour roles. +source: packages/@luke-ui/react/src/text --- `Text` applies Luke UI typography without adding heading semantics. It needs a Luke UI theme root. diff --git a/apps/docs/content/docs/components/typography/text/props.mdx b/apps/docs/content/docs/components/typography/text/props.mdx index 35cff8b2..2677f760 100644 --- a/apps/docs/content/docs/components/typography/text/props.mdx +++ b/apps/docs/content/docs/components/typography/text/props.mdx @@ -1,6 +1,7 @@ --- title: Text description: Styled text with a coordinated type scale and semantic colour roles. +source: packages/@luke-ui/react/src/text --- ## Props diff --git a/apps/docs/content/docs/components/visuals/icon/index.mdx b/apps/docs/content/docs/components/visuals/icon/index.mdx index de483157..dd519583 100644 --- a/apps/docs/content/docs/components/visuals/icon/index.mdx +++ b/apps/docs/content/docs/components/visuals/icon/index.mdx @@ -1,6 +1,7 @@ --- title: Icon description: SVG icon component backed by the generated spritesheet. +source: packages/@luke-ui/react/src/icon --- `Icon` renders a symbol from the generated Luke UI spritesheet. It needs an diff --git a/apps/docs/content/docs/components/visuals/icon/props.mdx b/apps/docs/content/docs/components/visuals/icon/props.mdx index 93795136..9b263bbd 100644 --- a/apps/docs/content/docs/components/visuals/icon/props.mdx +++ b/apps/docs/content/docs/components/visuals/icon/props.mdx @@ -1,6 +1,7 @@ --- title: Icon description: SVG icon component backed by the generated spritesheet. +source: packages/@luke-ui/react/src/icon --- ## Props diff --git a/apps/docs/package.json b/apps/docs/package.json index fc483110..a791a17b 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -1,6 +1,10 @@ { "name": "docs", "private": true, + "repository": { + "type": "git", + "url": "git+https://github.com/lukebennett88/luke-ui.git" + }, "type": "module", "sideEffects": false, "scripts": { diff --git a/apps/docs/public/apple-touch-icon.png b/apps/docs/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..eb192220a65c22bf90ede35548fff3a38523350c GIT binary patch literal 7320 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 + + + diff --git a/apps/docs/public/favicon.svg b/apps/docs/public/favicon.svg new file mode 100644 index 00000000..c87b580e --- /dev/null +++ b/apps/docs/public/favicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/docs/source.config.ts b/apps/docs/source.config.ts index cad978e5..91409364 100644 --- a/apps/docs/source.config.ts +++ b/apps/docs/source.config.ts @@ -1,10 +1,12 @@ import { fileURLToPath } from 'node:url'; +import { pageSchema } from 'fumadocs-core/source/schema'; import { defineConfig, defineDocs } from 'fumadocs-mdx/config'; import { createFileSystemGeneratorCache, createGenerator, remarkAutoTypeTable, } from 'fumadocs-typescript'; +import * as z from 'zod'; import { remarkValidateExamples } from './src/lib/remark-validate-examples'; export const docs = defineDocs({ @@ -13,6 +15,12 @@ export const docs = defineDocs({ postprocess: { includeProcessedMarkdown: true, }, + schema: pageSchema.extend({ + /** Full URL to this component's React Aria Components docs page, when it genuinely wraps one. */ + reactAria: z.string().optional(), + /** Repo-relative path to this component's source, e.g. `packages/@luke-ui/react/src/button`. */ + source: z.string().optional(), + }), }, }); diff --git a/apps/docs/src/components/docs-site-nav.tsx b/apps/docs/src/components/docs-site-nav.tsx index 203604d5..9a68e3cd 100644 --- a/apps/docs/src/components/docs-site-nav.tsx +++ b/apps/docs/src/components/docs-site-nav.tsx @@ -1,10 +1,6 @@ import { Icon } from '@luke-ui/react/icon'; import { cx } from '@luke-ui/react/utils'; -import { - SidebarCollapseTrigger, - SidebarTrigger, - useSidebar, -} from 'fumadocs-ui/layouts/notebook/slots/sidebar'; +import { SidebarTrigger } from 'fumadocs-ui/layouts/notebook/slots/sidebar'; import { SITE_NAV_BUTTON_CLASS_NAME, SiteNav } from './site-nav.js'; const TRIGGER_CLASS_NAME = cx(SITE_NAV_BUTTON_CLASS_NAME, 'w-8 px-0'); @@ -16,16 +12,6 @@ export function DocsSiteNav() { className="sticky top-(--fd-docs-row-1) z-10 [grid-area:header] layout:[--fd-header-height:--spacing(14)]" hasSidebarNavigation > - - - ); -} - -function DocsSidebarTriggers() { - const { collapsed } = useSidebar(); - - return ( - <> - - - - + ); } diff --git a/apps/docs/src/components/github-mark.tsx b/apps/docs/src/components/github-mark.tsx new file mode 100644 index 00000000..0085fb75 --- /dev/null +++ b/apps/docs/src/components/github-mark.tsx @@ -0,0 +1,19 @@ +import type { ComponentProps, JSX } from 'react'; + +/** + * GitHub's mark. Kept local to the docs app rather than added to + * `@luke-ui/react`'s icon spritesheet, which ships to every consumer of the + * library and holds generic UI glyphs rather than third-party trademarks. + */ +export function GithubMark(props: ComponentProps<'svg'>): JSX.Element { + return ( + + ); +} diff --git a/apps/docs/src/components/page-actions.browser.test.tsx b/apps/docs/src/components/page-actions.browser.test.tsx index c52dae98..1943650f 100644 --- a/apps/docs/src/components/page-actions.browser.test.tsx +++ b/apps/docs/src/components/page-actions.browser.test.tsx @@ -21,59 +21,76 @@ afterEach(() => { const githubUrl = 'https://github.com/example/repo/edit/main/page.mdx'; const markdownUrl = '/example-page.md'; +const reactAriaUrl = 'https://react-spectrum.adobe.com/react-aria/Button.html'; +const sourceUrl = 'https://github.com/example/repo/tree/main/packages/example/src/button'; const storybookUrl = 'https://storybook.example/?path=/docs/example--docs'; -test('collapses into a single trigger until opened', async () => { - renderActions({ githubUrl, markdownUrl, storybookUrl }); - - await expect.element(page.getByRole('button', { name: 'View options' })).toBeVisible(); - expect(page.getByRole('link', { name: 'Edit on GitHub' })).not.toBeInTheDocument(); - expect(page.getByRole('link', { name: 'View as Markdown' })).not.toBeInTheDocument(); - expect(page.getByRole('button', { name: 'Copy Markdown' })).not.toBeInTheDocument(); -}); - -test('opening the control surfaces every destination, labelled and reachable', async () => { - renderActions({ githubUrl, markdownUrl, storybookUrl }); - - await userEvent.click(page.getByRole('button', { name: 'View options' })); +test('renders every destination inline, labelled and reachable, when all are present', async () => { + renderActions({ githubUrl, markdownUrl, reactAriaUrl, sourceUrl, storybookUrl }); + await expect + .element(page.getByRole('link', { name: 'Storybook' })) + .toHaveAttribute('href', storybookUrl); + await expect + .element(page.getByRole('link', { name: 'React Aria' })) + .toHaveAttribute('href', reactAriaUrl); + await expect + .element(page.getByRole('link', { name: 'Source' })) + .toHaveAttribute('href', sourceUrl); await expect.element(page.getByRole('button', { name: 'Copy Markdown' })).toBeVisible(); await expect .element(page.getByRole('link', { name: 'View as Markdown' })) .toHaveAttribute('href', markdownUrl); - await expect - .element(page.getByRole('link', { name: 'View in Storybook' })) - .toHaveAttribute('href', storybookUrl); await expect .element(page.getByRole('link', { name: 'Edit on GitHub' })) .toHaveAttribute('href', githubUrl); }); -test('omits the Storybook destination when the page has none', async () => { - renderActions({ githubUrl, markdownUrl, storybookUrl: null }); - - await userEvent.click(page.getByRole('button', { name: 'View options' })); +test('omits the Storybook, React Aria, and Source pills when the page has none', async () => { + renderActions({ + githubUrl, + markdownUrl, + reactAriaUrl: null, + sourceUrl: null, + storybookUrl: null, + }); + expect(page.getByRole('link', { name: 'Storybook' })).not.toBeInTheDocument(); + expect(page.getByRole('link', { name: 'React Aria' })).not.toBeInTheDocument(); + expect(page.getByRole('link', { name: 'Source' })).not.toBeInTheDocument(); await expect.element(page.getByRole('button', { name: 'Copy Markdown' })).toBeVisible(); - expect(page.getByRole('link', { name: 'View in Storybook' })).not.toBeInTheDocument(); + await expect.element(page.getByRole('link', { name: 'View as Markdown' })).toBeVisible(); + await expect.element(page.getByRole('link', { name: 'Edit on GitHub' })).toBeVisible(); }); -test('is keyboard operable: Enter opens it, Tab moves through items, Escape returns focus to the trigger', async () => { - renderActions({ githubUrl, markdownUrl, storybookUrl: null }); +test('the Source and Edit on GitHub pills stay distinguishable, even though both use the GitHub mark', async () => { + renderActions({ githubUrl, markdownUrl, reactAriaUrl: null, sourceUrl, storybookUrl: null }); - const trigger = page.getByRole('button', { name: 'View options' }); - await userEvent.tab(); - await expect.element(trigger).toHaveFocus(); + const sourceLink = page.getByRole('link', { name: 'Source' }); + const editLink = page.getByRole('link', { name: 'Edit on GitHub' }); + await expect.element(sourceLink).toHaveAttribute('href', sourceUrl); + await expect.element(editLink).toHaveAttribute('href', githubUrl); +}); - await userEvent.keyboard('{Enter}'); - await expect.element(page.getByRole('button', { name: 'Copy Markdown' })).toHaveFocus(); +test('reports the copied state after Copy Markdown succeeds', async () => { + vi.stubGlobal( + 'fetch', + vi.fn(async () => new Response('# Example', { status: 200 })), + ); + const writeText = vi.spyOn(navigator.clipboard, 'writeText').mockResolvedValue(undefined); + + renderActions({ + githubUrl, + markdownUrl, + reactAriaUrl: null, + sourceUrl: null, + storybookUrl: null, + }); - await userEvent.tab(); - await expect.element(page.getByRole('link', { name: 'View as Markdown' })).toHaveFocus(); + await userEvent.click(page.getByRole('button', { name: 'Copy Markdown' })); - await userEvent.keyboard('{Escape}'); - await expect.element(trigger).toHaveFocus(); - expect(page.getByRole('link', { name: 'View as Markdown' })).not.toBeInTheDocument(); + await expect.element(page.getByRole('button', { name: 'Copied' })).toBeVisible(); + expect(writeText).toHaveBeenCalledWith('# Example'); }); test('does not claim success when the markdown fetch 404s', async () => { @@ -95,8 +112,13 @@ test('does not claim success when the markdown fetch 404s', async () => { window.addEventListener('unhandledrejection', onUnhandledRejection); try { - renderActions({ githubUrl, markdownUrl, storybookUrl: null }); - await userEvent.click(page.getByRole('button', { name: 'View options' })); + renderActions({ + githubUrl, + markdownUrl, + reactAriaUrl: null, + sourceUrl: null, + storybookUrl: null, + }); await userEvent.click(page.getByRole('button', { name: 'Copy Markdown' })); // Give the rejected promise a tick to settle, then confirm the label @@ -109,9 +131,22 @@ test('does not claim success when the markdown fetch 404s', async () => { } }); +test('does not announce the brand marks as separate content', async () => { + renderActions({ githubUrl, markdownUrl, reactAriaUrl, sourceUrl, storybookUrl }); + + // Each pill's accessible name comes from its visible label alone — the + // brand mark inside it is `aria-hidden`, not a second named element. + await expect.element(page.getByRole('link', { name: 'Storybook' })).toBeVisible(); + expect(page.getByRole('img', { name: /storybook/i })).not.toBeInTheDocument(); + expect(page.getByRole('img', { name: /react aria/i })).not.toBeInTheDocument(); + expect(page.getByRole('img', { name: /github/i })).not.toBeInTheDocument(); +}); + function renderActions(props: { githubUrl: string; markdownUrl: string; + reactAriaUrl: string | null; + sourceUrl: string | null; storybookUrl: string | null; }) { container = document.body.appendChild(document.createElement('div')); diff --git a/apps/docs/src/components/page-actions.tsx b/apps/docs/src/components/page-actions.tsx index dbe44999..4b592194 100644 --- a/apps/docs/src/components/page-actions.tsx +++ b/apps/docs/src/components/page-actions.tsx @@ -2,89 +2,101 @@ import type { IconName } from '@luke-ui/react/icon'; import { Icon } from '@luke-ui/react/icon'; import { cx } from '@luke-ui/react/utils'; import { buttonVariants } from 'fumadocs-ui/components/ui/button'; -import { Popover, PopoverContent, PopoverTrigger } from 'fumadocs-ui/components/ui/popover'; import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button'; +import type { ReactNode } from 'react'; import { Link } from 'react-aria-components/Link'; +import { GithubMark } from './github-mark.js'; +import { ReactAriaMark } from './react-aria-mark.js'; +import { StorybookMark } from './storybook-mark.js'; interface PageActionsProps { githubUrl: string; markdownUrl: string; + reactAriaUrl: string | null; + sourceUrl: string | null; storybookUrl: string | null; } -const rowClassName = cx( - 'flex w-full items-center gap-2 rounded-lg p-2 text-start text-sm text-fd-popover-foreground', - 'hover:bg-fd-accent hover:text-fd-accent-foreground', - 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring', -); +const pillClassName = cx(buttonVariants({ color: 'secondary', size: 'sm' }), 'gap-1.5'); /** - * Copy Markdown, View as Markdown, and Edit on GitHub are utilities most - * visits never touch, so they collapse into a single compact "view options" - * control instead of sitting on the page as a row of filled pill buttons - * above the first paragraph. This reuses Fumadocs' own popover primitives - * (`fumadocs-ui/components/ui/popover`) — the same "house pattern" behind - * its `ViewOptionsPopover` — rather than adopting that export directly, - * because `ViewOptionsPopover` bakes in third-party AI-assistant deep links - * (ChatGPT, Claude, Cursor, Scira) this repo does not want to surface, and - * has no notion of a Storybook destination. + * Every destination a page offers sits inline under the title, as a row of + * pill links with a brand mark and a visible label. The row wraps, so a page + * carrying all six actions falls onto a second line at narrower widths. + * + * This does not use Fumadocs' own `ViewOptionsPopover`, because it bakes in + * third-party AI-assistant deep links this repo does not want to surface, and + * it has no notion of a Storybook, React Aria, or component-source + * destination. */ -export function PageActions({ markdownUrl, githubUrl, storybookUrl }: PageActionsProps) { +export function PageActions({ + githubUrl, + markdownUrl, + reactAriaUrl, + sourceUrl, + storybookUrl, +}: PageActionsProps) { return ( - - - View options - - - - - - {storybookUrl ? ( - - ) : null} - - - +
+ {storybookUrl ? ( + } + label="Storybook" + /> + ) : null} + {reactAriaUrl ? ( + } + label="React Aria" + /> + ) : null} + {sourceUrl ? ( + } + label="Source" + /> + ) : null} + + + } + label="Edit on GitHub" + /> +
); } function PageActionLink({ href, + icon, iconName, label, }: { href: string; - iconName: IconName; + icon?: ReactNode; + iconName?: IconName; label: string; }) { return ( - - + + {icon ?? (iconName ? : null)} {label} - ); } -function CopyMarkdownRow({ markdownUrl }: { markdownUrl: string }) { +function CopyMarkdownButton({ markdownUrl }: { markdownUrl: string }) { // `useCopyButton` only flips to its "Copied" state once this callback's - // promise resolves — it has no `onRejected` handler — so a rejection - // leaves `copied` at `false` instead of lying about success. `fetch` - // itself does not reject on an HTTP error response (a 404 resolves - // normally with `res.ok === false`), so a non-ok response is thrown here - // to convert it into a rejection. Without that throw, a stale route or a - // missing generated `.md` file would copy an error page's HTML to the - // clipboard while the button still claimed success. + // promise resolves, and it has no `onRejected` handler, so a rejection + // leaves `copied` at `false` instead of claiming success. `fetch` does not + // reject on an HTTP error (a 404 resolves with `res.ok === false`), so a + // non-ok response is thrown here to turn it into one. Without the throw, a + // stale route or a missing generated `.md` file would copy an error page's + // HTML to the clipboard while the button reported success. const [copied, onCopy] = useCopyButton(async () => { const res = await fetch(markdownUrl); if (!res.ok) throw new Error(`Failed to fetch ${markdownUrl}: ${res.status}`); @@ -93,7 +105,7 @@ function CopyMarkdownRow({ markdownUrl }: { markdownUrl: string }) { }); return ( - diff --git a/apps/docs/src/components/react-aria-mark.tsx b/apps/docs/src/components/react-aria-mark.tsx new file mode 100644 index 00000000..f68f40e8 --- /dev/null +++ b/apps/docs/src/components/react-aria-mark.tsx @@ -0,0 +1,22 @@ +import type { ComponentProps, JSX } from 'react'; + +/** + * The React Aria Components mark. Kept local to the docs app rather than + * added to `@luke-ui/react`'s icon spritesheet, which ships to every consumer + * of the library and holds generic UI glyphs rather than third-party + * trademarks. + */ +export function ReactAriaMark({ style, ...props }: ComponentProps<'svg'>): JSX.Element { + return ( + + ); +} diff --git a/apps/docs/src/components/site-nav.browser.test.tsx b/apps/docs/src/components/site-nav.browser.test.tsx index fd2e61f7..ba555d91 100644 --- a/apps/docs/src/components/site-nav.browser.test.tsx +++ b/apps/docs/src/components/site-nav.browser.test.tsx @@ -79,6 +79,17 @@ test('leaves every destination inactive on the 404 page', async () => { expect(getCurrentLinks()).toHaveLength(0); }); +test('links to the repository with an accessible name', async () => { + await page.viewport(1024, 800); + await renderAt('/', ); + + const repositoryLink = page.getByRole('link', { name: 'GitHub repository' }); + await expect.element(repositoryLink).toBeVisible(); + await expect + .element(repositoryLink) + .toHaveAttribute('href', 'https://github.com/lukebennett88/luke-ui'); +}); + async function renderAt(pathname: string, children: ReactNode) { const rootRoute = createRootRoute({ component: () => ( diff --git a/apps/docs/src/components/site-nav.tsx b/apps/docs/src/components/site-nav.tsx index dfb38583..c97c37ea 100644 --- a/apps/docs/src/components/site-nav.tsx +++ b/apps/docs/src/components/site-nav.tsx @@ -1,11 +1,15 @@ import { cx } from '@luke-ui/react/utils'; +import { VisuallyHidden } from '@luke-ui/react/visually-hidden'; import { useLinkProps } from '@tanstack/react-router'; import { usePathname } from 'fumadocs-core/framework'; import Link from 'fumadocs-core/link'; import { Popover, PopoverContent, PopoverTrigger } from 'fumadocs-ui/components/ui/popover'; import { FullSearchTrigger, SearchTrigger } from 'fumadocs-ui/layouts/shared/slots/search-trigger'; import type { ComponentProps } from 'react'; +import { GITHUB_REPO_URL } from '../lib/github.js'; import { getActiveSiteDestination, siteDestinations } from '../lib/site-destinations.js'; +import { GithubMark } from './github-mark.js'; +import { StorybookMark } from './storybook-mark.js'; import { ThemeControls } from './theme-controls.js'; export const SITE_NAV_BUTTON_CLASS_NAME = @@ -46,12 +50,13 @@ export function SiteNav({ > {siteDestinations.map((destination) => { const isActive = destination === activeDestination; + const isStorybook = destination.label === 'Storybook'; return ( + {isStorybook ? : null} {destination.label} ); @@ -72,6 +78,7 @@ export function SiteNav({ + {children} @@ -110,3 +117,17 @@ function AppearancePopover() { ); } + +function RepositoryLink() { + return ( + + + GitHub repository + + ); +} diff --git a/apps/docs/src/components/storybook-mark.tsx b/apps/docs/src/components/storybook-mark.tsx new file mode 100644 index 00000000..4ab59368 --- /dev/null +++ b/apps/docs/src/components/storybook-mark.tsx @@ -0,0 +1,27 @@ +import type { ComponentProps, JSX } from 'react'; + +/** + * Storybook's mark. Kept local to the docs app rather than added to + * `@luke-ui/react`'s icon spritesheet, which ships to every consumer of the + * library and holds generic UI glyphs rather than third-party trademarks. + */ +export function StorybookMark({ style, ...props }: ComponentProps<'svg'>): JSX.Element { + return ( + + ); +} diff --git a/apps/docs/src/lib/base-path.ts b/apps/docs/src/lib/base-path.ts new file mode 100644 index 00000000..be1e87d6 --- /dev/null +++ b/apps/docs/src/lib/base-path.ts @@ -0,0 +1,12 @@ +const LEADING_SLASH_PATTERN = /^\//; + +/** + * Prefixes a root-relative public path with the app's configured base path + * (`import.meta.env.BASE_URL`), so links keep working when the site deploys + * under a sub-path such as `/luke-ui/`. + */ +export function withBasePath(publicPath: string, basePath: string): string { + if (basePath === '/' || basePath === '') return publicPath; + const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`; + return `${normalizedBase}${publicPath.replace(LEADING_SLASH_PATTERN, '')}`; +} diff --git a/apps/docs/src/lib/github.ts b/apps/docs/src/lib/github.ts new file mode 100644 index 00000000..d10891e4 --- /dev/null +++ b/apps/docs/src/lib/github.ts @@ -0,0 +1,7 @@ +import { repository } from '../../package.json'; + +/** npm records a repository as `git+.git`, which is not browsable. */ +const GIT_URL_DECORATION_PATTERN = /^git\+|\.git$/g; + +/** Root URL of the repository this site documents, taken from the app manifest. */ +export const GITHUB_REPO_URL = repository.url.replace(GIT_URL_DECORATION_PATTERN, ''); diff --git a/apps/docs/src/lib/storybook.ts b/apps/docs/src/lib/storybook.ts index 0fa26992..a0e6e3c8 100644 --- a/apps/docs/src/lib/storybook.ts +++ b/apps/docs/src/lib/storybook.ts @@ -1,13 +1,9 @@ +import { withBasePath } from './base-path.js'; + const STORYBOOK_DEV_URL = 'http://localhost:6006'; const COMPONENT_DOC_PATH = /^components\/([^/]+)\/([^/]+)\/index\.mdx$/; -export function withBasePath(publicPath: string, basePath: string): string { - if (basePath === '/' || basePath === '') return publicPath; - const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`; - return `${normalizedBase}${publicPath.replace(/^\//, '')}`; -} - export function getStorybookBaseUrl(basePath: string): string { if (import.meta.env.DEV) return STORYBOOK_DEV_URL; return withBasePath('/storybook', basePath).replace(/\/$/, ''); diff --git a/apps/docs/src/routes/$.tsx b/apps/docs/src/routes/$.tsx index a8ec2b6f..02fc2e66 100644 --- a/apps/docs/src/routes/$.tsx +++ b/apps/docs/src/routes/$.tsx @@ -14,12 +14,15 @@ import { ExampleBlock } from '../components/example-block'; import { IconGallery } from '../components/icon-gallery'; import { PageActions } from '../components/page-actions'; import { SourceCodeBlock } from '../components/source-code-block'; +import { withBasePath } from '../lib/base-path.js'; import { getComponentPageNavigation } from '../lib/component-page-navigation.js'; +import { GITHUB_REPO_URL } from '../lib/github.js'; import { baseOptions } from '../lib/layout.shared'; import { source } from '../lib/source'; -import { getStorybookStoryUrl, withBasePath } from '../lib/storybook'; +import { getStorybookStoryUrl } from '../lib/storybook'; -const GITHUB_DOCS_URL = 'https://github.com/lukebennett88/luke-ui/blob/main/apps/docs/content/docs'; +const GITHUB_DOCS_URL = `${GITHUB_REPO_URL}/blob/main/apps/docs/content/docs`; +const GITHUB_TREE_URL = `${GITHUB_REPO_URL}/tree/main`; const mdxComponents = { ...defaultMdxComponents, @@ -72,6 +75,8 @@ const loader = createServerFn({ markdownUrl: withBasePath(markdownPath, import.meta.env.BASE_URL), pageTree: await source.serializePageTree(source.getPageTree()), path: page.path, + reactAriaUrl: page.data.reactAria ?? null, + sourceUrl: page.data.source ? `${GITHUB_TREE_URL}/${page.data.source}` : null, storybookUrl: getStorybookStoryUrl(page.path, import.meta.env.BASE_URL), }; }); @@ -84,10 +89,20 @@ const clientLoader = browserCollections.docs.createClientLoader({ componentNavigation: ReturnType; githubUrl: string; markdownUrl: string; + reactAriaUrl: string | null; + sourceUrl: string | null; storybookUrl: string | null; }, ) { - const { componentNavigation, githubUrl, markdownUrl, storybookUrl, ...pageProps } = props; + const { + componentNavigation, + githubUrl, + markdownUrl, + reactAriaUrl, + sourceUrl, + storybookUrl, + ...pageProps + } = props; return ( {frontmatter.title} -
- {frontmatter.description} + {frontmatter.description} +
@@ -149,6 +166,8 @@ function Page() { componentNavigation: data.componentNavigation, githubUrl: data.githubUrl, markdownUrl: data.markdownUrl, + reactAriaUrl: data.reactAriaUrl, + sourceUrl: data.sourceUrl, storybookUrl: data.storybookUrl, })} diff --git a/apps/docs/src/routes/__root.tsx b/apps/docs/src/routes/__root.tsx index ee15c199..6edae1ba 100644 --- a/apps/docs/src/routes/__root.tsx +++ b/apps/docs/src/routes/__root.tsx @@ -8,6 +8,7 @@ import { RootProvider } from 'fumadocs-ui/provider/tanstack'; import type { ReactNode } from 'react'; import { lazy, Suspense } from 'react'; import { DocsThemeRoot } from '../components/theme-controls'; +import { withBasePath } from '../lib/base-path.js'; import appCss from '../styles/app.css?url'; const SearchDialog = lazy(() => import('../components/search')); @@ -19,6 +20,20 @@ export const Route = createRootRoute({ { href: appCss, rel: 'stylesheet' }, { href: paperCss, rel: 'stylesheet' }, { href: tactileCss, rel: 'stylesheet' }, + { + href: withBasePath('/favicon.svg', import.meta.env.BASE_URL), + rel: 'icon', + type: 'image/svg+xml', + }, + { + href: withBasePath('/favicon-dark.svg', import.meta.env.BASE_URL), + media: '(prefers-color-scheme: dark)', + rel: 'icon', + }, + { + href: withBasePath('/apple-touch-icon.png', import.meta.env.BASE_URL), + rel: 'apple-touch-icon', + }, ], meta: [ { diff --git a/apps/docs/src/routes/playground/index.tsx b/apps/docs/src/routes/playground/index.tsx index 0a3a28eb..6b3cc252 100644 --- a/apps/docs/src/routes/playground/index.tsx +++ b/apps/docs/src/routes/playground/index.tsx @@ -14,6 +14,7 @@ import { useIsDesktop } from '../../components/playground/use-is-desktop'; import type { ViewportWidth } from '../../components/playground/viewport-toggle'; import { SiteNav } from '../../components/site-nav.js'; import { useDocsThemeIdentity } from '../../components/theme-controls'; +import { withBasePath } from '../../lib/base-path.js'; import rawDefaultCode from '../../lib/playground-default-code.tsx?raw'; import { decodeCodeHash, encodeCodeHash } from '../../lib/playground-hash'; import type { @@ -21,7 +22,6 @@ import type { PlaygroundCodeMessage, } from '../../lib/playground-protocol'; import { isPlaygroundPreviewMessage } from '../../lib/playground-protocol'; -import { withBasePath } from '../../lib/storybook'; const PlaygroundEditor = lazy(() => import('../../components/playground/editor'));