diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c3ca43..c762b3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,10 +2,11 @@ name: CI on: push: - branches: - - main + branches: ["main"] + pull_request: - types: [opened, synchronize] + types: ["opened", "synchronize"] + workflow_dispatch: inputs: lint_fix: @@ -15,33 +16,33 @@ on: default: true concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-ci cancel-in-progress: true env: - LINT_FIX: ${{ fromJSON(inputs.lint_fix || 'false') || github.event_name == 'pull_request' && github.head_ref != 'main' }} + fixLintingErrors: ${{ fromJSON(inputs.lint_fix || 'false') || github.event_name == 'pull_request' && github.head_ref != 'main' }} jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: install pnpm - run: | - npm i -g pnpm - pnpm -v - - uses: actions/setup-node@v4 + + - uses: pnpm/action-setup@v4 with: - cache: "pnpm" - node-version-file: ".nvmrc" - - run: pnpm install + version: 9.4.0 + run_install: true + - run: pnpm typecheck + - run: pnpm lint - if: ${{ ! fromJSON(env.LINT_FIX) }} + if: ${{ ! fromJSON(env.fixLintingErrors) }} + - run: pnpm lint:fix - if: ${{ fromJSON(env.LINT_FIX) }} + if: ${{ fromJSON(env.fixLintingErrors) }} + - name: Commit linter fixes uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 #v5 - if: ${{ fromJSON(env.LINT_FIX) }} + if: ${{ fromJSON(env.fixLintingErrors) }} with: - commit_message: "automated commit: pnpm lint:fix" + commit_message: "pnpm lint:fix" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..c729a56 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,49 @@ +name: Deploy static content to Pages + +on: + push: + branches: ["main"] + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - uses: pnpm/action-setup@v4 + with: + version: 9.4.0 + run_install: true + + - name: Build Ladle static content + run: pnpm ladle build + env: + BASE_URL: ${{ github.event.repository.name }} + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: "build" + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.ladle/components.tsx b/.ladle/components.tsx index 5d99eff..5094ead 100644 --- a/.ladle/components.tsx +++ b/.ladle/components.tsx @@ -1,5 +1,6 @@ import React from "react" -import { Truck, AlertCircle } from "react-feather" +import { AlertCircle, ThumbsUp, Truck } from "react-feather" +import "./style.css" import { CustomGlobalProvider, @@ -9,12 +10,14 @@ import { } from "ladle-inject-custom-addons" import { GettingStarted } from "./components/GettingStarted" +import { Context, contextMessage } from "./components/ContextExample" const packageName = "ladle-inject-custom-addons" interface MyCustomAddonConfig { customAddon: { enabled: boolean + customMessage: string } } @@ -22,40 +25,46 @@ export const Provider: CustomGlobalProvider = ({ config, children, }) => ( - + {children} + + + {config.addons.customAddon.enabled && ( - } - tooltip="this addon must be enabled in config.mjs to show up" - /> + } + label="Use custom configuration" + tooltip="Uses a custom configuration to show a custom message." + > +

+ This addon is set up so that it must be enabled in the{" "} + config.mjs file to show up, similar to the built-in Ladle + addons. +

+

{config.addons.customAddon.customMessage}

+
)} +
) -const Context = React.createContext({ - message: "not in context", -}) - const PrependedHelloAddon = ({ position = 0 }) => { const [packageManager, setPackageManager] = React.useState("") return ( } + label="Show package info" tooltip="Shows info about this package." - style={{ display: "grid", gap: 16 }} position={position} >

{packageName}

Add your own components in the Ladle addon panel!

-
- ✨🐙✨ -
+
✨🐙✨
{ const CustomDialogAddon = ({ position = 0 }) => ( } + label="Show an alert" onClick={() => alert("hello!")} tooltip="Shows an alert to say hello." position={position} /> ) -const ContextTestAddon = ({ position = 0 }) => { +export const ContextTestAddon = ({ position = 0 }) => { const { message } = React.useContext(Context) return ( } - label="Test context" - tooltip="Tests if the context provider can be used within the button component." - badge={1} + label="Use a context" + tooltip="Demonstrates that addon buttons are able to inherit parent context." position={position} > -

{message}

+ {message}
) } diff --git a/.ladle/components/ContextExample.tsx b/.ladle/components/ContextExample.tsx new file mode 100644 index 0000000..a58c8ed --- /dev/null +++ b/.ladle/components/ContextExample.tsx @@ -0,0 +1,28 @@ +import React from "react" + +export const Context = React.createContext({ + message: ( +

+ If you can see this message, it means that the addon is not receiving data + from the context provider in the CustomGlobalProvider component. Dang. +

+ ), +}) + +export const contextMessage = ( + <> +

+ This message being displayed shows that the addon button is able to + receive data from a context provider. Yay! +

+

+ This message is populated from a context provided in the{" "} + CustomGlobalProvider component. If you want to know more + about how it works, check out the{" "} + + components.tsx + {" "} + source code on GitHub. +

+ +) diff --git a/.ladle/components/PlusOneAnimated.tsx b/.ladle/components/PlusOneAnimated.tsx new file mode 100644 index 0000000..06193a4 --- /dev/null +++ b/.ladle/components/PlusOneAnimated.tsx @@ -0,0 +1,22 @@ +import React from "react" + +const PlusOne = () => { + return
+1
+} + +export const usePlusOneAnimated = () => { + const [elements, setElements] = React.useState([]) + + const addPlusOne = React.useCallback(() => { + const element = + setElements((elems) => [...elems, element]) + setTimeout(() => { + setElements((elems) => elems.filter((e) => e !== element)) + }, 1000) + }, []) + + return { + addPlusOne, + elements, + } +} diff --git a/.ladle/config.mjs b/.ladle/config.mjs index b518106..34a0224 100644 --- a/.ladle/config.mjs +++ b/.ladle/config.mjs @@ -1,11 +1,14 @@ +const baseUrl = process.env["BASE_URL"] || "/" + export default { stories: ["{src,.ladle}/**/*.stories.{js,jsx,ts,tsx}"], + base: baseUrl, addons: { ladle: { enabled: false, }, theme: { - enabled: false, + enabled: true, }, mode: { enabled: false, @@ -14,7 +17,8 @@ export default { enabled: false, }, customAddon: { - enabled: false, + enabled: true, + customMessage: "You can also add custom values to the config file!", }, }, } diff --git a/.ladle/example.stories.tsx b/.ladle/example.stories.tsx index 22cc2f2..616f796 100644 --- a/.ladle/example.stories.tsx +++ b/.ladle/example.stories.tsx @@ -1,8 +1,84 @@ -import type { Story } from "@ladle/react"; +import React from "react" +import type { Story } from "@ladle/react" +import { PlusCircle } from "react-feather" +import { AddonButton } from "ladle-inject-custom-addons" +import { usePlusOneAnimated } from "./components/PlusOneAnimated" -export const Simple: Story = () => ( -
    -
  • Item 1
  • -
  • Item 2
  • -
+export const Home: Story = () => ( +
+

ladle-inject-custom-addons

+ + A screenshot of the Ladle addon bar, with a dialog box displaying text: 'ladle-inject-custom-addons' Add your own components in the Ladle addon panel! ✨🐙✨ +

+ This is a working example of the ladle-inject-custom-addons{" "} + package. Check out the buttons at the bottom of the screen to see it in + action. +

+

+ Learn more at the{" "} + + hiddenist/ladle-inject-custom-addons + {" "} + GitHub repository! +

+
) + +export const StoryWithAddon: Story = () => { + const [clickCount, setClickCount] = React.useState(0) + + const { elements, addPlusOne } = usePlusOneAnimated() + + return ( +
+

This story has its own addon button

+

+ In addition to creating addon buttons in the global context, you can + also create addon buttons that only show up for specific stories! +

+

+

+ You have clicked the button {clickCount} time{clickCount != 1 && "s"} +

+

+

+ Curious how it's done? Find the source code button and take a look at + the code for this story! +

+ + + + {elements} + + } + label="Count clicks" + tooltip="Increment the click count for this story" + position={0} + badge={clickCount < 10 ? clickCount : "9+"} + onClick={() => { + addPlusOne() + setClickCount((c) => c + 1) + }} + /> +
+ ) +} diff --git a/.ladle/style.css b/.ladle/style.css new file mode 100644 index 0000000..01a38bb --- /dev/null +++ b/.ladle/style.css @@ -0,0 +1,67 @@ +body { + font-family: Arial, Helvetica, sans-serif; +} + +[data-theme="dark"] body { + color: #eee; +} + +.octomoji { + font-size: 50px; + text-align: "center"; + margin-bottom: 16px; +} + +.home { + display: flex; + flex-direction: column; + align-items: center; +} + +.home p { + max-width: 80ch; +} + +.home img { + max-width: 100%; +} + +.github-badges { + display: flex; + justify-content: center; + gap: 16px; +} + +.plus-one { + position: absolute; + bottom: 100%; + animation: plus-one 1s forwards; + font-weight: bold; + font-size: 12px; + color: rgba(10, 60, 90, 0.9); + background-color: rgba(255, 255, 255, 0.2); + border-radius: 4px; +} + +[data-theme="dark"] .plus-one { + color: rgba(117, 194, 242, 0.9); + background-color: rgba(0, 0, 0, 0.2); +} + +@keyframes plus-one { + 0% { + opacity: 0; + transform: translateY(100%) scale(0.5); + } + 25% { + opacity: 1; + transform: translateY(-50%) scale(1); + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + transform: translateY(0); + } +} diff --git a/README.md b/README.md index c7568d5..ba43565 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [Ladle](https://github.com/tajo/ladle) doesn't officially support third party addons yet. Now we can pretend it does! -Confirmed to work up through `@ladle/react` version ^4.10.0. +Check out working example: https://hiddenist.github.io/ladle-inject-custom-addons/ - [Quick Start](#quick-start) - [Customization](#customization) @@ -22,7 +22,9 @@ Confirmed to work up through `@ladle/react` version ^4.10.0. pnpm add ladle-inject-custom-addons ``` -> **Note**
+This package is confirmed to work with `@ladle/react` up through version ^4.10.0. + +> [!NOTE] > Replace `pnpm` with `yarn` or `npm` to match what you use for your project. 😉 ### Basic Usage @@ -51,12 +53,17 @@ const HelloAddon = () => ( } onClick={() => alert("hello!")} + label="Say hello" tooltip="Shows an alert to say hello." /> ) const DialogExampleAddon = () => ( - } tooltip="Opens a dialog box."> + } + label="Show dialog" + tooltip="Opens a dialog box." + >

Custom text, or more advanced components, will show up in a dialog.

) @@ -66,7 +73,7 @@ const DialogExampleAddon = () => ( ### Icons -Most icon libraries will work for your addon buttons. Check out [react-feather](https://github.com/feathericons/react-feather) if you're not sure where to start! +Need icons for your addon buttons? Check out [react-feather](https://github.com/feathericons/react-feather) for a great set of icons! You can also add your own SVGs for your icons. Use `currentColor` for the stroke or fill on the icon to have it use the default hover and active colors. The icons are expected to be 24 by 24 pixels in size. @@ -88,9 +95,12 @@ const MyIcon = () => ( +> [!NOTE] +> Please be aware that you may encounter issues using certain libraries for your button icons. Material UI icons have been observed causing style issues with production bundles of component story libraries. + ### Button order -If you would like to put your custom addons at a different place in the list, you can pass the `position` property. +If you would like your custom addon to display in a different position within the addon list, you can pass the `position` property. ```tsx // .ladle/components.tsx @@ -100,6 +110,7 @@ export const Provider = ({ children }) => ( } onClick={() => alert("hello!")} + label="Say hello" tooltip="Shows an alert to say hello." // This button will be third in the addon panel list: position={3} @@ -113,7 +124,7 @@ export const Provider = ({ children }) => ( `AddonButton` utilizes a [React Portal](https://react.dev/reference/react-dom/createPortal) to mount your buttons within the existing Ladle addon list. -> **Warning**
+> [!WARNING] > This method of injecting components may not be very stable. Changes to the Ladle package could easily break this in future updates. ## Questions or contributions diff --git a/package.json b/package.json index 27fcc68..4f25c25 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ladle-inject-custom-addons", - "version": "4.0.0", + "version": "4.0.1", "description": "Adds a button to the Ladle addon bar", "files": [ "dist", @@ -9,18 +9,18 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs.js" + "import": "./dist/index.mjs", + "require": "./dist/index.js" }, "./*": { "types": "./dist/components/*.d.ts", - "import": "./dist/components/*.js", - "require": "./dist/components/*.cjs.js" + "import": "./dist/components/*.mjs", + "require": "./dist/components/*.js" } }, "types": "./dist/index.d.ts", - "module": "./dist/index.js", - "main": "./dist/index.cjs.js", + "module": "./dist/index.mjs", + "main": "./dist/index.js", "scripts": { "build": "tsup && tsc -p tsconfig.build.json", "build:watch": "pnpm build --watch", diff --git a/tsconfig.json b/tsconfig.json index 39d0e8a..86c7fa2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,6 +31,6 @@ }, "types": ["node"] }, - "include": ["**/*", ".ladle/components.tsx"], + "include": ["**/*", ".ladle/components.tsx", ".ladle/example.stories.tsx"], "exclude": ["node_modules", "dist", ""] } diff --git a/tsup.config.ts b/tsup.config.ts index cab7129..ddef04f 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -4,5 +4,6 @@ export default defineConfig({ format: ["cjs", "esm"], entry: ["src/**/*.{ts,tsx}", "!**/*.{test,stories}.{ts,tsx}"], splitting: false, + minify: true, clean: true, })