@refraction-ui/astro 0.11.1 → 0.12.0
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/astro-empty-state/EmptyState.astro +57 -0
- package/dist/astro-empty-state/index.ts +1 -0
- package/dist/astro-input/Input.astro +5 -1
- package/dist/astro-password-input/PasswordInput.astro +102 -0
- package/dist/astro-password-input/index.ts +1 -0
- package/dist/astro-segmented-control/SegmentedControl.astro +106 -0
- package/dist/astro-segmented-control/SegmentedControlItem.astro +32 -0
- package/dist/astro-segmented-control/index.ts +1 -0
- package/dist/astro-separator/Separator.astro +49 -0
- package/dist/astro-separator/index.ts +1 -0
- package/dist/astro-social-auth-button/SocialAuthButton.astro +110 -0
- package/dist/astro-social-auth-button/SocialAuthRow.astro +13 -0
- package/dist/astro-social-auth-button/index.ts +1 -0
- package/dist/empty-state/empty-state.styles.ts +42 -0
- package/dist/empty-state/empty-state.ts +27 -0
- package/dist/empty-state/index.ts +14 -0
- package/dist/index.ts +22 -17
- package/dist/input/index.ts +2 -0
- package/dist/input/input.styles.ts +4 -0
- package/dist/input/input.ts +39 -3
- package/dist/password-input/index.ts +9 -0
- package/dist/password-input/password-input.styles.ts +10 -0
- package/dist/password-input/password-input.ts +45 -0
- package/dist/segmented-control/index.ts +13 -0
- package/dist/segmented-control/segmented-control.styles.ts +42 -0
- package/dist/segmented-control/segmented-control.ts +80 -0
- package/dist/separator/index.ts +12 -0
- package/dist/separator/separator.styles.ts +34 -0
- package/dist/separator/separator.ts +53 -0
- package/dist/social-auth-button/index.ts +14 -0
- package/dist/social-auth-button/social-auth-button.styles.ts +16 -0
- package/dist/social-auth-button/social-auth-button.ts +51 -0
- package/package.json +24 -19
package/dist/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export * from './astro-accordion/index.ts';
|
|
3
3
|
export * from './shared/index.ts';
|
|
4
4
|
export * from './astro-ai/index.ts';
|
|
5
|
+
export * from './astro-analytics/index.ts';
|
|
5
6
|
export * from './astro-animated-text/index.ts';
|
|
6
7
|
export * from './astro-app-shell/index.ts';
|
|
7
8
|
export * from './astro-auth/index.ts';
|
|
@@ -12,14 +13,19 @@ export * from './astro-bottom-nav/index.ts';
|
|
|
12
13
|
export * from './astro-breadcrumbs/index.ts';
|
|
13
14
|
export * from './astro-button/index.ts';
|
|
14
15
|
export * from './astro-calendar/index.ts';
|
|
16
|
+
export * from './astro-callout/index.ts';
|
|
15
17
|
export * from './astro-card/index.ts';
|
|
18
|
+
export * from './astro-card-grid/index.ts';
|
|
19
|
+
export * from './astro-carousel/index.ts';
|
|
16
20
|
export * from './astro-charts/index.ts';
|
|
17
21
|
export * from './astro-checkbox/index.ts';
|
|
22
|
+
export * from './astro-code-block/index.ts';
|
|
18
23
|
export * from './astro-code-editor/index.ts';
|
|
19
24
|
export * from './astro-collapsible/index.ts';
|
|
20
25
|
export * from './astro-command/index.ts';
|
|
21
|
-
export * from './astro-
|
|
26
|
+
export * from './astro-command-input/index.ts';
|
|
22
27
|
export * from './astro-content-protection/index.ts';
|
|
28
|
+
export * from './astro-conversation/index.ts';
|
|
23
29
|
export * from './astro-cookie-consent/index.ts';
|
|
24
30
|
export * from './astro-data-table/index.ts';
|
|
25
31
|
export * from './astro-date-picker/index.ts';
|
|
@@ -28,19 +34,27 @@ export * from './astro-dialog/index.ts';
|
|
|
28
34
|
export * from './astro-diff-viewer/index.ts';
|
|
29
35
|
export * from './astro-dropdown-menu/index.ts';
|
|
30
36
|
export * from './astro-emoji-picker/index.ts';
|
|
37
|
+
export * from './astro-empty-state/index.ts';
|
|
31
38
|
export * from './astro-feedback-dialog/index.ts';
|
|
39
|
+
export * from './astro-file-tree/index.ts';
|
|
32
40
|
export * from './astro-file-upload/index.ts';
|
|
33
41
|
export * from './astro-footer/index.ts';
|
|
42
|
+
export * from './astro-icon-system/index.ts';
|
|
34
43
|
export * from './astro-inline-editor/index.ts';
|
|
35
44
|
export * from './astro-input/index.ts';
|
|
36
45
|
export * from './astro-input-group/index.ts';
|
|
37
46
|
export * from './astro-install-prompt/index.ts';
|
|
38
47
|
export * from './astro-keyboard-shortcut/index.ts';
|
|
39
48
|
export * from './astro-language-selector/index.ts';
|
|
49
|
+
export * from './astro-link-card/index.ts';
|
|
50
|
+
export * from './astro-logger/index.ts';
|
|
40
51
|
export * from './astro-markdown-renderer/index.ts';
|
|
41
52
|
export * from './astro-mobile-nav/index.ts';
|
|
42
53
|
export * from './astro-navbar/index.ts';
|
|
43
54
|
export * from './astro-otp-input/index.ts';
|
|
55
|
+
export * from './astro-pagination/index.ts';
|
|
56
|
+
export * from './astro-password-input/index.ts';
|
|
57
|
+
export * from './astro-payment/index.ts';
|
|
44
58
|
export * from './astro-popover/index.ts';
|
|
45
59
|
export * from './astro-presence-indicator/index.ts';
|
|
46
60
|
export * from './astro-progress-display/index.ts';
|
|
@@ -49,12 +63,19 @@ export * from './astro-reaction-bar/index.ts';
|
|
|
49
63
|
export * from './astro-resizable-layout/index.ts';
|
|
50
64
|
export * from './astro-rich-editor/index.ts';
|
|
51
65
|
export * from './astro-search-bar/index.ts';
|
|
66
|
+
export * from './astro-segmented-control/index.ts';
|
|
52
67
|
export * from './astro-select/index.ts';
|
|
68
|
+
export * from './astro-separator/index.ts';
|
|
53
69
|
export * from './astro-sidebar/index.ts';
|
|
54
70
|
export * from './astro-skeleton/index.ts';
|
|
71
|
+
export * from './astro-skip-to-content/index.ts';
|
|
55
72
|
export * from './astro-slide-viewer/index.ts';
|
|
73
|
+
export * from './astro-slider/index.ts';
|
|
74
|
+
export * from './astro-social-auth-button/index.ts';
|
|
56
75
|
export * from './astro-status-indicator/index.ts';
|
|
76
|
+
export * from './astro-steps/index.ts';
|
|
57
77
|
export * from './astro-switch/index.ts';
|
|
78
|
+
export * from './astro-table-of-contents/index.ts';
|
|
58
79
|
export * from './astro-tabs/index.ts';
|
|
59
80
|
export * from './astro-textarea/index.ts';
|
|
60
81
|
export * from './astro-theme/index.ts';
|
|
@@ -65,19 +86,3 @@ export * from './astro-version-selector/index.ts';
|
|
|
65
86
|
export * from './astro-video-player/index.ts';
|
|
66
87
|
export * from './astro-voice-pill/index.ts';
|
|
67
88
|
export * from './astro-waveform/index.ts';
|
|
68
|
-
export * from './astro-table-of-contents/index.ts';
|
|
69
|
-
export * from './astro-carousel/index.ts';
|
|
70
|
-
export * from './astro-slider/index.ts';
|
|
71
|
-
export * from './astro-pagination/index.ts';
|
|
72
|
-
export * from './astro-callout/index.ts';
|
|
73
|
-
export * from './astro-steps/index.ts';
|
|
74
|
-
export * from './astro-file-tree/index.ts';
|
|
75
|
-
export * from './astro-icon-system/index.ts';
|
|
76
|
-
export * from './astro-skip-to-content/index.ts';
|
|
77
|
-
export * from './astro-code-block/index.ts';
|
|
78
|
-
export * from './astro-link-card/index.ts';
|
|
79
|
-
export * from './astro-card-grid/index.ts';
|
|
80
|
-
export * from './astro-payment/index.ts';
|
|
81
|
-
export * from './astro-command-input/index.ts';
|
|
82
|
-
export * from './astro-logger/index.ts';
|
|
83
|
-
export * from './astro-analytics/index.ts';
|
package/dist/input/index.ts
CHANGED
|
@@ -8,6 +8,10 @@ export const inputVariants = cva({
|
|
|
8
8
|
default: 'h-9',
|
|
9
9
|
lg: 'h-10 text-base',
|
|
10
10
|
},
|
|
11
|
+
validationState: {
|
|
12
|
+
valid: 'border-green-500 focus-visible:ring-green-500',
|
|
13
|
+
invalid: 'border-destructive focus-visible:ring-destructive',
|
|
14
|
+
},
|
|
11
15
|
},
|
|
12
16
|
defaultVariants: {
|
|
13
17
|
size: 'default',
|
package/dist/input/input.ts
CHANGED
|
@@ -2,6 +2,9 @@ import type { AccessibilityProps } from '../shared/index.ts'
|
|
|
2
2
|
|
|
3
3
|
export type InputType = 'text' | 'password' | 'email' | 'number' | 'tel' | 'url' | 'search'
|
|
4
4
|
|
|
5
|
+
/** High-level validation affordance for an input. */
|
|
6
|
+
export type InputValidationState = 'valid' | 'invalid'
|
|
7
|
+
|
|
5
8
|
export interface InputProps {
|
|
6
9
|
type?: InputType
|
|
7
10
|
disabled?: boolean
|
|
@@ -9,6 +12,14 @@ export interface InputProps {
|
|
|
9
12
|
required?: boolean
|
|
10
13
|
placeholder?: string
|
|
11
14
|
'aria-invalid'?: boolean
|
|
15
|
+
/**
|
|
16
|
+
* Validation affordance. The core derives `aria-invalid` from this:
|
|
17
|
+
* - `'valid'` → `aria-invalid={false}`
|
|
18
|
+
* - `'invalid'` → `aria-invalid={true}`
|
|
19
|
+
*
|
|
20
|
+
* An explicit `aria-invalid` prop always takes precedence.
|
|
21
|
+
*/
|
|
22
|
+
validationState?: InputValidationState
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
export interface InputAPI {
|
|
@@ -18,22 +29,47 @@ export interface InputAPI {
|
|
|
18
29
|
dataAttributes: Record<string, string>
|
|
19
30
|
}
|
|
20
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the effective `aria-invalid` value from an explicit `aria-invalid`
|
|
34
|
+
* prop and a high-level `validationState`. The explicit prop always wins;
|
|
35
|
+
* otherwise `validationState` maps `'invalid' → true`, `'valid' → false`, and
|
|
36
|
+
* an absent state leaves `aria-invalid` undefined (legacy behavior).
|
|
37
|
+
*/
|
|
38
|
+
export function resolveAriaInvalid(
|
|
39
|
+
ariaInvalid: boolean | undefined,
|
|
40
|
+
validationState: InputValidationState | undefined,
|
|
41
|
+
): boolean | undefined {
|
|
42
|
+
if (ariaInvalid !== undefined) {
|
|
43
|
+
return ariaInvalid
|
|
44
|
+
}
|
|
45
|
+
if (validationState === 'invalid') {
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
if (validationState === 'valid') {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
return undefined
|
|
52
|
+
}
|
|
53
|
+
|
|
21
54
|
export function createInput(props: InputProps = {}): InputAPI {
|
|
22
55
|
const {
|
|
23
56
|
disabled = false,
|
|
24
57
|
readOnly = false,
|
|
25
58
|
required = false,
|
|
26
59
|
'aria-invalid': ariaInvalid,
|
|
60
|
+
validationState,
|
|
27
61
|
} = props
|
|
28
62
|
|
|
63
|
+
const resolvedInvalid = resolveAriaInvalid(ariaInvalid, validationState)
|
|
64
|
+
|
|
29
65
|
const ariaProps: InputAPI['ariaProps'] = {}
|
|
30
66
|
|
|
31
67
|
if (disabled) {
|
|
32
68
|
ariaProps['aria-disabled'] = true
|
|
33
69
|
}
|
|
34
70
|
|
|
35
|
-
if (
|
|
36
|
-
ariaProps['aria-invalid'] =
|
|
71
|
+
if (resolvedInvalid !== undefined) {
|
|
72
|
+
ariaProps['aria-invalid'] = resolvedInvalid
|
|
37
73
|
}
|
|
38
74
|
|
|
39
75
|
if (required) {
|
|
@@ -50,7 +86,7 @@ export function createInput(props: InputProps = {}): InputAPI {
|
|
|
50
86
|
dataAttributes['data-readonly'] = ''
|
|
51
87
|
}
|
|
52
88
|
|
|
53
|
-
if (
|
|
89
|
+
if (resolvedInvalid === true) {
|
|
54
90
|
dataAttributes['data-invalid'] = ''
|
|
55
91
|
}
|
|
56
92
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Positioning classes for the reveal/hide toggle button rendered inside a
|
|
5
|
+
* password input's trailing padding. The wrapper must be `relative` and the
|
|
6
|
+
* input must reserve trailing space (e.g. `pr-10`).
|
|
7
|
+
*/
|
|
8
|
+
export const passwordToggleVariants = cva({
|
|
9
|
+
base: 'absolute right-2 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
10
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface PasswordInputProps {
|
|
2
|
+
/** Whether the password is currently revealed (shown as plain text). */
|
|
3
|
+
revealed?: boolean
|
|
4
|
+
/**
|
|
5
|
+
* Accessible label for the reveal (show password) action.
|
|
6
|
+
* @default 'Show password'
|
|
7
|
+
*/
|
|
8
|
+
revealLabel?: string
|
|
9
|
+
/**
|
|
10
|
+
* Accessible label for the hide action.
|
|
11
|
+
* @default 'Hide password'
|
|
12
|
+
*/
|
|
13
|
+
hideLabel?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PasswordInputAPI {
|
|
17
|
+
/** The `type` attribute for the underlying input given the revealed state. */
|
|
18
|
+
inputType: 'text' | 'password'
|
|
19
|
+
/** The accessible label for the toggle button given the revealed state. */
|
|
20
|
+
toggleLabel: string
|
|
21
|
+
/** The `aria-pressed` value for the toggle button. */
|
|
22
|
+
togglePressed: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const DEFAULT_REVEAL_LABEL = 'Show password'
|
|
26
|
+
export const DEFAULT_HIDE_LABEL = 'Hide password'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* State-free password-input helper. Given a `revealed` flag (owned by the
|
|
30
|
+
* framework adapter), it returns the input `type`, the toggle button's
|
|
31
|
+
* accessible label, and its `aria-pressed` value. It does not hold state.
|
|
32
|
+
*/
|
|
33
|
+
export function createPasswordInput(props: PasswordInputProps = {}): PasswordInputAPI {
|
|
34
|
+
const {
|
|
35
|
+
revealed = false,
|
|
36
|
+
revealLabel = DEFAULT_REVEAL_LABEL,
|
|
37
|
+
hideLabel = DEFAULT_HIDE_LABEL,
|
|
38
|
+
} = props
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
inputType: revealed ? 'text' : 'password',
|
|
42
|
+
toggleLabel: revealed ? hideLabel : revealLabel,
|
|
43
|
+
togglePressed: revealed,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createSegmentedControl,
|
|
3
|
+
getNextSegmentIndex,
|
|
4
|
+
type SegmentedControlSize,
|
|
5
|
+
type SegmentedControlValue,
|
|
6
|
+
type SegmentedControlProps,
|
|
7
|
+
type SegmentedControlAPI,
|
|
8
|
+
} from './segmented-control.js'
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
segmentedControlVariants,
|
|
12
|
+
segmentedControlItemVariants,
|
|
13
|
+
} from './segmented-control.styles.js'
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/** Pill-shaped container that holds the segments (`role="radiogroup"`). */
|
|
4
|
+
export const segmentedControlVariants = cva({
|
|
5
|
+
base: 'inline-flex items-center gap-1 rounded-lg bg-muted p-1',
|
|
6
|
+
variants: {
|
|
7
|
+
size: {
|
|
8
|
+
sm: '',
|
|
9
|
+
md: '',
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
defaultVariants: {
|
|
13
|
+
size: 'md',
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A single segment (`role="radio"`). The `state` variant carries the
|
|
19
|
+
* active vs inactive visual treatment so adapters never inline these classes.
|
|
20
|
+
*/
|
|
21
|
+
export const segmentedControlItemVariants = cva({
|
|
22
|
+
base: [
|
|
23
|
+
'inline-flex items-center justify-center gap-1.5 rounded-md font-medium',
|
|
24
|
+
'whitespace-nowrap transition-colors',
|
|
25
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
26
|
+
'focus-visible:ring-offset-2 focus-visible:ring-offset-muted',
|
|
27
|
+
].join(' '),
|
|
28
|
+
variants: {
|
|
29
|
+
size: {
|
|
30
|
+
sm: 'px-2.5 py-1 text-xs',
|
|
31
|
+
md: 'px-3 py-1.5 text-sm',
|
|
32
|
+
},
|
|
33
|
+
state: {
|
|
34
|
+
checked: 'bg-background text-foreground shadow-sm',
|
|
35
|
+
unchecked: 'text-muted-foreground hover:text-foreground',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
size: 'md',
|
|
40
|
+
state: 'unchecked',
|
|
41
|
+
},
|
|
42
|
+
})
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
export type SegmentedControlSize = 'sm' | 'md'
|
|
4
|
+
|
|
5
|
+
/** Value identifying a single segment. */
|
|
6
|
+
export type SegmentedControlValue = string
|
|
7
|
+
|
|
8
|
+
export interface SegmentedControlProps {
|
|
9
|
+
/** Currently selected segment value (controlled). */
|
|
10
|
+
value?: SegmentedControlValue
|
|
11
|
+
/** Visual size of the control. */
|
|
12
|
+
size?: SegmentedControlSize
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SegmentedControlAPI {
|
|
16
|
+
/** ARIA attributes to spread on the group element. */
|
|
17
|
+
ariaProps: Partial<AccessibilityProps>
|
|
18
|
+
/** Data attributes for CSS styling hooks. */
|
|
19
|
+
dataAttributes: Record<string, string>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Build the framework-agnostic group props for a segmented control.
|
|
24
|
+
*
|
|
25
|
+
* Returns `role="radiogroup"` plus data attributes; adapters spread these
|
|
26
|
+
* onto their group element and handle their own rendering/state.
|
|
27
|
+
*/
|
|
28
|
+
export function createSegmentedControl(
|
|
29
|
+
props: SegmentedControlProps = {},
|
|
30
|
+
): SegmentedControlAPI {
|
|
31
|
+
const { value, size = 'md' } = props
|
|
32
|
+
|
|
33
|
+
const ariaProps: Partial<AccessibilityProps> = {
|
|
34
|
+
role: 'radiogroup',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const dataAttributes: Record<string, string> = {
|
|
38
|
+
'data-size': size,
|
|
39
|
+
}
|
|
40
|
+
if (value !== undefined) {
|
|
41
|
+
dataAttributes['data-value'] = value
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { ariaProps, dataAttributes }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Pure keyboard-navigation rule shared by every adapter.
|
|
49
|
+
*
|
|
50
|
+
* Given the current index, the pressed key, and the item count, returns the
|
|
51
|
+
* index that focus/selection should move to. Arrow keys wrap; Home/End jump
|
|
52
|
+
* to the ends. Unhandled keys (or an empty group) return `current` unchanged.
|
|
53
|
+
*
|
|
54
|
+
* - ArrowRight / ArrowDown → next (wraps to 0 past the end)
|
|
55
|
+
* - ArrowLeft / ArrowUp → previous (wraps to last before 0)
|
|
56
|
+
* - Home → first
|
|
57
|
+
* - End → last
|
|
58
|
+
*/
|
|
59
|
+
export function getNextSegmentIndex(
|
|
60
|
+
current: number,
|
|
61
|
+
key: string,
|
|
62
|
+
count: number,
|
|
63
|
+
): number {
|
|
64
|
+
if (count <= 0) return current
|
|
65
|
+
|
|
66
|
+
switch (key) {
|
|
67
|
+
case 'ArrowRight':
|
|
68
|
+
case 'ArrowDown':
|
|
69
|
+
return (current + 1) % count
|
|
70
|
+
case 'ArrowLeft':
|
|
71
|
+
case 'ArrowUp':
|
|
72
|
+
return (current - 1 + count) % count
|
|
73
|
+
case 'Home':
|
|
74
|
+
return 0
|
|
75
|
+
case 'End':
|
|
76
|
+
return count - 1
|
|
77
|
+
default:
|
|
78
|
+
return current
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Visual variants for the separator rule.
|
|
5
|
+
*
|
|
6
|
+
* - `orientation: horizontal` → a full-width 1px line.
|
|
7
|
+
* - `orientation: vertical` → a full-height 1px line.
|
|
8
|
+
* - `labeled` → flex layout used by the labeled-divider variant, which
|
|
9
|
+
* arranges a centered label between two flanking lines.
|
|
10
|
+
*/
|
|
11
|
+
export const separatorVariants = cva({
|
|
12
|
+
base: '',
|
|
13
|
+
variants: {
|
|
14
|
+
orientation: {
|
|
15
|
+
horizontal: 'h-px w-full bg-border',
|
|
16
|
+
vertical: 'w-px h-full bg-border',
|
|
17
|
+
},
|
|
18
|
+
labeled: {
|
|
19
|
+
true: 'flex items-center',
|
|
20
|
+
false: '',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultVariants: {
|
|
24
|
+
orientation: 'horizontal',
|
|
25
|
+
labeled: 'false',
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
/** Class for each flanking line in the labeled-divider variant. */
|
|
30
|
+
export const separatorLineClass = 'h-px flex-1 bg-border'
|
|
31
|
+
|
|
32
|
+
/** Class for the centered label in the labeled-divider variant. */
|
|
33
|
+
export const separatorLabelClass =
|
|
34
|
+
'px-3 text-xs uppercase tracking-wide text-muted-foreground'
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
export type SeparatorOrientation = 'horizontal' | 'vertical'
|
|
4
|
+
|
|
5
|
+
export interface SeparatorProps {
|
|
6
|
+
/** Orientation of the separator. Defaults to `'horizontal'`. */
|
|
7
|
+
orientation?: SeparatorOrientation
|
|
8
|
+
/**
|
|
9
|
+
* When `true` (the default) the separator is purely visual and is hidden
|
|
10
|
+
* from assistive tech (`role="none"`). When `false` it is exposed as a
|
|
11
|
+
* semantic `role="separator"` with the appropriate `aria-orientation`.
|
|
12
|
+
*/
|
|
13
|
+
decorative?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* ARIA attributes for a separator. Extends the shared base with
|
|
18
|
+
* `aria-orientation`, which is specific to the `separator` role.
|
|
19
|
+
*/
|
|
20
|
+
export interface SeparatorAriaProps extends Partial<AccessibilityProps> {
|
|
21
|
+
'aria-orientation'?: SeparatorOrientation
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SeparatorAPI {
|
|
25
|
+
/** ARIA attributes to spread on the element. */
|
|
26
|
+
ariaProps: SeparatorAriaProps
|
|
27
|
+
/** Data attributes for CSS styling hooks. */
|
|
28
|
+
dataAttributes: Record<string, string>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Headless separator core — computes ARIA semantics and data attributes for
|
|
33
|
+
* a separator rule. JSX-free so it can be shared across framework adapters.
|
|
34
|
+
*
|
|
35
|
+
* Per ARIA, a decorative separator must not be announced (`role="none"`),
|
|
36
|
+
* while a semantic one exposes `role="separator"` and `aria-orientation`.
|
|
37
|
+
*/
|
|
38
|
+
export function createSeparator(props: SeparatorProps = {}): SeparatorAPI {
|
|
39
|
+
const { orientation = 'horizontal', decorative = true } = props
|
|
40
|
+
|
|
41
|
+
const ariaProps: SeparatorAriaProps = decorative
|
|
42
|
+
? { role: 'none' }
|
|
43
|
+
: { role: 'separator', 'aria-orientation': orientation }
|
|
44
|
+
|
|
45
|
+
const dataAttributes: Record<string, string> = {
|
|
46
|
+
'data-orientation': orientation,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
ariaProps,
|
|
51
|
+
dataAttributes,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createSocialAuthButton,
|
|
3
|
+
PROVIDER_LABELS,
|
|
4
|
+
PROVIDER_MONO,
|
|
5
|
+
type SocialProvider,
|
|
6
|
+
type SocialAuthButtonProps,
|
|
7
|
+
type SocialAuthButtonAPI,
|
|
8
|
+
} from './social-auth-button.js'
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
socialAuthButtonVariants,
|
|
12
|
+
socialAuthRowVariants,
|
|
13
|
+
lastUsedBadgeVariants,
|
|
14
|
+
} from './social-auth-button.styles.js'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/** Wrapper around an individual social auth button (positions the lastUsed badge). */
|
|
4
|
+
export const socialAuthButtonVariants = cva({
|
|
5
|
+
base: 'relative',
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
/** Responsive grid wrapper: one column on mobile, two columns from `sm` up. */
|
|
9
|
+
export const socialAuthRowVariants = cva({
|
|
10
|
+
base: 'grid grid-cols-1 sm:grid-cols-2 gap-3',
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
/** Floating "Last used" badge anchored to the top-right of a button. */
|
|
14
|
+
export const lastUsedBadgeVariants = cva({
|
|
15
|
+
base: 'absolute -top-2 -right-2 rounded-full bg-primary text-primary-foreground text-[10px] px-1.5',
|
|
16
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type SocialProvider = 'google' | 'github' | 'microsoft' | 'apple'
|
|
2
|
+
|
|
3
|
+
/** "Continue with …" label shown on each provider's button. */
|
|
4
|
+
export const PROVIDER_LABELS: Record<SocialProvider, string> = {
|
|
5
|
+
google: 'Continue with Google',
|
|
6
|
+
github: 'Continue with GitHub',
|
|
7
|
+
microsoft: 'Continue with Microsoft',
|
|
8
|
+
apple: 'Continue with Apple',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Which providers render their brand mark monochrome (currentColor). GitHub and
|
|
13
|
+
* Apple are monochrome by design so they read correctly on dark/outline
|
|
14
|
+
* surfaces; Google and Microsoft keep their full brand color.
|
|
15
|
+
*/
|
|
16
|
+
export const PROVIDER_MONO: Record<SocialProvider, boolean> = {
|
|
17
|
+
google: false,
|
|
18
|
+
github: true,
|
|
19
|
+
microsoft: false,
|
|
20
|
+
apple: true,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SocialAuthButtonProps {
|
|
24
|
+
/** Identity provider this button authenticates against. */
|
|
25
|
+
provider: SocialProvider
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SocialAuthButtonAPI {
|
|
29
|
+
/** Data attributes for CSS styling hooks. */
|
|
30
|
+
dataAttributes: Record<string, string>
|
|
31
|
+
/** "Continue with …" label for the provider. */
|
|
32
|
+
label: string
|
|
33
|
+
/** Whether the brand mark should render monochrome. */
|
|
34
|
+
mono: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Framework-agnostic core for a social sign-in button. Returns the provider
|
|
39
|
+
* label, monochrome hint, and the `data-provider` styling hook. No React/JSX.
|
|
40
|
+
*/
|
|
41
|
+
export function createSocialAuthButton(
|
|
42
|
+
props: SocialAuthButtonProps,
|
|
43
|
+
): SocialAuthButtonAPI {
|
|
44
|
+
const { provider } = props
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
dataAttributes: { 'data-provider': provider },
|
|
48
|
+
label: PROVIDER_LABELS[provider],
|
|
49
|
+
mono: PROVIDER_MONO[provider],
|
|
50
|
+
}
|
|
51
|
+
}
|