Skip to content

Commit b5d4368

Browse files
authored
feat(shadcn-ui): add support for aliases
1 parent 3819f07 commit b5d4368

15 files changed

Lines changed: 756 additions & 306 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"shadcn-ui": patch
3+
---
4+
5+
rename package to shadcn-ui

.github/workflows/prerelease-comment.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
2929
for (const artifact of allArtifacts.data.artifacts) {
3030
// Extract the PR number and package version from the artifact name
31-
const match = /^npm-package-@shadcn-ui@(.*?)-pr-(\d+)/.exec(artifact.name);
31+
const match = /^npm-package-shadcn-ui@(.*?)-pr-(\d+)/.exec(artifact.name);
3232
3333
if (match) {
3434
require("fs").appendFileSync(
@@ -49,7 +49,7 @@ jobs:
4949
A new prerelease is available for testing:
5050
5151
```sh
52-
npx @shadcn/ui@${{ env.BETA_PACKAGE_VERSION }}
52+
npx shadcn-ui@${{ env.BETA_PACKAGE_VERSION }}
5353
```
5454
5555
- name: "Remove the autorelease label once published"

.github/workflows/prerelease.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,5 @@ jobs:
5454
- name: Upload packaged artifact
5555
uses: actions/upload-artifact@v2
5656
with:
57-
name: npm-package-@shadcn-ui@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
57+
name: npm-package-shadcn-ui@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
5858
path: packages/cli/dist/index.js
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// Inspired by react-hot-toast library
2+
import * as React from "react"
3+
4+
import { ToastActionElement, type ToastProps } from "@/components/ui/toast"
5+
6+
const TOAST_LIMIT = 1
7+
const TOAST_REMOVE_DELAY = 1000
8+
9+
type ToasterToast = ToastProps & {
10+
id: string
11+
title?: React.ReactNode
12+
description?: React.ReactNode
13+
action?: ToastActionElement
14+
}
15+
16+
const actionTypes = {
17+
ADD_TOAST: "ADD_TOAST",
18+
UPDATE_TOAST: "UPDATE_TOAST",
19+
DISMISS_TOAST: "DISMISS_TOAST",
20+
REMOVE_TOAST: "REMOVE_TOAST",
21+
} as const
22+
23+
let count = 0
24+
25+
function genId() {
26+
count = (count + 1) % Number.MAX_VALUE
27+
return count.toString()
28+
}
29+
30+
type ActionType = typeof actionTypes
31+
32+
type Action =
33+
| {
34+
type: ActionType["ADD_TOAST"]
35+
toast: ToasterToast
36+
}
37+
| {
38+
type: ActionType["UPDATE_TOAST"]
39+
toast: Partial<ToasterToast>
40+
}
41+
| {
42+
type: ActionType["DISMISS_TOAST"]
43+
toastId?: ToasterToast["id"]
44+
}
45+
| {
46+
type: ActionType["REMOVE_TOAST"]
47+
toastId?: ToasterToast["id"]
48+
}
49+
50+
interface State {
51+
toasts: ToasterToast[]
52+
}
53+
54+
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
55+
56+
const addToRemoveQueue = (toastId: string) => {
57+
if (toastTimeouts.has(toastId)) {
58+
return
59+
}
60+
61+
const timeout = setTimeout(() => {
62+
toastTimeouts.delete(toastId)
63+
dispatch({
64+
type: "REMOVE_TOAST",
65+
toastId: toastId,
66+
})
67+
}, TOAST_REMOVE_DELAY)
68+
69+
toastTimeouts.set(toastId, timeout)
70+
}
71+
72+
export const reducer = (state: State, action: Action): State => {
73+
switch (action.type) {
74+
case "ADD_TOAST":
75+
return {
76+
...state,
77+
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
78+
}
79+
80+
case "UPDATE_TOAST":
81+
return {
82+
...state,
83+
toasts: state.toasts.map((t) =>
84+
t.id === action.toast.id ? { ...t, ...action.toast } : t
85+
),
86+
}
87+
88+
case "DISMISS_TOAST": {
89+
const { toastId } = action
90+
91+
// ! Side effects ! - This could be extracted into a dismissToast() action,
92+
// but I'll keep it here for simplicity
93+
if (toastId) {
94+
addToRemoveQueue(toastId)
95+
} else {
96+
state.toasts.forEach((toast) => {
97+
addToRemoveQueue(toast.id)
98+
})
99+
}
100+
101+
return {
102+
...state,
103+
toasts: state.toasts.map((t) =>
104+
t.id === toastId || toastId === undefined
105+
? {
106+
...t,
107+
open: false,
108+
}
109+
: t
110+
),
111+
}
112+
}
113+
case "REMOVE_TOAST":
114+
if (action.toastId === undefined) {
115+
return {
116+
...state,
117+
toasts: [],
118+
}
119+
}
120+
return {
121+
...state,
122+
toasts: state.toasts.filter((t) => t.id !== action.toastId),
123+
}
124+
}
125+
}
126+
127+
const listeners: Array<(state: State) => void> = []
128+
129+
let memoryState: State = { toasts: [] }
130+
131+
function dispatch(action: Action) {
132+
memoryState = reducer(memoryState, action)
133+
listeners.forEach((listener) => {
134+
listener(memoryState)
135+
})
136+
}
137+
138+
interface Toast extends Omit<ToasterToast, "id"> {}
139+
140+
function toast({ ...props }: Toast) {
141+
const id = genId()
142+
143+
const update = (props: ToasterToast) =>
144+
dispatch({
145+
type: "UPDATE_TOAST",
146+
toast: { ...props, id },
147+
})
148+
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
149+
150+
dispatch({
151+
type: "ADD_TOAST",
152+
toast: {
153+
...props,
154+
id,
155+
open: true,
156+
onOpenChange: (open) => {
157+
if (!open) dismiss()
158+
},
159+
},
160+
})
161+
162+
return {
163+
id: id,
164+
dismiss,
165+
update,
166+
}
167+
}
168+
169+
function useToast() {
170+
const [state, setState] = React.useState<State>(memoryState)
171+
172+
React.useEffect(() => {
173+
listeners.push(setState)
174+
return () => {
175+
const index = listeners.indexOf(setState)
176+
if (index > -1) {
177+
listeners.splice(index, 1)
178+
}
179+
}
180+
}, [state])
181+
182+
return {
183+
...state,
184+
toast,
185+
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
186+
}
187+
}
188+
189+
export { useToast, toast }

0 commit comments

Comments
 (0)