@crisp-ui-kit/crisp 0.57.1 → 0.57.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authshell.css +9 -4
- package/dist/authshell.layered.css +9 -4
- package/dist/badge.css +3 -3
- package/dist/badge.layered.css +3 -3
- package/dist/board.css +28 -8
- package/dist/board.layered.css +28 -8
- package/dist/bottomsheet.css +26 -0
- package/dist/bottomsheet.d.ts +7 -0
- package/dist/bottomsheet.layered.css +28 -0
- package/dist/button.css +10 -6
- package/dist/button.d.ts +2 -2
- package/dist/button.layered.css +10 -6
- package/dist/checkbox.css +7 -3
- package/dist/checkbox.layered.css +7 -3
- package/dist/chip.css +16 -1
- package/dist/chip.d.ts +3 -1
- package/dist/chip.layered.css +16 -1
- package/dist/chipgroup.css +5 -0
- package/dist/chipgroup.d.ts +7 -0
- package/dist/chipgroup.layered.css +7 -0
- package/dist/cjs/components/badge/badge.recipe.js +22 -8
- package/dist/cjs/components/bottomsheet/bottomsheet.js +90 -0
- package/dist/cjs/components/bottomsheet/bottomsheet.recipe.js +77 -0
- package/dist/cjs/components/bottomsheet/index.js +9 -0
- package/dist/cjs/components/breadcrumb/breadcrumb.js +1 -1
- package/dist/cjs/components/button/button.recipe.js +67 -15
- package/dist/cjs/components/checkbox/checkbox.recipe.js +61 -9
- package/dist/cjs/components/chip/chip.js +14 -2
- package/dist/cjs/components/chip/chip.recipe.js +72 -2
- package/dist/cjs/components/chipgroup/chipgroup.js +51 -0
- package/dist/cjs/components/chipgroup/chipgroup.recipe.js +36 -0
- package/dist/cjs/components/chipgroup/index.js +7 -0
- package/dist/cjs/components/datatable/datatable.js +5 -1
- package/dist/cjs/components/datatable/datatable.recipe.js +21 -6
- package/dist/cjs/components/datepickerfield/datepickerfield.js +2 -2
- package/dist/cjs/components/datepickerfield/datepickerfield.recipe.js +16 -3
- package/dist/cjs/components/dialog/dialog.recipe.js +21 -10
- package/dist/cjs/components/emptystate/emptystate.recipe.js +7 -2
- package/dist/cjs/components/fileupload/fileupload.js +21 -4
- package/dist/cjs/components/input/index.js +2 -1
- package/dist/cjs/components/input/input.js +31 -1
- package/dist/cjs/components/input/input.recipe.js +16 -9
- package/dist/cjs/components/menu/menu.js +18 -2
- package/dist/cjs/components/menu/menu.recipe.js +21 -7
- package/dist/cjs/components/menusheet/index.js +9 -0
- package/dist/cjs/components/menusheet/menusheet.js +133 -0
- package/dist/cjs/components/menusheet/menusheet.recipe.js +103 -0
- package/dist/cjs/components/multiselect/multiselect.js +1 -1
- package/dist/cjs/components/multiselect/multiselect.recipe.js +33 -9
- package/dist/cjs/components/select/select.js +1 -1
- package/dist/cjs/components/select/select.recipe.js +38 -12
- package/dist/cjs/components/snackbar/index.js +8 -0
- package/dist/cjs/components/snackbar/snackbar.js +121 -0
- package/dist/cjs/components/snackbar/snackbar.recipe.js +71 -0
- package/dist/cjs/components/table/table.recipe.js +7 -1
- package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +37 -12
- package/dist/cjs/components/tabs/tabs.js +10 -4
- package/dist/cjs/components/tabs/tabs.recipe.js +23 -6
- package/dist/cjs/components/textarea/textarea.recipe.js +11 -8
- package/dist/cjs/core/floating-portal.js +7 -4
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/product/authshell/authshell.recipe.js +5 -1
- package/dist/cjs/product/command/command.recipe.js +4 -1
- package/dist/cjs/product/comments/comments.js +7 -2
- package/dist/cjs/product/comments/comments.recipe.js +25 -7
- package/dist/cjs/product/inbox/inbox.js +11 -3
- package/dist/cjs/product/memberssettings/index.js +6 -0
- package/dist/cjs/product/memberssettings/memberssettings.js +99 -0
- package/dist/cjs/product/memberssettings/memberssettings.recipe.js +83 -0
- package/dist/cjs/product/notificationssettings/index.js +5 -0
- package/dist/cjs/product/notificationssettings/notificationssettings.js +19 -0
- package/dist/cjs/product/notificationssettings/notificationssettings.recipe.js +44 -0
- package/dist/cjs/product/recordpage/index.js +2 -1
- package/dist/cjs/product/recordpage/recordpage.js +106 -9
- package/dist/cjs/product/recordpage/recordpage.recipe.js +68 -6
- package/dist/cjs/product/workspacegeneralsettings/index.js +5 -0
- package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.js +21 -0
- package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +51 -0
- package/dist/cjs/tokens/contract.js +1 -0
- package/dist/cjs/tokens/elevation.js +8 -5
- package/dist/cjs/tokens/palette.js +28 -8
- package/dist/cjs/tokens/scale.js +44 -2
- package/dist/cjs/tokens/semantic.js +2 -0
- package/dist/columncalc.css +15 -7
- package/dist/columncalc.layered.css +15 -7
- package/dist/command.css +23 -19
- package/dist/command.layered.css +23 -19
- package/dist/comments.css +28 -19
- package/dist/comments.d.ts +2 -2
- package/dist/comments.layered.css +28 -19
- package/dist/datatable.css +171 -71
- package/dist/datatable.layered.css +171 -71
- package/dist/datepicker.css +13 -41
- package/dist/datepicker.layered.css +13 -41
- package/dist/datepickerfield.css +12 -3
- package/dist/datepickerfield.d.ts +2 -2
- package/dist/datepickerfield.layered.css +12 -3
- package/dist/dialog.css +12 -7
- package/dist/dialog.layered.css +12 -7
- package/dist/emptystate.css +5 -3
- package/dist/emptystate.layered.css +5 -3
- package/dist/esm/components/badge/badge.recipe.js +22 -8
- package/dist/esm/components/bottomsheet/bottomsheet.js +52 -0
- package/dist/esm/components/bottomsheet/bottomsheet.recipe.js +74 -0
- package/dist/esm/components/bottomsheet/index.js +2 -0
- package/dist/esm/components/breadcrumb/breadcrumb.js +1 -1
- package/dist/esm/components/button/button.recipe.js +68 -16
- package/dist/esm/components/checkbox/checkbox.recipe.js +61 -9
- package/dist/esm/components/chip/chip.js +14 -2
- package/dist/esm/components/chip/chip.recipe.js +72 -2
- package/dist/esm/components/chipgroup/chipgroup.js +15 -0
- package/dist/esm/components/chipgroup/chipgroup.recipe.js +33 -0
- package/dist/esm/components/chipgroup/index.js +2 -0
- package/dist/esm/components/datatable/datatable.js +5 -1
- package/dist/esm/components/datatable/datatable.recipe.js +21 -6
- package/dist/esm/components/datepickerfield/datepickerfield.js +2 -2
- package/dist/esm/components/datepickerfield/datepickerfield.recipe.js +16 -3
- package/dist/esm/components/dialog/dialog.recipe.js +22 -11
- package/dist/esm/components/emptystate/emptystate.recipe.js +7 -2
- package/dist/esm/components/fileupload/fileupload.js +20 -5
- package/dist/esm/components/input/index.js +1 -1
- package/dist/esm/components/input/input.js +30 -0
- package/dist/esm/components/input/input.recipe.js +17 -10
- package/dist/esm/components/menu/menu.js +18 -2
- package/dist/esm/components/menu/menu.recipe.js +21 -7
- package/dist/esm/components/menusheet/index.js +2 -0
- package/dist/esm/components/menusheet/menusheet.js +95 -0
- package/dist/esm/components/menusheet/menusheet.recipe.js +100 -0
- package/dist/esm/components/multiselect/multiselect.js +1 -1
- package/dist/esm/components/multiselect/multiselect.recipe.js +33 -9
- package/dist/esm/components/select/select.js +1 -1
- package/dist/esm/components/select/select.recipe.js +38 -12
- package/dist/esm/components/snackbar/index.js +2 -0
- package/dist/esm/components/snackbar/snackbar.js +84 -0
- package/dist/esm/components/snackbar/snackbar.recipe.js +68 -0
- package/dist/esm/components/table/table.recipe.js +7 -1
- package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +37 -12
- package/dist/esm/components/tabs/tabs.js +10 -4
- package/dist/esm/components/tabs/tabs.recipe.js +23 -6
- package/dist/esm/components/textarea/textarea.recipe.js +12 -9
- package/dist/esm/core/floating-portal.js +7 -4
- package/dist/esm/index.js +7 -0
- package/dist/esm/product/authshell/authshell.recipe.js +5 -1
- package/dist/esm/product/command/command.recipe.js +4 -1
- package/dist/esm/product/comments/comments.js +7 -2
- package/dist/esm/product/comments/comments.recipe.js +25 -7
- package/dist/esm/product/inbox/inbox.js +11 -3
- package/dist/esm/product/memberssettings/index.js +1 -0
- package/dist/esm/product/memberssettings/memberssettings.js +62 -0
- package/dist/esm/product/memberssettings/memberssettings.recipe.js +80 -0
- package/dist/esm/product/notificationssettings/index.js +1 -0
- package/dist/esm/product/notificationssettings/notificationssettings.js +16 -0
- package/dist/esm/product/notificationssettings/notificationssettings.recipe.js +41 -0
- package/dist/esm/product/recordpage/index.js +1 -1
- package/dist/esm/product/recordpage/recordpage.js +106 -10
- package/dist/esm/product/recordpage/recordpage.recipe.js +68 -6
- package/dist/esm/product/workspacegeneralsettings/index.js +1 -0
- package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.js +18 -0
- package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +48 -0
- package/dist/esm/tokens/contract.js +2 -1
- package/dist/esm/tokens/elevation.js +8 -5
- package/dist/esm/tokens/palette.js +28 -8
- package/dist/esm/tokens/scale.js +43 -1
- package/dist/esm/tokens/semantic.js +2 -0
- package/dist/fileupload.css +3 -1
- package/dist/fileupload.layered.css +3 -1
- package/dist/input.css +6 -4
- package/dist/input.layered.css +6 -4
- package/dist/listpage.css +3 -1
- package/dist/listpage.layered.css +3 -1
- package/dist/memberssettings.css +67 -0
- package/dist/memberssettings.d.ts +7 -0
- package/dist/memberssettings.layered.css +69 -0
- package/dist/menu.css +2 -2
- package/dist/menu.layered.css +2 -2
- package/dist/menusheet.css +36 -0
- package/dist/menusheet.d.ts +7 -0
- package/dist/menusheet.layered.css +38 -0
- package/dist/multiselect.css +13 -6
- package/dist/multiselect.layered.css +13 -6
- package/dist/native/tokens.d.ts +2 -1
- package/dist/native/tokens.js +20 -3
- package/dist/notificationssettings.css +22 -0
- package/dist/notificationssettings.d.ts +7 -0
- package/dist/notificationssettings.layered.css +24 -0
- package/dist/objectsettings.css +4 -4
- package/dist/objectsettings.layered.css +4 -4
- package/dist/recordpage.css +90 -23
- package/dist/recordpage.layered.css +90 -23
- package/dist/recordpanel.css +35 -16
- package/dist/recordpanel.layered.css +35 -16
- package/dist/resizablepanel.css +8 -5
- package/dist/resizablepanel.layered.css +8 -5
- package/dist/richtext.css +24 -8
- package/dist/richtext.layered.css +24 -8
- package/dist/savedviews.css +9 -2
- package/dist/savedviews.layered.css +9 -2
- package/dist/select.css +25 -5
- package/dist/select.layered.css +25 -5
- package/dist/settingsshell.css +2 -3
- package/dist/settingsshell.layered.css +2 -3
- package/dist/slider.css +6 -27
- package/dist/slider.layered.css +6 -27
- package/dist/snackbar.css +24 -0
- package/dist/snackbar.d.ts +7 -0
- package/dist/snackbar.layered.css +26 -0
- package/dist/statcard.css +3 -1
- package/dist/statcard.layered.css +3 -1
- package/dist/styles.css +911 -354
- package/dist/styles.layered.css +911 -354
- package/dist/table.css +35 -12
- package/dist/table.layered.css +35 -12
- package/dist/tabletoolbar.css +35 -24
- package/dist/tabletoolbar.layered.css +35 -24
- package/dist/tabs.css +18 -4
- package/dist/tabs.layered.css +18 -4
- package/dist/textarea.css +2 -6
- package/dist/textarea.layered.css +2 -6
- package/dist/themepicker.css +2 -1
- package/dist/themepicker.layered.css +2 -1
- package/dist/tokens.css +13 -2
- package/dist/tokens.json +20 -3
- package/dist/types/components/bottomsheet/bottomsheet.d.ts +35 -0
- package/dist/types/components/bottomsheet/bottomsheet.recipe.d.ts +3 -0
- package/dist/types/components/bottomsheet/index.d.ts +2 -0
- package/dist/types/components/breadcrumb/breadcrumb.d.ts +9 -1
- package/dist/types/components/button/button.d.ts +2 -0
- package/dist/types/components/button/button.recipe.d.ts +24 -6
- package/dist/types/components/checkbox/checkbox.recipe.d.ts +28 -5
- package/dist/types/components/chip/chip.d.ts +22 -0
- package/dist/types/components/chip/chip.recipe.d.ts +49 -1
- package/dist/types/components/chipgroup/chipgroup.d.ts +14 -0
- package/dist/types/components/chipgroup/chipgroup.recipe.d.ts +5 -0
- package/dist/types/components/chipgroup/index.d.ts +2 -0
- package/dist/types/components/datatable/datatable.d.ts +19 -0
- package/dist/types/components/datepickerfield/datepickerfield.d.ts +4 -1
- package/dist/types/components/datepickerfield/datepickerfield.recipe.d.ts +18 -3
- package/dist/types/components/fileupload/fileupload.d.ts +26 -0
- package/dist/types/components/input/index.d.ts +1 -1
- package/dist/types/components/input/input.d.ts +72 -0
- package/dist/types/components/menusheet/index.d.ts +2 -0
- package/dist/types/components/menusheet/menusheet.d.ts +42 -0
- package/dist/types/components/menusheet/menusheet.recipe.d.ts +3 -0
- package/dist/types/components/snackbar/index.d.ts +2 -0
- package/dist/types/components/snackbar/snackbar.d.ts +36 -0
- package/dist/types/components/snackbar/snackbar.recipe.d.ts +5 -0
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +28 -1
- package/dist/types/components/tabs/tabs.d.ts +11 -1
- package/dist/types/core/floating-portal.d.ts +6 -5
- package/dist/types/index.d.ts +7 -0
- package/dist/types/product/comments/comments.d.ts +9 -3
- package/dist/types/product/comments/comments.recipe.d.ts +12 -2
- package/dist/types/product/inbox/inbox.d.ts +13 -1
- package/dist/types/product/memberssettings/index.d.ts +1 -0
- package/dist/types/product/memberssettings/memberssettings.d.ts +85 -0
- package/dist/types/product/memberssettings/memberssettings.recipe.d.ts +3 -0
- package/dist/types/product/notificationssettings/index.d.ts +1 -0
- package/dist/types/product/notificationssettings/notificationssettings.d.ts +37 -0
- package/dist/types/product/notificationssettings/notificationssettings.recipe.d.ts +3 -0
- package/dist/types/product/recordpage/index.d.ts +1 -1
- package/dist/types/product/recordpage/recordpage.d.ts +99 -2
- package/dist/types/product/workspacegeneralsettings/index.d.ts +1 -0
- package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.d.ts +48 -0
- package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.recipe.d.ts +3 -0
- package/dist/types/tokens/contract.d.ts +4 -1
- package/dist/types/tokens/elevation.d.ts +8 -5
- package/dist/types/tokens/palette.d.ts +9 -1
- package/dist/types/tokens/scale.d.ts +11 -1
- package/dist/types/tokens/semantic.d.ts +2 -0
- package/dist/wheelpicker.css +4 -2
- package/dist/wheelpicker.layered.css +4 -2
- package/dist/workspacegeneralsettings.css +30 -0
- package/dist/workspacegeneralsettings.d.ts +7 -0
- package/dist/workspacegeneralsettings.layered.css +32 -0
- package/package.json +36 -1
|
@@ -29,7 +29,10 @@ const config = defineRecipe({
|
|
|
29
29
|
& .crisp-command-list > .crisp-command-group-label:first-child { padding-top: ${c.space["1"]}; }
|
|
30
30
|
& .crisp-command-footer { display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; height: 44px; padding: 0 ${c.space["2.5"]}; border-top: 1px solid ${c.borderSubtle}; }
|
|
31
31
|
& .crisp-command-footer-nav { display: flex; align-items: center; gap: ${c.space["1.5"]}; font-size: ${c.text.sm}; color: ${c.fgMuted}; }
|
|
32
|
-
|
|
32
|
+
/* your-moon/crisp#953: bgBrandContrast, not bgBrandSolid --
|
|
33
|
+
the accent (#0298d9) only clears 3.23:1 with white; the darker sibling
|
|
34
|
+
clears 4.5:1+, same fix already applied to Button/Badge/Avatar (#897). */
|
|
35
|
+
& .crisp-command-footer-action { display: flex; align-items: center; gap: ${c.space["2"]}; height: 28px; max-width: 60%; padding: 0 ${c.space["1"]} 0 ${c.space["2.5"]}; border: none; border-radius: ${c.radius.md}; background: ${c.bgBrandContrast}; color: ${c.fgOnBrand}; font-family: inherit; font-size: ${c.text.md}; font-weight: ${c.weight.medium}; cursor: pointer; }
|
|
33
36
|
& .crisp-command-footer-action:disabled { opacity: 0.5; cursor: default; }
|
|
34
37
|
& .crisp-command-footer-action-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
35
38
|
& .crisp-command-footer-key { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 ${c.space["1"]}; border-radius: ${c.radius.sm}; background: rgba(255,255,255,0.22); font-size: ${c.text.xs}; }
|
|
@@ -277,8 +277,13 @@ function CommentRow({ comment, replyLabel, reactLabel, resolveLabel, moreLabel,
|
|
|
277
277
|
* nothing on its own beyond that data. See `CommentCountBadge` for the
|
|
278
278
|
* separate row affordance (P-051) — deliberately NOT part of this
|
|
279
279
|
* component, since it lives in a table cell this component doesn't own.
|
|
280
|
+
*
|
|
281
|
+
* `variant="drawer"` (default) is today's measured 320px + left-border
|
|
282
|
+
* look; `variant="pane"` (crisp#960) fills its container's width instead,
|
|
283
|
+
* for a consumer embedding this in a full-width tab pane rather than a
|
|
284
|
+
* true slide-over/drawer.
|
|
280
285
|
*/
|
|
281
|
-
export function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResolved = false, onShowResolvedChange, onlyMine = false, onOnlyMineChange, activeThreadId: activeThreadIdProp, onActiveThreadChange, onPost, onReact, onResolve, onDelete, onCopyLink, emojiOptions = DEFAULT_EMOJI, mentionTrigger = "@", renderMentionPicker, onClose, closeLabel: closeLabelProp, backLabel: backLabelProp, title: titleProp, threadTitle: threadTitleProp, breadcrumbPrefix: breadcrumbPrefixProp, composerPlaceholder: composerPlaceholderProp, replyPlaceholder: replyPlaceholderProp, postLabel: postLabelProp, replyPostLabel: replyPostLabelProp, emptyLabel, filterLabel: filterLabelProp, showResolvedLabel: showResolvedLabelProp, onlyMineLabel: onlyMineLabelProp, replyActionLabel: replyActionLabelProp, reactActionLabel: reactActionLabelProp, resolveActionLabel: resolveActionLabelProp, resolvedByYouLabel: resolvedByYouLabelProp, resolvedByLabel, moreActionsLabel: moreActionsLabelProp, copyLinkLabel: copyLinkLabelProp, deleteLabel: deleteLabelProp, addReactionLabel: addReactionLabelProp, reactionSearchLabel: reactionSearchLabelProp, deleteConfirmTitle: deleteConfirmTitleProp, deleteConfirmBody: deleteConfirmBodyProp, deleteConfirmActionLabel: deleteConfirmActionLabelProp, cancelLabel: cancelLabelProp, composerMentionToolLabel: composerMentionToolLabelProp, className, ...rest }) {
|
|
286
|
+
export function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResolved = false, onShowResolvedChange, onlyMine = false, onOnlyMineChange, activeThreadId: activeThreadIdProp, onActiveThreadChange, onPost, onReact, onResolve, onDelete, onCopyLink, emojiOptions = DEFAULT_EMOJI, mentionTrigger = "@", renderMentionPicker, onClose, closeLabel: closeLabelProp, backLabel: backLabelProp, title: titleProp, threadTitle: threadTitleProp, breadcrumbPrefix: breadcrumbPrefixProp, composerPlaceholder: composerPlaceholderProp, replyPlaceholder: replyPlaceholderProp, postLabel: postLabelProp, replyPostLabel: replyPostLabelProp, emptyLabel, filterLabel: filterLabelProp, showResolvedLabel: showResolvedLabelProp, onlyMineLabel: onlyMineLabelProp, replyActionLabel: replyActionLabelProp, reactActionLabel: reactActionLabelProp, resolveActionLabel: resolveActionLabelProp, resolvedByYouLabel: resolvedByYouLabelProp, resolvedByLabel, moreActionsLabel: moreActionsLabelProp, copyLinkLabel: copyLinkLabelProp, deleteLabel: deleteLabelProp, addReactionLabel: addReactionLabelProp, reactionSearchLabel: reactionSearchLabelProp, deleteConfirmTitle: deleteConfirmTitleProp, deleteConfirmBody: deleteConfirmBodyProp, deleteConfirmActionLabel: deleteConfirmActionLabelProp, cancelLabel: cancelLabelProp, composerMentionToolLabel: composerMentionToolLabelProp, variant, className, ...rest }) {
|
|
282
287
|
const closeLabel = useCrispMessage(closeLabelProp, "Close");
|
|
283
288
|
const backLabel = useCrispMessage(backLabelProp, "Back");
|
|
284
289
|
const title = useCrispMessage(titleProp, "All comments");
|
|
@@ -331,7 +336,7 @@ export function CommentsPanel({ threads, currentUserId, subject, subjectIcon, sh
|
|
|
331
336
|
const activeThread = activeThreadId
|
|
332
337
|
? threads.find((t) => t.id === activeThreadId)
|
|
333
338
|
: null;
|
|
334
|
-
return (_jsxs("div", { className: commentsRecipe({ className }), role: "dialog", "aria-label": title, ...rest, children: [_jsxs("div", { className: "crisp-comments-head", children: [activeThread ? (_jsx("button", { type: "button", className: "crisp-comments-headbtn", "aria-label": backLabel, onClick: () => setActiveThreadId(null), children: _jsx(ChevronLeft, { "data-rtl-flip": "", size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) })) : null, _jsxs("div", { className: "crisp-comments-title-wrap", children: [_jsx("span", { className: "crisp-comments-title", children: activeThread ? threadTitle : title }), activeThread && (_jsxs("span", { className: "crisp-comments-breadcrumb", children: [breadcrumbPrefix, " ", subjectIcon, " ", subject] }))] }), !activeThread && (_jsxs(Menu, { align: "end", trigger: _jsx("button", { type: "button", className: "crisp-comments-headbtn", "aria-label": filterLabel, children: _jsx(ListFilter, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: [_jsx(MenuCheckboxItem, { checked: showResolved, onCheckedChange: onShowResolvedChange, children: showResolvedLabel }), _jsx(MenuCheckboxItem, { checked: onlyMine, onCheckedChange: onOnlyMineChange, children: onlyMineLabel })] })), onClose && (_jsx("button", { type: "button", className: "crisp-comments-headbtn", "aria-label": closeLabel, onClick: onClose, children: _jsx(X, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }))] }), _jsx("div", { className: "crisp-comments-body crisp-comments-body--pad-top", children: activeThread ? (_jsxs("div", { children: [_jsx(CommentRow, { comment: activeThread.root, currentUserId: currentUserId, replyLabel: replyActionLabel, reactLabel: reactActionLabel, resolveLabel: resolveActionLabel, moreLabel: moreActionsLabel, copyLinkLabel: copyLinkLabel, deleteLabel: deleteLabel, addReactionLabel: addReactionLabel, reactionSearchLabel: reactionSearchLabel, emojiOptions: emojiOptions, resolvable: !!onResolve, resolved: activeThread.resolved, resolvedLabel: activeThread.resolved
|
|
339
|
+
return (_jsxs("div", { className: commentsRecipe({ variant, className }), role: "dialog", "aria-label": title, ...rest, children: [_jsxs("div", { className: "crisp-comments-head", children: [activeThread ? (_jsx("button", { type: "button", className: "crisp-comments-headbtn", "aria-label": backLabel, onClick: () => setActiveThreadId(null), children: _jsx(ChevronLeft, { "data-rtl-flip": "", size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) })) : null, _jsxs("div", { className: "crisp-comments-title-wrap", children: [_jsx("span", { className: "crisp-comments-title", children: activeThread ? threadTitle : title }), activeThread && (_jsxs("span", { className: "crisp-comments-breadcrumb", children: [breadcrumbPrefix, " ", subjectIcon, " ", subject] }))] }), !activeThread && (_jsxs(Menu, { align: "end", trigger: _jsx("button", { type: "button", className: "crisp-comments-headbtn", "aria-label": filterLabel, children: _jsx(ListFilter, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: [_jsx(MenuCheckboxItem, { checked: showResolved, onCheckedChange: onShowResolvedChange, children: showResolvedLabel }), _jsx(MenuCheckboxItem, { checked: onlyMine, onCheckedChange: onOnlyMineChange, children: onlyMineLabel })] })), onClose && (_jsx("button", { type: "button", className: "crisp-comments-headbtn", "aria-label": closeLabel, onClick: onClose, children: _jsx(X, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }))] }), _jsx("div", { className: "crisp-comments-body crisp-comments-body--pad-top", children: activeThread ? (_jsxs("div", { children: [_jsx(CommentRow, { comment: activeThread.root, currentUserId: currentUserId, replyLabel: replyActionLabel, reactLabel: reactActionLabel, resolveLabel: resolveActionLabel, moreLabel: moreActionsLabel, copyLinkLabel: copyLinkLabel, deleteLabel: deleteLabel, addReactionLabel: addReactionLabel, reactionSearchLabel: reactionSearchLabel, emojiOptions: emojiOptions, resolvable: !!onResolve, resolved: activeThread.resolved, resolvedLabel: activeThread.resolved
|
|
335
340
|
? activeThread.resolvedBy?.id === currentUserId ||
|
|
336
341
|
!activeThread.resolvedBy
|
|
337
342
|
? resolvedByYouLabel
|
|
@@ -15,8 +15,8 @@ import { contract as c, elevation as e } from "../../tokens/index.js";
|
|
|
15
15
|
// width and still gets an odd number).
|
|
16
16
|
const config = defineRecipe({
|
|
17
17
|
name: "comments",
|
|
18
|
-
base: `display: flex; flex-direction: column; box-sizing: border-box;
|
|
19
|
-
background: ${c.bgSurface};
|
|
18
|
+
base: `display: flex; flex-direction: column; box-sizing: border-box; height: 100%;
|
|
19
|
+
background: ${c.bgSurface};
|
|
20
20
|
font-family: ${c.font.sans};
|
|
21
21
|
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
22
22
|
& * { box-sizing: border-box; }
|
|
@@ -96,9 +96,13 @@ const config = defineRecipe({
|
|
|
96
96
|
box-shadow, so this reuses that literal rather than re-deriving it.
|
|
97
97
|
32px/radius.lg/6px-10px padding has no matching Button \`size\` yet
|
|
98
98
|
(24/28/40 are the three that exist), so this stays hand-rolled instead
|
|
99
|
-
of stretching one of those.
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
of stretching one of those. your-moon/crisp#953:
|
|
100
|
+
bgBrandContrast, not bgBrandSolid -- the accent (#0298d9) only clears
|
|
101
|
+
3.23:1 with white; same fix already applied to Button/Badge/Avatar
|
|
102
|
+
(#897). Hover is a brightness filter on the new fill, matching that
|
|
103
|
+
PR's own Button primary treatment, not bgBrandSolidPressed. */
|
|
104
|
+
& .crisp-comments-post { display: inline-flex; align-items: center; justify-content: center; height: 32px; padding: 6px ${c.space["2.5"]}; border: none; border-radius: ${c.radius.lg}; background: ${c.bgBrandContrast}; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08); font-family: inherit; font-size: ${c.text.lg}; font-weight: ${c.weight.semibold}; color: ${c.fgOnBrand}; cursor: pointer; opacity: 1; transition: opacity .12s, filter .12s; }
|
|
105
|
+
& .crisp-comments-post:hover:not(:disabled) { filter: brightness(0.96); }
|
|
102
106
|
& .crisp-comments-post:disabled { opacity: .4; cursor: default; }
|
|
103
107
|
|
|
104
108
|
/* Filter menu -- measured: 220px, radius 12 (radius.xl), 4px padding,
|
|
@@ -110,8 +114,22 @@ const config = defineRecipe({
|
|
|
110
114
|
rather than re-derived since it's the same atomic-chip affordance. */
|
|
111
115
|
& .crisp-comments-mention { display: inline; padding: 0 2px; border-radius: 4px; background: color-mix(in srgb, ${c.bgBrandSolid} 12%, transparent); color: ${c.bgBrandSolid}; font-weight: ${c.weight.medium}; user-select: none; }
|
|
112
116
|
${rtlFlipCss}`,
|
|
113
|
-
variants: {
|
|
114
|
-
|
|
117
|
+
variants: {
|
|
118
|
+
// your-moon/crisp#960: the 320px width + left-border hairline is a
|
|
119
|
+
// drawer-consumer look (RecordPage's own comments drawer, #864), not
|
|
120
|
+
// an intrinsic property of the panel itself. A consumer embedding this
|
|
121
|
+
// in a full-width tab pane (internal/grc's own record-page Comments
|
|
122
|
+
// tab, sibling to Related/Activity/Notes/Tasks/Lists, all of which
|
|
123
|
+
// fill the pane) needs `width: 100%` and no border -- a tab pane
|
|
124
|
+
// doesn't need a seam separating it from itself. `drawer` (default)
|
|
125
|
+
// is today's exact, already-measured look; nothing changes for an
|
|
126
|
+
// existing consumer that doesn't pass `variant`.
|
|
127
|
+
variant: {
|
|
128
|
+
drawer: `width: 320px; border-inline-start: 1px solid ${c.borderSubtle};`,
|
|
129
|
+
pane: `width: 100%;`,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
defaultVariants: { variant: "drawer" },
|
|
115
133
|
compoundVariants: [],
|
|
116
134
|
// Two pieces below intentionally live OUTSIDE the nested \`base\` above,
|
|
117
135
|
// the same reason Menu's own \`.crisp-menu-content\` does (menu.recipe.ts's
|
|
@@ -18,10 +18,18 @@ export function Inbox({ notifications = [], title: titleProp, tabs, activeTab, o
|
|
|
18
18
|
// own `empty`.
|
|
19
19
|
const crispMessages = useCrispMessages();
|
|
20
20
|
const title = titleProp ?? crispMessages.Notifications ?? "Notifications";
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
// A tab's own empty copy wins over the instance-level prop, which wins
|
|
22
|
+
// over the built-in default — same precedence as `title`/`emptyAction`.
|
|
23
|
+
const activeTabDef = tabs?.find((t) => t.id === activeTab);
|
|
24
|
+
const emptyTitle = activeTabDef?.emptyTitle ??
|
|
25
|
+
emptyTitleProp ??
|
|
26
|
+
crispMessages["No notifications"] ??
|
|
27
|
+
"No notifications";
|
|
28
|
+
const emptySubtitle = activeTabDef?.emptySubtitle ??
|
|
29
|
+
emptySubtitleProp ??
|
|
23
30
|
crispMessages["Notifications such as mentions and important updates will be displayed here."] ??
|
|
24
31
|
"Notifications such as mentions and important updates will be displayed here.";
|
|
32
|
+
const resolvedEmptyAction = activeTabDef?.emptyAction ?? emptyAction;
|
|
25
33
|
const tabsLabel = useCrispMessage(tabsLabelProp, "Notification views");
|
|
26
34
|
// crisp's shared tablist model, the one `Tabs` runs (#195). Hand-rolling a
|
|
27
35
|
// second one is how Inbox ended up with no arrow keys, no roving tabindex
|
|
@@ -32,6 +40,6 @@ export function Inbox({ notifications = [], title: titleProp, tabs, activeTab, o
|
|
|
32
40
|
select: (id) => onTabChange?.(id),
|
|
33
41
|
listRef,
|
|
34
42
|
});
|
|
35
|
-
return (_jsxs("div", { className: inboxRecipe({ className }), ...rest, children: [_jsxs("div", { className: "crisp-inbox-head", children: [_jsx("div", { className: "crisp-inbox-title", children: title }), tabs && tabs.length > 0 && (_jsx("div", { ref: listRef, className: "crisp-inbox-tabs", role: "tablist", "aria-label": tabsLabel, onKeyDown: onKeyDown, children: tabs.map((t) => (_jsxs("button", { type: "button", role: "tab", "aria-selected": t.id === activeTab, tabIndex: tabIndexFor(t.id), "data-value": t.id, disabled: t.disabled, className: "crisp-inbox-tab", "data-active": t.id === activeTab ? "" : undefined, onClick: () => onTabChange?.(t.id), children: [t.label, t.count != null && (_jsxs("span", { className: "crisp-inbox-tab-count", children: ["(", t.count, ")"] }))] }, t.id))) }))] }), notifications.length === 0 ? (_jsxs("div", { className: "crisp-inbox-empty", children: [_jsx("span", { className: "crisp-inbox-empty-icon", children: _jsx(Bell, { size: 40, strokeWidth: 1.25 }) }), _jsx("div", { className: "crisp-inbox-empty-title", children: emptyTitle }), emptySubtitle != null && (_jsx("div", { className: "crisp-inbox-empty-sub", children: emptySubtitle })),
|
|
43
|
+
return (_jsxs("div", { className: inboxRecipe({ className }), ...rest, children: [_jsxs("div", { className: "crisp-inbox-head", children: [_jsx("div", { className: "crisp-inbox-title", children: title }), tabs && tabs.length > 0 && (_jsx("div", { ref: listRef, className: "crisp-inbox-tabs", role: "tablist", "aria-label": tabsLabel, onKeyDown: onKeyDown, children: tabs.map((t) => (_jsxs("button", { type: "button", role: "tab", "aria-selected": t.id === activeTab, tabIndex: tabIndexFor(t.id), "data-value": t.id, disabled: t.disabled, className: "crisp-inbox-tab", "data-active": t.id === activeTab ? "" : undefined, onClick: () => onTabChange?.(t.id), children: [t.label, t.count != null && (_jsxs("span", { className: "crisp-inbox-tab-count", children: ["(", t.count, ")"] }))] }, t.id))) }))] }), notifications.length === 0 ? (_jsxs("div", { className: "crisp-inbox-empty", children: [_jsx("span", { className: "crisp-inbox-empty-icon", children: _jsx(Bell, { size: 40, strokeWidth: 1.25 }) }), _jsx("div", { className: "crisp-inbox-empty-title", children: emptyTitle }), emptySubtitle != null && (_jsx("div", { className: "crisp-inbox-empty-sub", children: emptySubtitle })), resolvedEmptyAction != null && (_jsx("div", { className: "crisp-inbox-empty-action", children: resolvedEmptyAction }))] })) : (_jsx("div", { className: "crisp-inbox-list", children: notifications.map((n) => (_jsxs("button", { type: "button", className: "crisp-inbox-row", onClick: n.onClick, children: [n.avatar != null && (_jsx("span", { className: "crisp-inbox-row-avatar", children: n.avatar })), _jsxs("span", { className: "crisp-inbox-row-body", children: [_jsx("span", { className: "crisp-inbox-row-msg", children: n.message }), n.time != null && (_jsx("span", { className: "crisp-inbox-row-time", children: n.time }))] }), n.unread && _jsx("span", { className: "crisp-inbox-row-unread" })] }, n.id))) }))] }));
|
|
36
44
|
}
|
|
37
45
|
Inbox.displayName = "Inbox";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { InviteMembersDialog, MembersSettings, } from "./memberssettings.js";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Filter, MoreHorizontal, Search, User, UsersRound } from "lucide-react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Avatar } from "../../components/avatar/index.js";
|
|
5
|
+
import { Badge } from "../../components/badge/index.js";
|
|
6
|
+
import { Button } from "../../components/button/index.js";
|
|
7
|
+
import { Dialog, DialogBody, DialogFooter } from "../../components/dialog/index.js";
|
|
8
|
+
import { FormField } from "../../components/formfield/index.js";
|
|
9
|
+
import { Select } from "../../components/select/index.js";
|
|
10
|
+
import { useTablistKeyboard } from "../../components/tabs/tabs-keyboard.js";
|
|
11
|
+
import { Textarea } from "../../components/textarea/index.js";
|
|
12
|
+
import { TextLink } from "../../components/textlink/index.js";
|
|
13
|
+
import { memberssettingsRecipe } from "./memberssettings.recipe.js";
|
|
14
|
+
/**
|
|
15
|
+
* Attio's "Members and teams" settings page (crisp#636, P-119/P-120):
|
|
16
|
+
* title + description, a Members/Teams segmented switcher, a
|
|
17
|
+
* search+Filter+Invite toolbar, and the members table (User/Role/Teams).
|
|
18
|
+
* Compose `InviteMembersDialog` alongside for the "Invite members" flow.
|
|
19
|
+
*/
|
|
20
|
+
export function MembersSettings({ members, searchValue, onSearchChange, onFilterClick, onInviteClick, onRowMenuClick, tab: tabProp, onTabChange, teamsContent, className, title = "Members and teams", description = "Manage workspace members and teams, set access levels, and invite new users.", learnMoreLabel = "Learn more", learnMoreHref = "#", membersTabLabel = "Members", teamsTabLabel = "Teams", tablistLabel = "Members and teams", searchPlaceholder = "Search name or email", filterLabel = "Filter", inviteLabel = "Invite members", userColumnLabel = "User", roleColumnLabel = "Role", teamsColumnLabel = "Teams", memberActionsLabel = "Member actions", noTeamsLabel = "No teams yet", ...rest }) {
|
|
21
|
+
const [internalTab, setInternalTab] = React.useState("members");
|
|
22
|
+
const tab = tabProp ?? internalTab;
|
|
23
|
+
const setTab = (next) => {
|
|
24
|
+
if (tabProp === undefined)
|
|
25
|
+
setInternalTab(next);
|
|
26
|
+
onTabChange?.(next);
|
|
27
|
+
};
|
|
28
|
+
const listRef = React.useRef(null);
|
|
29
|
+
const tabEntries = [
|
|
30
|
+
{
|
|
31
|
+
value: "members",
|
|
32
|
+
label: membersTabLabel,
|
|
33
|
+
icon: _jsx(User, { size: 16 }),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
value: "teams",
|
|
37
|
+
label: teamsTabLabel,
|
|
38
|
+
icon: _jsx(UsersRound, { size: 16 }),
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
const { onKeyDown, tabIndexFor } = useTablistKeyboard({
|
|
42
|
+
tabs: tabEntries,
|
|
43
|
+
active: tab,
|
|
44
|
+
select: (value) => setTab(value),
|
|
45
|
+
listRef,
|
|
46
|
+
});
|
|
47
|
+
return (_jsx("div", { className: memberssettingsRecipe({ className }), ...rest, children: _jsxs("div", { className: "crisp-memberssettings-page", children: [_jsxs("div", { className: "crisp-memberssettings-heading", children: [_jsx("h1", { className: "crisp-memberssettings-title", children: title }), _jsxs("p", { className: "crisp-memberssettings-desc", children: [description, " ", _jsx(TextLink, { href: learnMoreHref, children: learnMoreLabel })] })] }), _jsx("div", { ref: listRef, className: "crisp-memberssettings-tabpair", role: "tablist", "aria-label": tablistLabel, onKeyDown: onKeyDown, children: tabEntries.map((t) => (_jsxs("button", { type: "button", role: "tab", "data-value": t.value, "aria-selected": tab === t.value, "data-selected": tab === t.value ? "" : undefined, tabIndex: tabIndexFor(t.value), className: "crisp-memberssettings-tab", onClick: () => setTab(t.value), children: [t.icon, t.label] }, t.value))) }), tab === "members" ? (_jsxs("div", { role: "tabpanel", "aria-label": membersTabLabel, children: [_jsxs("div", { className: "crisp-memberssettings-toolbar", children: [_jsxs("div", { className: "crisp-memberssettings-search", children: [_jsx(Search, { size: 16 }), _jsx("input", { value: searchValue, onChange: (e) => onSearchChange?.(e.target.value), placeholder: searchPlaceholder })] }), _jsxs("button", { type: "button", className: "crisp-memberssettings-filterbtn", onClick: onFilterClick, children: [_jsx(Filter, { size: 14 }), filterLabel] }), _jsx("button", { type: "button", className: "crisp-memberssettings-invitebtn", onClick: onInviteClick, children: inviteLabel })] }), _jsx("div", { className: "crisp-memberssettings-table-wrap", children: _jsxs("table", { className: "crisp-memberssettings-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: userColumnLabel }), _jsx("th", { children: roleColumnLabel }), _jsx("th", { children: teamsColumnLabel })] }) }), _jsx("tbody", { children: members.map((m) => (_jsxs("tr", { children: [_jsx("td", { children: _jsxs("div", { className: "crisp-memberssettings-user", children: [m.avatarSrc ? (_jsx("img", { className: "crisp-memberssettings-avatarimg", src: m.avatarSrc, alt: "" })) : (_jsx(Avatar, { size: "lg", shape: "circle", tone: "brand", children: String(m.name).charAt(0) })), _jsxs("div", { className: "crisp-memberssettings-usermeta", children: [_jsxs("span", { children: [m.name, m.isYou ? " (You)" : ""] }), _jsx("span", { className: "crisp-memberssettings-useremail", children: m.email })] })] }) }), _jsx("td", { children: m.role === "Admin" ? (_jsx(Badge, { tone: "purple", children: m.role })) : (m.role) }), _jsxs("td", { children: [m.teams ?? "—", _jsx("button", { type: "button", className: "crisp-memberssettings-kebab", "aria-label": memberActionsLabel, onClick: () => onRowMenuClick?.(m), children: _jsx(MoreHorizontal, { size: 16 }) })] })] }, m.id))) })] }) })] })) : (_jsx("div", { role: "tabpanel", "aria-label": teamsTabLabel, children: teamsContent ?? (_jsx("div", { className: "crisp-memberssettings-teamspanel", children: noTeamsLabel })) }))] }) }));
|
|
48
|
+
}
|
|
49
|
+
MembersSettings.displayName = "MembersSettings";
|
|
50
|
+
/**
|
|
51
|
+
* Attio's "Invite members" modal (crisp#636, P-120), measured live: 708px
|
|
52
|
+
* wide, a multi-email composer, an "Invite as" select (default Member) and
|
|
53
|
+
* an optional "Add to team" search-or-create select, footer "Send Invites".
|
|
54
|
+
*/
|
|
55
|
+
export function InviteMembersDialog({ open, onOpenChange, emails, onEmailsChange, inviteAs, onInviteAsChange, team, onTeamChange, teamOptions = [], onSend, title = "Invite members", emailsPlaceholder = "Send Invite to ...", inviteAsFieldLabel = "Invite as", memberOptionLabel = "Member — can edit", adminOptionLabel = "Admin — full access", addToTeamFieldLabel = "Add to team", addToTeamPlaceholder = "Search or create a team", sendLabel = "Send Invites ⌘⏎", ...rest }) {
|
|
56
|
+
const inviteAsOptions = [
|
|
57
|
+
{ value: "member", label: memberOptionLabel },
|
|
58
|
+
{ value: "admin", label: adminOptionLabel },
|
|
59
|
+
];
|
|
60
|
+
return (_jsxs(Dialog, { open: open, onOpenChange: onOpenChange, title: title, width: 708, ...rest, children: [_jsxs(DialogBody, { children: [_jsx("div", { className: "crisp-memberssettings-invitefield", children: _jsx(Textarea, { value: emails, onChange: (e) => onEmailsChange(e.target.value), placeholder: emailsPlaceholder, rows: 3 }) }), _jsx(FormField, { label: inviteAsFieldLabel, children: _jsx(Select, { options: inviteAsOptions, value: inviteAs, onValueChange: (v) => onInviteAsChange(v) }) }), _jsx(FormField, { label: addToTeamFieldLabel, optional: true, children: _jsx(Select, { options: teamOptions, value: team, onValueChange: onTeamChange, placeholder: addToTeamPlaceholder, searchable: true }) })] }), _jsx(DialogFooter, { children: _jsx(Button, { intent: "primary", onClick: onSend, children: sendLabel }) })] }));
|
|
61
|
+
}
|
|
62
|
+
InviteMembersDialog.displayName = "InviteMembersDialog";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { defineRecipe, resolveRecipe } from "../../core/index.js";
|
|
2
|
+
import { contract as c } from "../../tokens/index.js";
|
|
3
|
+
// Measured live against app.attio.com/<workspace>/settings/members-and-teams
|
|
4
|
+
// (crisp#636, P-119/P-120). The Members/Teams switcher is a plain segmented
|
|
5
|
+
// pill toggle -- NOT the shared `Tabs` primitive, which always reserves an
|
|
6
|
+
// 8px baseline strip and draws an ink underline (crisp#749's own tabs
|
|
7
|
+
// recipe comment): this page's tab container measured a clean 28px, no
|
|
8
|
+
// baseline, no underline, bg transparent. Reusing `Tabs` here would paint a
|
|
9
|
+
// baseline Attio's own switcher doesn't have.
|
|
10
|
+
const config = defineRecipe({
|
|
11
|
+
name: "memberssettings",
|
|
12
|
+
base: `& .crisp-memberssettings-page { display: flex; flex-direction: column; gap: ${c.space["6"]}; font-family: ${c.font.sans}; }
|
|
13
|
+
& .crisp-memberssettings-heading { display: flex; flex-direction: column; gap: ${c.space["0.5"]}; }
|
|
14
|
+
& .crisp-memberssettings-title { font-size: 24px; line-height: 28px; font-weight: ${c.weight.semibold}; letter-spacing: -0.48px; color: ${c.fgPrimary}; margin: 0; }
|
|
15
|
+
& .crisp-memberssettings-desc { font-size: ${c.text.lg}; line-height: 20px; color: ${c.fgMuted}; margin: 0; }
|
|
16
|
+
|
|
17
|
+
/* Segmented Members/Teams switcher -- measured: 28px pills, radius 8,
|
|
18
|
+
4px gap, 6px icon-label gap, 14/500 label, selected bg bgSubtle +
|
|
19
|
+
inset borderSubtle ring, unselected transparent + a flat 63%-opacity
|
|
20
|
+
fgPrimary mix (measured rgba(0,0,0,.63) on the live page, not the
|
|
21
|
+
solid fgMuted token — same alpha technique this file's table header
|
|
22
|
+
already uses). */
|
|
23
|
+
& .crisp-memberssettings-tabpair { display: inline-flex; align-items: center; gap: 4px; }
|
|
24
|
+
& .crisp-memberssettings-tab { display: inline-flex; align-items: center; gap: ${c.space["1.5"]}; height: 28px; padding: 0 ${c.space["2"]}; border: none; border-radius: ${c.radius.md}; background: none; box-shadow: inset 0 0 0 1px transparent; font-family: inherit; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); cursor: pointer; white-space: nowrap; transition: color .12s ease, background-color .12s ease, box-shadow .12s ease; }
|
|
25
|
+
& .crisp-memberssettings-tab svg { flex-shrink: 0; }
|
|
26
|
+
& .crisp-memberssettings-tab:hover:not([data-selected]) { color: ${c.fgPrimary}; }
|
|
27
|
+
& .crisp-memberssettings-tab[data-selected] { color: ${c.fgPrimary}; background: ${c.bgSubtle}; box-shadow: inset 0 0 0 1px ${c.borderSubtle}; }
|
|
28
|
+
& .crisp-memberssettings-tab:focus-visible { outline: 2px solid ${c.bgBrandSolid}; outline-offset: 1px; }
|
|
29
|
+
|
|
30
|
+
/* Toolbar row: search grows, Filter + Invite sit trailing -- measured
|
|
31
|
+
32px height on all three, search radius 9 with a 6% ink ring,
|
|
32
|
+
Filter/Invite radius 9 with the button drop-shadow pairing. */
|
|
33
|
+
& .crisp-memberssettings-toolbar { display: flex; align-items: center; gap: ${c.space["2"]}; }
|
|
34
|
+
& .crisp-memberssettings-search { display: flex; align-items: center; flex: 1; min-width: 0; gap: ${c.space["1.5"]}; height: 32px; padding: 0 ${c.space["2.5"]} 0 ${c.space["2"]}; border-radius: 9px; background: ${c.bgSurface}; box-shadow: inset 0 0 0 1px color-mix(in srgb, ${c.fgPrimary} 6%, transparent); color: ${c.fgMuted}; }
|
|
35
|
+
& .crisp-memberssettings-search:focus-within { box-shadow: inset 0 0 0 1px ${c.bgBrandSolid}; }
|
|
36
|
+
& .crisp-memberssettings-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: ${c.fgPrimary}; }
|
|
37
|
+
& .crisp-memberssettings-search input::placeholder { color: ${c.fgMuted}; }
|
|
38
|
+
& .crisp-memberssettings-filterbtn { display: inline-flex; align-items: center; gap: ${c.space["1.5"]}; height: 32px; padding: 6px 10px 6px 8px; border: none; border-radius: 9px; background: ${c.bgSurface}; box-shadow: inset 0 0 0 1px transparent, 0 0 2px 0 rgba(28,40,64,.18), 0 1px 3px 0 rgba(0,0,0,.04); color: ${c.fgPrimary}; font-family: inherit; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; cursor: pointer; white-space: nowrap; }
|
|
39
|
+
& .crisp-memberssettings-filterbtn:hover { background: ${c.bgRaised}; }
|
|
40
|
+
/* your-moon/crisp#979: bgBrandContrast, not bgBrandSolid -- the accent
|
|
41
|
+
(#0298d9) only clears 3.23:1 with white; the darker sibling clears
|
|
42
|
+
4.5:1+, same fix already applied to Button/Badge/Avatar (#897) and
|
|
43
|
+
TableToolbar/Dialog/Command/Comments/AuthShell (#953/#961), which
|
|
44
|
+
explicitly flagged this button as found-not-fixed. */
|
|
45
|
+
& .crisp-memberssettings-invitebtn { display: inline-flex; align-items: center; gap: ${c.space["1.5"]}; height: 32px; padding: 6px 10px 6px 8px; border: none; border-radius: 9px; background: ${c.bgBrandContrast}; box-shadow: inset 0 0 0 1px rgba(0,0,0,.06), 0 2px 4px -2px color-mix(in srgb, ${c.bgBrandContrast} 12%, transparent), 0 3px 6px -2px color-mix(in srgb, ${c.bgBrandContrast} 8%, transparent); color: ${c.fgOnBrand}; font-family: inherit; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; cursor: pointer; white-space: nowrap; }
|
|
46
|
+
& .crisp-memberssettings-invitebtn:hover { filter: brightness(0.95); }
|
|
47
|
+
|
|
48
|
+
/* Table wrapper -- measured card: radius 12, 1px borderSubtle, 40px
|
|
49
|
+
rows, header label 12/500/63%-opacity fgPrimary. */
|
|
50
|
+
& .crisp-memberssettings-table-wrap { border-radius: 12px; border: 1px solid ${c.borderSubtle}; overflow: hidden; }
|
|
51
|
+
& .crisp-memberssettings-table { width: 100%; border-collapse: collapse; font-family: inherit; }
|
|
52
|
+
& .crisp-memberssettings-table th { height: 32px; padding: 0 ${c.space["4"]}; text-align: start; font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); border-bottom: 1px solid ${c.borderSubtle}; background: ${c.bgSurface}; }
|
|
53
|
+
& .crisp-memberssettings-table td { height: 40px; padding: 0 ${c.space["4"]}; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: ${c.fgPrimary}; border-bottom: 1px solid ${c.borderSubtle}; }
|
|
54
|
+
& .crisp-memberssettings-table tr:last-child td { border-bottom: none; }
|
|
55
|
+
& .crisp-memberssettings-user { display: flex; align-items: center; gap: ${c.space["2"]}; }
|
|
56
|
+
& .crisp-memberssettings-avatarimg { width: 24px; height: 24px; border-radius: ${c.radius.full}; object-fit: cover; }
|
|
57
|
+
/* Name + email sit on ONE line, not stacked -- confirmed live: both spans
|
|
58
|
+
share the same y and the same 14px size, only the email's colour
|
|
59
|
+
differs (a flat 63%-opacity fgPrimary mix, not a smaller muted font). */
|
|
60
|
+
& .crisp-memberssettings-usermeta { display: flex; align-items: baseline; gap: ${c.space["2"]}; min-width: 0; }
|
|
61
|
+
& .crisp-memberssettings-usermeta > span:first-child { flex-shrink: 0; }
|
|
62
|
+
& .crisp-memberssettings-useremail { font-size: ${c.text.lg}; font-weight: ${c.weight.regular}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
63
|
+
& .crisp-memberssettings-kebab { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-inline-start: auto; border: none; border-radius: ${c.radius.sm}; background: none; font-family: inherit; color: ${c.fgMuted}; cursor: pointer; }
|
|
64
|
+
& .crisp-memberssettings-kebab:hover { background: ${c.bgRaised}; color: ${c.fgPrimary}; }
|
|
65
|
+
|
|
66
|
+
& .crisp-memberssettings-teamspanel { display: flex; align-items: center; justify-content: center; padding: ${c.space["8"]} 0; color: ${c.fgMuted}; font-size: ${c.text.lg}; }
|
|
67
|
+
|
|
68
|
+
/* Invite dialog fields -- measured 708x386 dialog, radius 16; the
|
|
69
|
+
"Invite as"/"Add to team" selects are 32/34px with a flat 1px
|
|
70
|
+
6%-ink border and 9/10px radius respectively (Attio's own two
|
|
71
|
+
selects don't share one radius). */
|
|
72
|
+
& .crisp-memberssettings-invitefield { display: flex; flex-direction: column; gap: ${c.space["1.5"]}; }
|
|
73
|
+
& .crisp-memberssettings-invitelabel { font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: ${c.fgMuted}; }
|
|
74
|
+
`,
|
|
75
|
+
variants: {},
|
|
76
|
+
defaultVariants: {},
|
|
77
|
+
compoundVariants: [],
|
|
78
|
+
});
|
|
79
|
+
export const memberssettingsRecipe = resolveRecipe(config);
|
|
80
|
+
export default config;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NotificationsSettings, } from "./notificationssettings.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Bell, Mail, MonitorSmartphone } from "lucide-react";
|
|
3
|
+
import { Checkbox } from "../../components/checkbox/index.js";
|
|
4
|
+
import { Switch } from "../../components/switch/index.js";
|
|
5
|
+
import { TextLink } from "../../components/textlink/index.js";
|
|
6
|
+
import { notificationssettingsRecipe } from "./notificationssettings.recipe.js";
|
|
7
|
+
/**
|
|
8
|
+
* Attio's "Notifications" settings page (crisp#630, P-083): title +
|
|
9
|
+
* description, a "Daily digest" card (a single toggle row), and a
|
|
10
|
+
* "Collaboration notifications" matrix — a header row plus one row per
|
|
11
|
+
* event, each with an Email and an App checkbox. Measured live.
|
|
12
|
+
*/
|
|
13
|
+
export function NotificationsSettings({ dailyDigestEnabled, onDailyDigestChange, rows, onEmailChange, onAppChange, className, title = "Notifications", description = "Customize your notification settings to stay informed without being overwhelmed.", learnMoreLabel = "Learn more", learnMoreHref = "#", dailyDigestSectionTitle = "Daily digest", dailyDigestRowTitle = "Enable daily digest", dailyDigestRowDescription = "Includes tasks overdue and due today. Sent every morning if any tasks are due or overdue.", collaborationSectionTitle = "Collaboration notifications", notifyMeAboutLabel = "Notify me about", emailColumnLabel = "Email", appColumnLabel = "App", ...rest }) {
|
|
14
|
+
return (_jsx("div", { className: notificationssettingsRecipe({ className }), ...rest, children: _jsxs("div", { className: "crisp-notificationssettings-page", children: [_jsxs("div", { className: "crisp-notificationssettings-heading", children: [_jsx("h1", { className: "crisp-notificationssettings-title", children: title }), _jsxs("p", { className: "crisp-notificationssettings-desc", children: [description, " ", _jsx(TextLink, { href: learnMoreHref, children: learnMoreLabel })] })] }), _jsxs("div", { className: "crisp-notificationssettings-section", children: [_jsx("h2", { className: "crisp-notificationssettings-sectiontitle", children: dailyDigestSectionTitle }), _jsxs("div", { className: "crisp-notificationssettings-digestcard", children: [_jsxs("div", { className: "crisp-notificationssettings-digesttext", children: [_jsx("span", { className: "crisp-notificationssettings-rowtitle", children: dailyDigestRowTitle }), _jsx("span", { className: "crisp-notificationssettings-rowdesc", children: dailyDigestRowDescription })] }), _jsx(Switch, { checked: dailyDigestEnabled, onCheckedChange: onDailyDigestChange, "aria-label": dailyDigestRowTitle })] })] }), _jsxs("div", { className: "crisp-notificationssettings-section", children: [_jsx("h2", { className: "crisp-notificationssettings-sectiontitle", children: collaborationSectionTitle }), _jsxs("div", { className: "crisp-notificationssettings-matrix", children: [_jsxs("div", { className: "crisp-notificationssettings-matrixhead", children: [_jsxs("span", { className: "crisp-notificationssettings-matrixhead-about", children: [_jsx(Bell, { size: 14 }), notifyMeAboutLabel] }), _jsxs("span", { className: "crisp-notificationssettings-matrixhead-col", children: [_jsx(Mail, { size: 14 }), emailColumnLabel] }), _jsxs("span", { className: "crisp-notificationssettings-matrixhead-col", children: [_jsx(MonitorSmartphone, { size: 14 }), appColumnLabel] })] }), rows.map((row) => (_jsxs("div", { className: "crisp-notificationssettings-matrixrow", children: [_jsxs("div", { className: "crisp-notificationssettings-matrixrow-about", children: [_jsx("span", { className: "crisp-notificationssettings-rowtitle", children: row.title }), _jsx("span", { className: "crisp-notificationssettings-rowdesc", children: row.description })] }), _jsx("div", { className: "crisp-notificationssettings-matrixrow-col", children: _jsx(Checkbox, { checked: row.email, onChange: (e) => onEmailChange(row.id, e.target.checked), "aria-label": `${emailColumnLabel}: ${typeof row.title === "string" ? row.title : row.id}` }) }), _jsx("div", { className: "crisp-notificationssettings-matrixrow-col", children: _jsx(Checkbox, { checked: row.app, onChange: (e) => onAppChange(row.id, e.target.checked), "aria-label": `${appColumnLabel}: ${typeof row.title === "string" ? row.title : row.id}` }) })] }, row.id)))] })] })] }) }));
|
|
15
|
+
}
|
|
16
|
+
NotificationsSettings.displayName = "NotificationsSettings";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { defineRecipe, resolveRecipe } from "../../core/index.js";
|
|
2
|
+
import { contract as c } from "../../tokens/index.js";
|
|
3
|
+
// Measured live against app.attio.com/<workspace>/settings/notifications
|
|
4
|
+
// (crisp#630, P-083). Two sections, each a 16/600 heading over a bordered
|
|
5
|
+
// 12px-radius card: "Daily digest" (a single 56px row, label 14/500 +
|
|
6
|
+
// description 12/500/63%-opacity, a Switch trailing) and "Collaboration
|
|
7
|
+
// notifications" (a header row -- 12/500/63%-opacity labels -- then 7 rows,
|
|
8
|
+
// each 52px, a title/description pair on the left and two Checkboxes,
|
|
9
|
+
// right-aligned under Email/App, per row).
|
|
10
|
+
const config = defineRecipe({
|
|
11
|
+
name: "notificationssettings",
|
|
12
|
+
base: `& .crisp-notificationssettings-page { display: flex; flex-direction: column; gap: ${c.space["6"]}; font-family: ${c.font.sans}; }
|
|
13
|
+
& .crisp-notificationssettings-heading { display: flex; flex-direction: column; gap: ${c.space["0.5"]}; }
|
|
14
|
+
& .crisp-notificationssettings-title { font-size: 24px; line-height: 28px; font-weight: ${c.weight.semibold}; letter-spacing: -0.48px; color: ${c.fgPrimary}; margin: 0; }
|
|
15
|
+
& .crisp-notificationssettings-desc { font-size: ${c.text.lg}; line-height: 20px; color: ${c.fgMuted}; margin: 0; }
|
|
16
|
+
|
|
17
|
+
& .crisp-notificationssettings-section { display: flex; flex-direction: column; gap: ${c.space["4"]}; }
|
|
18
|
+
& .crisp-notificationssettings-sectiontitle { font-size: 16px; line-height: 20px; font-weight: ${c.weight.semibold}; color: ${c.fgPrimary}; margin: 0; }
|
|
19
|
+
|
|
20
|
+
/* Daily digest card: one bordered 12px-radius row. */
|
|
21
|
+
& .crisp-notificationssettings-digestcard { display: flex; align-items: center; justify-content: space-between; gap: ${c.space["6"]}; padding: ${c.space["2"]} ${c.space["3"]}; border: 1px solid ${c.borderSubtle}; border-radius: 12px; background: ${c.bgSurface}; }
|
|
22
|
+
& .crisp-notificationssettings-digesttext { display: flex; flex-direction: column; gap: ${c.space["1"]}; }
|
|
23
|
+
& .crisp-notificationssettings-rowtitle { font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: ${c.fgPrimary}; }
|
|
24
|
+
& .crisp-notificationssettings-rowdesc { font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); }
|
|
25
|
+
|
|
26
|
+
/* Collaboration matrix: a bordered card, header row + N body rows. */
|
|
27
|
+
& .crisp-notificationssettings-matrix { border: 1px solid ${c.borderSubtle}; border-radius: 12px; overflow: hidden; background: ${c.bgSurface}; }
|
|
28
|
+
& .crisp-notificationssettings-matrixhead { display: flex; align-items: center; gap: ${c.space["3"]}; padding: 0 ${c.space["4"]}; height: 40px; border-bottom: 1px solid ${c.borderSubtle}; }
|
|
29
|
+
& .crisp-notificationssettings-matrixhead-about { display: flex; align-items: center; gap: ${c.space["1.5"]}; flex: 1; min-width: 0; font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); }
|
|
30
|
+
& .crisp-notificationssettings-matrixhead-col { display: flex; align-items: center; justify-content: center; gap: ${c.space["1.5"]}; width: 64px; flex-shrink: 0; font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); }
|
|
31
|
+
& .crisp-notificationssettings-matrixrow { display: flex; align-items: center; gap: ${c.space["3"]}; padding: ${c.space["2"]} ${c.space["4"]}; min-height: 52px; }
|
|
32
|
+
& .crisp-notificationssettings-matrixrow + .crisp-notificationssettings-matrixrow { border-top: 1px solid ${c.borderSubtle}; }
|
|
33
|
+
& .crisp-notificationssettings-matrixrow-about { display: flex; flex-direction: column; gap: ${c.space["1"]}; flex: 1; min-width: 0; }
|
|
34
|
+
& .crisp-notificationssettings-matrixrow-col { display: flex; align-items: center; justify-content: center; width: 64px; flex-shrink: 0; }
|
|
35
|
+
`,
|
|
36
|
+
variants: {},
|
|
37
|
+
defaultVariants: {},
|
|
38
|
+
compoundVariants: [],
|
|
39
|
+
});
|
|
40
|
+
export const notificationssettingsRecipe = resolveRecipe(config);
|
|
41
|
+
export default config;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { tagTone, } from "./column.js";
|
|
2
2
|
export { editableInputType, useEditableField, } from "./editable-field.js";
|
|
3
|
-
export { RecordDetails, RecordPage, recordPageLabels, } from "./recordpage.js";
|
|
3
|
+
export { RecordDetails, RecordPage, RecordPageMain, recordPageLabels, } from "./recordpage.js";
|
|
4
4
|
export { recordpageRecipe } from "./recordpage.recipe.js";
|